authorgravatar for git@paperclover.netclover caruso <git@paperclover.net> 2026-02-16 02:38:02-08:00
committergravatar for git@paperclover.netclover caruso <git@paperclover.net> 2026-02-18 02:36:58-08:00
logc5e1b81a764db3ca4debd1826c1e779e9e56b4b2
tree815bac26ddb794b2462314ce1a6ff24d44faea8a
parent4a31117a7678a74905aad5c6aec7e1f7d827061e
signature Signed by SSH key SHA256:xbd+BjjhyBfwk7GVoURf9Yx0gzDerHbvYv7SddNWmAs

chore: drop MDX, replacing it with Markodown

Through this process i've found how unstable my marko adapter is. instead of salvaging it, it will be better to just drop the entire rendering engine. Marko is doing great for me and I feel safe investing into their ecosystem.

54 files changed, 3262 insertions(+), 16501 deletions(-)

framework/engine/marko-runtime.ts deleted-139
......@@ -1,139 +0,0 @@
1// This file is used to integrate Marko into the Clover Engine and Sitegen
2// To use, replace the "marko/html" import with this file.
3export * from "#marko/html";
4
5export const _template: typeof marko._template = (
6 templateId,
7 renderer,
8) => {
9 const { render: renderFn } = marko._template(templateId, renderer);
10 function wrap({ content, ...attrs }: Record<string, unknown>, n: number) {
11 // Marko Custom Tags
12 const cloverAsyncMarker = { isAsync: false };
13 const r = render.current;
14 // Support using Marko outside of Clover SSR
15 const props = {
16 content: content ? () => content : undefined,
17 ...attrs,
18 };
19 if (!r) {
20 return renderer(props, n);
21 }
22 render.setCurrent(null);
23 const markoResult = renderFn.call(renderer, {
24 ...props,
25 $global: { clover: r, cloverAsyncMarker },
26 });
27 if (cloverAsyncMarker.isAsync) {
28 return markoResult.then(render.raw);
29 }
30 const rr = markoResult.toString();
31 return render.raw(rr);
32 }
33 wrap.render = renderFn;
34 wrap.unwrapped = renderer;
35 return wrap as unknown as Template;
36};
37
38export const _dynamic_tag: typeof marko._dynamic_tag = (
39 scopeId,
40 accessor,
41 tag,
42 inputOrArgs,
43 content,
44 inputIsArgs,
45 serializeReason,
46) => {
47 if (typeof tag === "function") {
48 clover: {
49 const unwrapped = (tag as any).unwrapped;
50 if (unwrapped) {
51 tag = unwrapped;
52 break clover;
53 }
54 const r = render.current ?? (marko.$global().clover as render.State);
55 if (!r) throw new Error("No Clover Render Active");
56 const subRender = render.init(r.async !== -1, r.context);
57 const resolved = render.resolveNode(
58 subRender,
59 render.element(
60 tag as render.Component,
61 inputOrArgs as Record<any, any>,
62 ),
63 );
64
65 if (subRender.async > 0) {
66 const marker = marko.$global().cloverAsyncMarker as Async;
67 marker.isAsync = true;
68
69 // Wait for async work to finish
70 const { resolve, reject, promise } = Promise.withResolvers<string>();
71 subRender.asyncDone = () => {
72 const rejections = subRender.rejections;
73 if (!rejections) return resolve(render.stringifyNode(resolved));
74 (r.rejections ??= []).push(...rejections);
75 return reject(new Error("Render had errors"));
76 };
77 marko._await(
78 scopeId,
79 accessor,
80 promise,
81 (string: string) => marko._html(string),
82 0,
83 );
84 } else {
85 marko._html(render.stringifyNode(resolved));
86 }
87 return;
88 }
89 }
90 return marko._dynamic_tag(
91 scopeId,
92 accessor,
93 tag,
94 inputOrArgs,
95 content,
96 inputIsArgs,
97 serializeReason,
98 );
99};
100
101export const _await: typeof marko._await = (
102 scopeId,
103 accessor,
104 promise,
105 callback,
106 serializeMarker,
107) => {
108 const marker = marko.$global().cloverAsyncMarker as Async;
109 marker.isAsync = true;
110 marko._await(scopeId, accessor, promise, callback, serializeMarker);
111};
112
113export function _escape(input: unknown) {
114 // The rationale of this check is that the default toString method
115 // creating `[object Object]` is universally useless to any end user.
116 if (
117 input == null ||
118 (typeof input === "object" &&
119 input &&
120 // only block this if it's the default `toString`
121 input.toString === Object.prototype.toString)
122 ) {
123 throw new Error(
124 `Unexpected value in template placeholder: '` +
125 render.inspect(input) +
126 "'. " +
127 `To emit a literal '${input}', use \${String(value)}`,
128 );
129 }
130 return marko._escape(input);
131}
132
133interface Async {
134 isAsync: boolean;
135}
136
137import type { Template } from "marko/common/types";
138import * as render from "lib/render.ts";
139import * as marko from "#marko/html";
framework/generate.ts+72-60
......@@ -183,7 +183,7 @@ export async function scanSiteSection(io: Io, section: sg.Section) {
183183 dir: sectionPath("pages"),
184184 list: pages,
185185 prefix: section.base,
186 include: [".tsx", ".mdx", ".marko"],
186 include: [".tsx", ".marko", ".mdo"],
187187 exclude: [".client.ts", ".client.tsx"],
188188 page: true,
189189 },
......@@ -205,7 +205,7 @@ export async function scanSiteSection(io: Io, section: sg.Section) {
205205 dir: sectionPath("views"),
206206 list: views,
207207 prefix: rootPrefix,
208 include: [".tsx", ".mdx", ".marko"],
208 include: [".tsx", ".marko", ".mdo"],
209209 exclude: [".client.ts", ".client.tsx"],
210210 page: false,
211211 },
......@@ -251,70 +251,79 @@ export async function preparePage(
251251 io: Io,
252252 { item, metaTemplate }: { item: sg.FileItem; metaTemplate: meta.Template },
253253) {
254 // -- load and validate module --
255 let {
256 default: Page,
257 meta: metadata,
258 theme: pageTheme,
259 layout,
260 regenerate,
261 } = await io.import<sg.PageExports>(item.file);
262 if (!Page) throw new Error("Page is missing a 'default' export.");
263 if (!metadata) throw new Error("Page is missing 'meta' export with a title.");
264
265 // -- css --
266 if (layout?.theme) pageTheme = layout.theme;
267 const theme: css.Theme = {
268 ...css.defaultTheme,
269 ...pageTheme,
270 };
271 const cssImports = Array.from(
272 new Set([globalCssPath, ...hot.getCssImports(item.file)]),
273 (file) => path.relative(hot.projectSrc, file),
274 );
254 try {
255 // -- load and validate module --
256 let {
257 default: Page,
258 meta: metadata,
259 theme: pageTheme,
260 layout,
261 regenerate,
262 markoOnly,
263 } = await io.import<sg.PageExports>(item.file);
264 if (!Page) throw new Error("Page is missing a 'default' export.");
265 if (!metadata) {
266 throw new Error("Page is missing 'meta' export with a title.");
267 }
268
269 // -- css --
270 if (layout?.theme) pageTheme = layout.theme;
271 const theme: css.Theme = {
272 ...css.defaultTheme,
273 ...pageTheme,
274 };
275 const cssImports = Array.from(
276 new Set([globalCssPath, ...hot.getCssImports(item.file)]),
277 (file) => path.relative(hot.projectSrc, file),
278 );
275279
276 // -- metadata --
277 const renderedMetaPromise = Promise.resolve(
278 typeof metadata === "function" ? metadata({ ssr: true }) : metadata,
279 ).then((m) =>
280 meta.render({
280 // -- metadata --
281 const renderedMeta = meta.render({
281282 canonical: item.id,
282 ...m,
283 }, metaTemplate)
284 );
283 ...typeof metadata === "function"
284 ? await metadata({ ssr: true })
285 : metadata,
286 }, metaTemplate);
287 if (!renderedMeta.includes("<title>")) {
288 throw new Error(
289 "Page is missing 'meta.title'. " + "All pages need a title tag.",
290 );
291 }
285292
286 // -- html --
287 let page = render.element(Page);
288 if (layout?.default) {
289 page = render.element(layout.default, { content: page });
290 }
293 // -- html --
294 let text: string;
295 const sitegen = sg.context.init();
296 if ("render" in Page) {
297 text = await SitegenWrap.render({
298 cloverScripts: sitegen.scripts,
299 page: Page,
300 attrs: {},
301 });
302 } else {
303 let page = render.element(Page);
304 if (layout?.default) {
305 page = render.element(layout.default, { content: page });
306 }
291307
292 const sitegen = sg.context.init();
293 const bodyPromise = render.async(page, [sitegen]);
308 text = (await render.async(page, [sitegen])).text;
309 }
294310
295 const [{ text }, renderedMeta] = await Promise.all([
296 bodyPromise,
297 renderedMetaPromise,
298 ]);
299 if (!renderedMeta.includes("<title>")) {
300 throw new Error(
301 "Page is missing 'meta.title'. " + "All pages need a title tag.",
302 );
311 const styleKey = css.styleKey(cssImports, theme);
312 return {
313 type: "page",
314 id: item.id,
315 file: item.file,
316 regenerate,
317 html: text,
318 meta: renderedMeta,
319 cssImports,
320 theme: theme ?? null,
321 styleKey,
322 clientRefs: Array.from(sitegen.scripts),
323 } as const;
324 } catch (e) {
325 error.rethrowWithMetadata(e, { page: item.id });
303326 }
304
305 const styleKey = css.styleKey(cssImports, theme);
306 return {
307 type: "page",
308 id: item.id,
309 file: item.file,
310 regenerate,
311 html: text,
312 meta: renderedMeta,
313 cssImports,
314 theme: theme ?? null,
315 styleKey,
316 clientRefs: Array.from(sitegen.scripts),
317 } as const;
318327}
319328
320329export async function prepareView(io: Io, item: sg.FileItem) {
......@@ -410,6 +419,9 @@ import * as render from "lib/render.ts";
410419import * as sg from "#sitegen";
411420import type { FileItem } from "#sitegen";
412421import * as progress from "../lib/progress.ts";
422import * as error from "lib/error.ts";
423
424import SitegenWrap from "./sitegen-wrap.marko";
413425
414426import * as bundle from "./bundle.ts";
415427import * as css from "./css.ts";
framework/hot.ts+4-13
......@@ -28,7 +28,7 @@ exts[".ts"] = loadEsbuild;
2828exts[".tsx"] = loadEsbuild;
2929exts[".jsx"] = loadEsbuild;
3030exts[".marko"] = loadMarko;
31exts[".mdx"] = loadMdx;
31exts[".mdo"] = loadMarkodown;
3232exts[".css"] = loadCss;
3333
3434// Intercept all module load calls to track CSS imports + file times.
......@@ -173,16 +173,9 @@ function loadMarko(module: NodeJS.Module, filepath: string) {
173173 lazyMarko ??= require<typeof import("./marko.ts")>("./framework/marko.ts");
174174 lazyMarko.loadMarko(module, filepath);
175175}
176
177function loadMdx(module: NodeJS.Module, filepath: string) {
178 const input = fs.readFileSync(filepath);
179 const out = mdx.compileSync(input, {
180 jsxImportSource: "#jsx",
181 providerImportSource: "#mdx",
182 rehypePlugins: [rehypeMdxCodeProps],
183 }).value;
184 const src = typeof out === "string" ? out : Buffer.from(out).toString("utf8");
185 return loadEsbuildCode(module, filepath, src);
176function loadMarkodown(module: NodeJS.Module, filepath: string) {
177 lazyMarko ??= require<typeof import("./marko.ts")>("./framework/marko.ts");
178 lazyMarko.loadMarkodown(module, filepath);
186179}
187180
188181function loadCss(module: NodeJS.Module, _filepath: string) {
......@@ -328,8 +321,6 @@ import * as path from "node:path";
328321import { pathToFileURL } from "node:url";
329322import * as esbuild from "esbuild";
330323import { createRequire } from "node:module";
331import * as mdx from "@mdx-js/mdx";
332import rehypeMdxCodeProps from "rehype-mdx-code-props";
333324import * as self from "./hot.ts";
334325import { Buffer } from "node:buffer";
335326import { UNWRAP } from "lib/assert.ts";
framework/lib/add-script.marko created+1
......@@ -0,0 +1 @@
1<const/_=$global.cloverScripts.add(input.value) />
framework/lib/sitegen.ts+5-2
......@@ -2,6 +2,7 @@ export type ScriptId = string;
22
33export interface PageExports extends ViewExports {
44 regenerate?: PageRegenerateOptions;
5 markoOnly?: boolean;
56}
67export interface ViewExports {
78 default: render.Component;
......@@ -10,9 +11,9 @@ export interface ViewExports {
1011 layout?: Layout;
1112}
1213export interface Layout {
13 default: render.Component;
14 default?: render.Component;
1415 theme?: css.Theme;
15 // TODO: nested layout
16 markoOnly?: boolean;
1617}
1718export interface PageRegenerateOptions {
1819 tags?: string[];
......@@ -97,6 +98,8 @@ export function joinScripts(scriptSources: string[]) {
9798 "].map(e=>e())";
9899}
99100
101export { default as CloverScriptInclude } from "./add-script.marko";
102
100103import * as render from "lib/render.ts";
101104import type * as meta from "lib/render/meta.ts";
102105import type * as css from "../css.ts";
framework/lib/view.ts+20-8
......@@ -53,10 +53,10 @@ type PropsFromModule<M extends any> = M extends {
5353
5454export async function renderToString<K extends Key>(
5555 id: K,
56 props: PropsFromModule<ViewMap[K]>,
56 attrs: PropsFromModule<ViewMap[K]>,
5757) {
5858 const {
59 component,
59 component: Page,
6060 inlineCss,
6161 layout,
6262 meta: metadata,
......@@ -67,18 +67,29 @@ export async function renderToString<K extends Key>(
6767
6868 // -- metadata --
6969 const renderedMetaPromise = Promise.resolve(
70 typeof metadata === "function" ? metadata(props) : metadata,
70 typeof metadata === "function" ? metadata(attrs) : metadata,
7171 ).then((m) => meta.render(m, codegen.metaTemplate));
7272
73 // -- html --
74 let page: render.Element = render.element(component, props);
75 if (layout) page = render.element(layout, { content: page });
73 let text;
7674 const sitegen = sg.context.init();
77 const { text: body } = await render.async(page, [sitegen]);
75 if ("render" in Page) {
76 text = await SitegenWrap.render({
77 cloverScripts: sitegen.scripts,
78 page: Page,
79 attrs,
80 });
81 } else {
82 let page = render.element(Page, attrs);
83 if (layout?.default) {
84 page = render.element(layout.default, { content: page });
85 }
86
87 text = (await render.async(page, [sitegen])).text;
88 }
7889
7990 // -- join document and send --
8091 return sg.wrapDocument({
81 body,
92 body: text,
8293 head: await renderedMetaPromise,
8394 inlineCss,
8495 scripts: sg.joinScripts(
......@@ -116,3 +127,4 @@ import type {
116127 RegisteredViews as ViewMap,
117128} from "../../.clover/ts/view.d.ts";
118129import { UNWRAP } from "lib/assert.ts";
130import SitegenWrap from "../sitegen-wrap.marko";
framework/marko.ts+42-3
......@@ -6,9 +6,47 @@ export interface MarkoCacheEntry {
66export const markoCache = new Map<string, MarkoCacheEntry>();
77
88export function loadMarko(module: NodeJS.Module, filepath: string) {
9 return loadMarkoExtra(module, filepath, "html");
10}
11export function loadMarkodown(module: NodeJS.Module, filepath: string) {
12 return loadMarkoExtra(module, filepath, "markdown");
13}
14
15function loadMarkoExtra(
16 module: NodeJS.Module,
17 filepath: string,
18 mode: "html" | "markdown",
19) {
920 let cache = markoCache.get(filepath);
1021 if (!cache) {
1122 let src = fs.readFileSync(filepath, "utf8");
23 if (mode === "markdown") {
24 let transform;
25 try {
26 transform = markodown.transform({
27 source: src,
28 selfImport: `./${path.basename(filepath)}`,
29 });
30 } catch (err) {
31 throw new Error(`Failed ${filepath}: Markodown Compiler Panic`, {
32 cause: err,
33 });
34 }
35 if (!transform.success) {
36 // TODO: better error handling
37 throw new Error(
38 "Failed " + filepath + ": " + JSON.stringify(transform.errors),
39 );
40 }
41 src = transform.text;
42
43 fs.writeMkdirSync(
44 ".clover/marko/" + path.relative(".", filepath).replace(/^src\//, "") +
45 ".marko",
46 src,
47 );
48 }
49
1250 // A non-standard thing here is Clover Sitegen implements
1351 // its own client side scripting stuff, so it overrides
1452 // bare client import statements to it's own usage.
......@@ -26,16 +64,16 @@ export function loadMarko(module: NodeJS.Module, filepath: string) {
2664 )
2765 } />`;
2866 },
29 ) + '\nimport { addScript as CloverScriptInclude } from "#sitegen";\n';
67 ) + '\nimport { CloverScriptInclude } from "#sitegen";\n';
3068 }
3169
32 const compiled = marko.compileSync(src, filepath, {
70 let compiled = marko.compileSync(src, filepath, {
3371 sourceMaps: true,
3472 });
3573 // workaround an esbuild bug?
3674 (compiled.map as unknown as { sourceRoot: string }).sourceRoot = "/";
3775 compiled.map.sources[0] = filepath;
38 src = compiled.code.replace("marko/debug/html", "#marko/runtime") +
76 src = compiled.code +
3977 "\n//# sourceMappingURL=data:application/json;charset=utf-8;base64," +
4078 Buffer.from(JSON.stringify(compiled.map), "utf-8").toString("base64");
4179 cache = { src, scannedClientRefs: Array.from(scannedClientRefs) };
......@@ -54,6 +92,7 @@ export function loadMarko(module: NodeJS.Module, filepath: string) {
5492}
5593
5694import * as marko from "@marko/compiler";
95import * as markodown from "@clo/markodown";
5796import * as hot from "./hot.ts";
5897import * as fs from "#sitegen/fs";
5998import * as path from "node:path";
framework/mdx-hack.tsx deleted-32
......@@ -1,32 +0,0 @@
1export function useMDXComponents() {
2 return {
3 pre: async (
4 { filename, whitespace, tint, content: [, , { className, content }] },
5 ) => {
6 const lang = className?.split("-")?.[1] ?? null;
7 const code = ["tsx", "jsx", "json", "html", "ts", "js"].includes(lang)
8 ? await highlight.highlightCode(
9 content.trim(),
10 lang,
11 )
12 : content.trim();
13 return (
14 <figure class={["code", tint]}>
15 {filename && <figcaption>{filename}</figcaption>}
16 <pre
17 style={whitespace ? { "white-space": whitespace } : {}}
18 ><code>{render.raw(code)}</code></pre>
19 </figure>
20 );
21 },
22 code: ({ content }) => {
23 return (
24 <code>
25 {content}
26 </code>
27 );
28 },
29 };
30}
31import * as highlight from "../src/file-viewer/highlight.ts";
32import * as render from "lib/render.ts";
framework/sitegen-wrap.marko created+2
......@@ -0,0 +1,2 @@
1<const/_=$global.cloverScripts=input.cloverScripts />
2<${input.page} ...input.attrs />
lib/render.ts+3
......@@ -1,4 +1,7 @@
11/**
2 * @deprecated replacing with pure Marko
3 * https://git.paperclover.net/clo/sitegen/issues/90
4 *
25 * Clover's rendering engine is the backbone of her website generator. It
36 * converts objects and components (functions returning {@linkcode Node}) into
47 * HTML. the engine is simple, self-contained, and easily extendable.
lib/render/meta.ts+4-2
......@@ -148,7 +148,7 @@ function renderMetadata(meta: Meta, template: Template): string {
148148 const referrer = meta.referrer ?? template.referrer ?? null;
149149 const themeColor = meta.themeColor ?? template.themeColor ?? null;
150150 const viewport = meta.viewport ?? template.viewport ??
151 "width=device-width, initial-scale=1.0";
151 "width=device-width, initial-scale=1, maximum-scale=1";
152152
153153 const embed = meta.embed ?? template.embed ?? null;
154154 let openGraph = meta.openGraph ?? template.openGraph ?? null;
......@@ -168,7 +168,9 @@ function renderMetadata(meta: Meta, template: Template): string {
168168 ...twitter,
169169 };
170170 if (thumbnail) {
171 openGraph.image = embed.thumbnail;
171 const resolved = new URL(thumbnail, template.base).href;
172 openGraph.image = resolved;
173 twitter.image = resolved;
172174 }
173175 if (siteTitle) {
174176 openGraph.site_name = siteTitle;
package-lock.json+255-2348
......@@ -4,30 +4,27 @@
44 "requires": true,
55 "packages": {
66 "": {
7 "name": "sitegen",
87 "dependencies": {
8 "@clo/markodown": "npm:@paperclover/markodown@rc",
99 "@hono/node-server": "1.14.3",
10 "@mdx-js/mdx": "3.1.0",
10 "@marko/compiler": "^5.39.55",
11 "@types/node": "24.0.10",
12 "browserslist": "4.28.1",
1113 "codemirror": "6.0.1",
12 "dashjs": "^5.0.3",
13 "devalue": "^5.3.2",
14 "dashjs": "5.0.3",
15 "devalue": "5.6.2",
1416 "esbuild": "0.25.5",
1517 "hls.js": "1.6.5",
16 "hono": "^4.10.3",
18 "hono": "4.11.9",
1719 "lib": "file:lib",
18 "marko": "6.0.82",
20 "marko": "^5.38.24",
1921 "msgpackr": "1.11.5",
2022 "puppeteer": "24.23.0",
21 "rehype-mdx-code-props": "^3.0.1",
2223 "source-map-support": "0.5.21",
24 "typescript": "5.8.3",
2325 "unique-names-generator": "4.7.1",
2426 "vscode-oniguruma": "2.0.1",
2527 "vscode-textmate": "9.2.0"
26 },
27 "devDependencies": {
28 "@types/node": "24.0.10",
29 "baseline-browser-mapping": "^2.9.14",
30 "typescript": "5.8.3"
3128 }
3229 },
3330 "lib": {},
......@@ -45,223 +42,6 @@
4542 "node": ">=6.9.0"
4643 }
4744 },
48 "node_modules/@babel/compat-data": {
49 "version": "7.28.4",
50 "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.4.tgz",
51 "integrity": "sha512-YsmSKC29MJwf0gF8Rjjrg5LQCmyh+j/nD8/eP7f+BeoQTKYqs9RoWbjGOdy0+1Ekr68RJZMUOPVQaQisnIo4Rw==",
52 "license": "MIT",
53 "engines": {
54 "node": ">=6.9.0"
55 }
56 },
57 "node_modules/@babel/core": {
58 "version": "7.28.4",
59 "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.4.tgz",
60 "integrity": "sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==",
61 "license": "MIT",
62 "peer": true,
63 "dependencies": {
64 "@babel/code-frame": "^7.27.1",
65 "@babel/generator": "^7.28.3",
66 "@babel/helper-compilation-targets": "^7.27.2",
67 "@babel/helper-module-transforms": "^7.28.3",
68 "@babel/helpers": "^7.28.4",
69 "@babel/parser": "^7.28.4",
70 "@babel/template": "^7.27.2",
71 "@babel/traverse": "^7.28.4",
72 "@babel/types": "^7.28.4",
73 "@jridgewell/remapping": "^2.3.5",
74 "convert-source-map": "^2.0.0",
75 "debug": "^4.1.0",
76 "gensync": "^1.0.0-beta.2",
77 "json5": "^2.2.3",
78 "semver": "^6.3.1"
79 },
80 "engines": {
81 "node": ">=6.9.0"
82 },
83 "funding": {
84 "type": "opencollective",
85 "url": "https://opencollective.com/babel"
86 }
87 },
88 "node_modules/@babel/generator": {
89 "version": "7.28.3",
90 "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.3.tgz",
91 "integrity": "sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==",
92 "license": "MIT",
93 "dependencies": {
94 "@babel/parser": "^7.28.3",
95 "@babel/types": "^7.28.2",
96 "@jridgewell/gen-mapping": "^0.3.12",
97 "@jridgewell/trace-mapping": "^0.3.28",
98 "jsesc": "^3.0.2"
99 },
100 "engines": {
101 "node": ">=6.9.0"
102 }
103 },
104 "node_modules/@babel/helper-annotate-as-pure": {
105 "version": "7.27.3",
106 "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz",
107 "integrity": "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==",
108 "license": "MIT",
109 "dependencies": {
110 "@babel/types": "^7.27.3"
111 },
112 "engines": {
113 "node": ">=6.9.0"
114 }
115 },
116 "node_modules/@babel/helper-compilation-targets": {
117 "version": "7.27.2",
118 "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz",
119 "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==",
120 "license": "MIT",
121 "dependencies": {
122 "@babel/compat-data": "^7.27.2",
123 "@babel/helper-validator-option": "^7.27.1",
124 "browserslist": "^4.24.0",
125 "lru-cache": "^5.1.1",
126 "semver": "^6.3.1"
127 },
128 "engines": {
129 "node": ">=6.9.0"
130 }
131 },
132 "node_modules/@babel/helper-create-class-features-plugin": {
133 "version": "7.28.3",
134 "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.3.tgz",
135 "integrity": "sha512-V9f6ZFIYSLNEbuGA/92uOvYsGCJNsuA8ESZ4ldc09bWk/j8H8TKiPw8Mk1eG6olpnO0ALHJmYfZvF4MEE4gajg==",
136 "license": "MIT",
137 "dependencies": {
138 "@babel/helper-annotate-as-pure": "^7.27.3",
139 "@babel/helper-member-expression-to-functions": "^7.27.1",
140 "@babel/helper-optimise-call-expression": "^7.27.1",
141 "@babel/helper-replace-supers": "^7.27.1",
142 "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1",
143 "@babel/traverse": "^7.28.3",
144 "semver": "^6.3.1"
145 },
146 "engines": {
147 "node": ">=6.9.0"
148 },
149 "peerDependencies": {
150 "@babel/core": "^7.0.0"
151 }
152 },
153 "node_modules/@babel/helper-globals": {
154 "version": "7.28.0",
155 "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz",
156 "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==",
157 "license": "MIT",
158 "engines": {
159 "node": ">=6.9.0"
160 }
161 },
162 "node_modules/@babel/helper-member-expression-to-functions": {
163 "version": "7.27.1",
164 "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.27.1.tgz",
165 "integrity": "sha512-E5chM8eWjTp/aNoVpcbfM7mLxu9XGLWYise2eBKGQomAk/Mb4XoxyqXTZbuTohbsl8EKqdlMhnDI2CCLfcs9wA==",
166 "license": "MIT",
167 "dependencies": {
168 "@babel/traverse": "^7.27.1",
169 "@babel/types": "^7.27.1"
170 },
171 "engines": {
172 "node": ">=6.9.0"
173 }
174 },
175 "node_modules/@babel/helper-module-imports": {
176 "version": "7.27.1",
177 "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz",
178 "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==",
179 "license": "MIT",
180 "dependencies": {
181 "@babel/traverse": "^7.27.1",
182 "@babel/types": "^7.27.1"
183 },
184 "engines": {
185 "node": ">=6.9.0"
186 }
187 },
188 "node_modules/@babel/helper-module-transforms": {
189 "version": "7.28.3",
190 "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz",
191 "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==",
192 "license": "MIT",
193 "dependencies": {
194 "@babel/helper-module-imports": "^7.27.1",
195 "@babel/helper-validator-identifier": "^7.27.1",
196 "@babel/traverse": "^7.28.3"
197 },
198 "engines": {
199 "node": ">=6.9.0"
200 },
201 "peerDependencies": {
202 "@babel/core": "^7.0.0"
203 }
204 },
205 "node_modules/@babel/helper-optimise-call-expression": {
206 "version": "7.27.1",
207 "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.27.1.tgz",
208 "integrity": "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==",
209 "license": "MIT",
210 "dependencies": {
211 "@babel/types": "^7.27.1"
212 },
213 "engines": {
214 "node": ">=6.9.0"
215 }
216 },
217 "node_modules/@babel/helper-plugin-utils": {
218 "version": "7.27.1",
219 "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz",
220 "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==",
221 "license": "MIT",
222 "engines": {
223 "node": ">=6.9.0"
224 }
225 },
226 "node_modules/@babel/helper-replace-supers": {
227 "version": "7.27.1",
228 "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.27.1.tgz",
229 "integrity": "sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA==",
230 "license": "MIT",
231 "dependencies": {
232 "@babel/helper-member-expression-to-functions": "^7.27.1",
233 "@babel/helper-optimise-call-expression": "^7.27.1",
234 "@babel/traverse": "^7.27.1"
235 },
236 "engines": {
237 "node": ">=6.9.0"
238 },
239 "peerDependencies": {
240 "@babel/core": "^7.0.0"
241 }
242 },
243 "node_modules/@babel/helper-skip-transparent-expression-wrappers": {
244 "version": "7.27.1",
245 "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.27.1.tgz",
246 "integrity": "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==",
247 "license": "MIT",
248 "dependencies": {
249 "@babel/traverse": "^7.27.1",
250 "@babel/types": "^7.27.1"
251 },
252 "engines": {
253 "node": ">=6.9.0"
254 }
255 },
256 "node_modules/@babel/helper-string-parser": {
257 "version": "7.27.1",
258 "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
259 "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
260 "license": "MIT",
261 "engines": {
262 "node": ">=6.9.0"
263 }
264 },
26545 "node_modules/@babel/helper-validator-identifier": {
26646 "version": "7.27.1",
26747 "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz",
......@@ -271,145 +51,26 @@
27151 "node": ">=6.9.0"
27252 }
27353 },
274 "node_modules/@babel/helper-validator-option": {
275 "version": "7.27.1",
276 "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz",
277 "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==",
278 "license": "MIT",
279 "engines": {
280 "node": ">=6.9.0"
281 }
282 },
283 "node_modules/@babel/helpers": {
284 "version": "7.28.4",
285 "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz",
286 "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==",
287 "license": "MIT",
288 "dependencies": {
289 "@babel/template": "^7.27.2",
290 "@babel/types": "^7.28.4"
291 },
292 "engines": {
293 "node": ">=6.9.0"
294 }
295 },
296 "node_modules/@babel/parser": {
297 "version": "7.28.4",
298 "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.4.tgz",
299 "integrity": "sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg==",
300 "license": "MIT",
301 "dependencies": {
302 "@babel/types": "^7.28.4"
303 },
304 "bin": {
305 "parser": "bin/babel-parser.js"
306 },
307 "engines": {
308 "node": ">=6.0.0"
309 }
310 },
311 "node_modules/@babel/plugin-syntax-typescript": {
312 "version": "7.27.1",
313 "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.27.1.tgz",
314 "integrity": "sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==",
315 "license": "MIT",
316 "dependencies": {
317 "@babel/helper-plugin-utils": "^7.27.1"
318 },
319 "engines": {
320 "node": ">=6.9.0"
321 },
322 "peerDependencies": {
323 "@babel/core": "^7.0.0-0"
324 }
325 },
326 "node_modules/@babel/plugin-transform-modules-commonjs": {
327 "version": "7.27.1",
328 "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.27.1.tgz",
329 "integrity": "sha512-OJguuwlTYlN0gBZFRPqwOGNWssZjfIUdS7HMYtN8c1KmwpwHFBwTeFZrg9XZa+DFTitWOW5iTAG7tyCUPsCCyw==",
330 "license": "MIT",
331 "dependencies": {
332 "@babel/helper-module-transforms": "^7.27.1",
333 "@babel/helper-plugin-utils": "^7.27.1"
334 },
335 "engines": {
336 "node": ">=6.9.0"
337 },
338 "peerDependencies": {
339 "@babel/core": "^7.0.0-0"
340 }
341 },
342 "node_modules/@babel/plugin-transform-typescript": {
343 "version": "7.28.0",
344 "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.28.0.tgz",
345 "integrity": "sha512-4AEiDEBPIZvLQaWlc9liCavE0xRM0dNca41WtBeM3jgFptfUOSG9z0uteLhq6+3rq+WB6jIvUwKDTpXEHPJ2Vg==",
346 "license": "MIT",
347 "dependencies": {
348 "@babel/helper-annotate-as-pure": "^7.27.3",
349 "@babel/helper-create-class-features-plugin": "^7.27.1",
350 "@babel/helper-plugin-utils": "^7.27.1",
351 "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1",
352 "@babel/plugin-syntax-typescript": "^7.27.1"
353 },
354 "engines": {
355 "node": ">=6.9.0"
356 },
54 "node_modules/@clo/markodown": {
55 "name": "@paperclover/markodown",
56 "version": "1.0.0-rc.15",
57 "resolved": "https://registry.npmjs.org/@paperclover/markodown/-/markodown-1.0.0-rc.15.tgz",
58 "integrity": "sha512-c4aTliRZUQ9DwQx3nFaftTE5GCvjAvCl0EF3nton6ah9zDoRu69nEuFbKOpSlxYrWizWzkPQVBu9xZY6gr+ezQ==",
35759 "peerDependencies": {
358 "@babel/core": "^7.0.0-0"
359 }
360 },
361 "node_modules/@babel/runtime": {
362 "version": "7.28.4",
363 "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz",
364 "integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==",
365 "license": "MIT",
366 "engines": {
367 "node": ">=6.9.0"
368 }
369 },
370 "node_modules/@babel/template": {
371 "version": "7.27.2",
372 "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz",
373 "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==",
374 "license": "MIT",
375 "dependencies": {
376 "@babel/code-frame": "^7.27.1",
377 "@babel/parser": "^7.27.2",
378 "@babel/types": "^7.27.1"
379 },
380 "engines": {
381 "node": ">=6.9.0"
382 }
383 },
384 "node_modules/@babel/traverse": {
385 "version": "7.28.4",
386 "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.4.tgz",
387 "integrity": "sha512-YEzuboP2qvQavAcjgQNVgsvHIDv6ZpwXvcvjmyySP2DIMuByS/6ioU5G9pYrWHM6T2YDfc7xga9iNzYOs12CFQ==",
388 "license": "MIT",
389 "dependencies": {
390 "@babel/code-frame": "^7.27.1",
391 "@babel/generator": "^7.28.3",
392 "@babel/helper-globals": "^7.28.0",
393 "@babel/parser": "^7.28.4",
394 "@babel/template": "^7.27.2",
395 "@babel/types": "^7.28.4",
396 "debug": "^4.3.1"
397 },
398 "engines": {
399 "node": ">=6.9.0"
400 }
401 },
402 "node_modules/@babel/types": {
403 "version": "7.28.4",
404 "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.4.tgz",
405 "integrity": "sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==",
406 "license": "MIT",
407 "dependencies": {
408 "@babel/helper-string-parser": "^7.27.1",
409 "@babel/helper-validator-identifier": "^7.27.1"
60 "@marko/compiler": "^5",
61 "esbuild": "0.x",
62 "rollup": "^4"
41063 },
411 "engines": {
412 "node": ">=6.9.0"
64 "peerDependenciesMeta": {
65 "@marko/compiler": {
66 "optional": true
67 },
68 "esbuild": {
69 "optional": true
70 },
71 "rollup": {
72 "optional": true
73 }
41374 }
41475 },
41576 "node_modules/@codemirror/autocomplete": {
......@@ -905,51 +566,12 @@
905566 "hono": "^4"
906567 }
907568 },
908 "node_modules/@jridgewell/gen-mapping": {
909 "version": "0.3.13",
910 "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
911 "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
912 "license": "MIT",
913 "dependencies": {
914 "@jridgewell/sourcemap-codec": "^1.5.0",
915 "@jridgewell/trace-mapping": "^0.3.24"
916 }
917 },
918 "node_modules/@jridgewell/remapping": {
919 "version": "2.3.5",
920 "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
921 "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
922 "license": "MIT",
923 "dependencies": {
924 "@jridgewell/gen-mapping": "^0.3.5",
925 "@jridgewell/trace-mapping": "^0.3.24"
926 }
927 },
928 "node_modules/@jridgewell/resolve-uri": {
929 "version": "3.1.2",
930 "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
931 "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
932 "license": "MIT",
933 "engines": {
934 "node": ">=6.0.0"
935 }
936 },
937569 "node_modules/@jridgewell/sourcemap-codec": {
938 "version": "1.5.4",
939 "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.4.tgz",
940 "integrity": "sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==",
570 "version": "1.5.5",
571 "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
572 "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
941573 "license": "MIT"
942574 },
943 "node_modules/@jridgewell/trace-mapping": {
944 "version": "0.3.31",
945 "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
946 "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
947 "license": "MIT",
948 "dependencies": {
949 "@jridgewell/resolve-uri": "^3.1.0",
950 "@jridgewell/sourcemap-codec": "^1.4.14"
951 }
952 },
953575 "node_modules/@lezer/common": {
954576 "version": "1.2.3",
955577 "resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.2.3.tgz",
......@@ -990,25 +612,16 @@
990612 "license": "MIT"
991613 },
992614 "node_modules/@marko/compiler": {
993 "version": "5.39.40",
994 "resolved": "https://registry.npmjs.org/@marko/compiler/-/compiler-5.39.40.tgz",
995 "integrity": "sha512-gQSdfJ5XOy01DtP7ULiqI1onp7Cix1QvnjM0KbfR2HMrWNI14V8V/2JguFAm3BzKEMZmmgvRLk0rvBzSm9FP6Q==",
615 "version": "5.39.55",
616 "resolved": "https://registry.npmjs.org/@marko/compiler/-/compiler-5.39.55.tgz",
617 "integrity": "sha512-QHukS/gMzxKWcF5xALVAH1swVAudCXC5DA2DDC5r+TzBiQ0v+uz7D8oNlwxO2xjTRPu7AUukcufHscbhytcyGg==",
996618 "license": "MIT",
619 "peer": true,
997620 "dependencies": {
998 "@babel/code-frame": "^7.27.1",
999 "@babel/core": "^7.28.0",
1000 "@babel/generator": "^7.28.0",
1001 "@babel/parser": "^7.28.0",
1002 "@babel/plugin-syntax-typescript": "^7.27.1",
1003 "@babel/plugin-transform-modules-commonjs": "^7.27.1",
1004 "@babel/plugin-transform-typescript": "^7.28.0",
1005 "@babel/runtime": "^7.28.2",
1006 "@babel/traverse": "^7.28.0",
1007 "@babel/types": "^7.28.2",
1008621 "@luxass/strip-json-comments": "^1.4.0",
1009622 "complain": "^1.6.1",
1010623 "he": "^1.2.0",
1011 "htmljs-parser": "^5.7.3",
624 "htmljs-parser": "^5.7.4",
1012625 "jsesc": "^3.1.0",
1013626 "kleur": "^4.1.5",
1014627 "lasso-package-root": "^1.0.1",
......@@ -1023,40 +636,18 @@
1023636 "node": "18 || 20 || >=22"
1024637 }
1025638 },
1026 "node_modules/@mdx-js/mdx": {
1027 "version": "3.1.0",
1028 "resolved": "https://registry.npmjs.org/@mdx-js/mdx/-/mdx-3.1.0.tgz",
1029 "integrity": "sha512-/QxEhPAvGwbQmy1Px8F899L5Uc2KZ6JtXwlCgJmjSTBedwOZkByYcBG4GceIGPXRDsmfxhHazuS+hlOShRLeDw==",
639 "node_modules/@marko/runtime-tags": {
640 "version": "6.0.145",
641 "resolved": "https://registry.npmjs.org/@marko/runtime-tags/-/runtime-tags-6.0.145.tgz",
642 "integrity": "sha512-3M65F25XylMRoitQZ7NmvSCk8IliBwhT8ay5AJaChJ55NvR9B2l2mnT0YcDvVQjqxKGUYU/xKfNmFYGcCj2RVA==",
1030643 "license": "MIT",
1031644 "dependencies": {
1032 "@types/estree": "^1.0.0",
1033 "@types/estree-jsx": "^1.0.0",
1034 "@types/hast": "^3.0.0",
1035 "@types/mdx": "^2.0.0",
1036 "collapse-white-space": "^2.0.0",
1037 "devlop": "^1.0.0",
1038 "estree-util-is-identifier-name": "^3.0.0",
1039 "estree-util-scope": "^1.0.0",
1040 "estree-walker": "^3.0.0",
1041 "hast-util-to-jsx-runtime": "^2.0.0",
1042 "markdown-extensions": "^2.0.0",
1043 "recma-build-jsx": "^1.0.0",
1044 "recma-jsx": "^1.0.0",
1045 "recma-stringify": "^1.0.0",
1046 "rehype-recma": "^1.0.0",
1047 "remark-mdx": "^3.0.0",
1048 "remark-parse": "^11.0.0",
1049 "remark-rehype": "^11.0.0",
1050 "source-map": "^0.7.0",
1051 "unified": "^11.0.0",
1052 "unist-util-position-from-estree": "^2.0.0",
1053 "unist-util-stringify-position": "^4.0.0",
1054 "unist-util-visit": "^5.0.0",
1055 "vfile": "^6.0.0"
645 "@marko/compiler": "^5.39.53",
646 "csstype": "^3.2.3",
647 "magic-string": "^0.30.21"
1056648 },
1057 "funding": {
1058 "type": "opencollective",
1059 "url": "https://opencollective.com/unified"
649 "engines": {
650 "node": "18 || 20 || >=22"
1060651 }
1061652 },
1062653 "node_modules/@msgpackr-extract/msgpackr-extract-darwin-arm64": {
......@@ -1170,242 +761,35 @@
1170761 "node": ">=10"
1171762 }
1172763 },
1173 "node_modules/@svta/cml-608": {
1174 "version": "1.0.1",
1175 "resolved": "https://registry.npmjs.org/@svta/cml-608/-/cml-608-1.0.1.tgz",
1176 "integrity": "sha512-Y/Ier9VPUSOBnf0bJqdDyTlPrt4dDB+jk5mYHa1bnD2kcRl8qn7KkW3PRuj4w1aVN+BS2eHmsLxodt7P2hylUg==",
1177 "license": "Apache-2.0",
1178 "engines": {
1179 "node": ">=20"
1180 }
764 "node_modules/@svta/common-media-library": {
765 "version": "0.12.4",
766 "resolved": "https://registry.npmjs.org/@svta/common-media-library/-/common-media-library-0.12.4.tgz",
767 "integrity": "sha512-9EuOoaNmz7JrfGwjsrD9SxF9otU5TNMnbLu1yU4BeLK0W5cDxVXXR58Z89q9u2AnHjIctscjMTYdlqQ1gojTuw==",
768 "license": "Apache-2.0"
1181769 },
1182 "node_modules/@svta/cml-cmcd": {
1183 "version": "1.0.1",
1184 "resolved": "https://registry.npmjs.org/@svta/cml-cmcd/-/cml-cmcd-1.0.1.tgz",
1185 "integrity": "sha512-eox305g+QUJgXqOLVrbgxeQHCgl90ewwQ9O2bIoo7m+hanR8Xswu5CknFnT5qqIbLOHfw80ug+raycoAFHTQ+w==",
1186 "license": "Apache-2.0",
1187 "engines": {
1188 "node": ">=20"
1189 },
1190 "peerDependencies": {
1191 "@svta/cml-cta": "1.0.1",
1192 "@svta/cml-structured-field-values": "1.0.1",
1193 "@svta/cml-utils": "1.0.1"
770 "node_modules/@tootallnate/quickjs-emscripten": {
771 "version": "0.23.0",
772 "resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz",
773 "integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==",
774 "license": "MIT"
775 },
776 "node_modules/@types/node": {
777 "version": "24.0.10",
778 "resolved": "https://registry.npmjs.org/@types/node/-/node-24.0.10.tgz",
779 "integrity": "sha512-ENHwaH+JIRTDIEEbDK6QSQntAYGtbvdDXnMXnZaZ6k13Du1dPMmprkEHIL7ok2Wl2aZevetwTAb5S+7yIF+enA==",
780 "license": "MIT",
781 "dependencies": {
782 "undici-types": "~7.8.0"
1194783 }
1195784 },
1196 "node_modules/@svta/cml-cmsd": {
1197 "version": "1.0.1",
1198 "resolved": "https://registry.npmjs.org/@svta/cml-cmsd/-/cml-cmsd-1.0.1.tgz",
1199 "integrity": "sha512-+nIB8PuSfb/qw+xGaArPhNqPm84tBJUbe3H1DnPL5QUsjSUI7mUIUQwAtRV1ZdEu0+80g9i0op79woB0OIwr/g==",
1200 "license": "Apache-2.0",
1201 "engines": {
1202 "node": ">=20"
1203 },
1204 "peerDependencies": {
1205 "@svta/cml-cta": "1.0.1",
1206 "@svta/cml-structured-field-values": "1.0.1",
1207 "@svta/cml-utils": "1.0.1"
1208 }
1209 },
1210 "node_modules/@svta/cml-cta": {
1211 "version": "1.0.1",
1212 "resolved": "https://registry.npmjs.org/@svta/cml-cta/-/cml-cta-1.0.1.tgz",
1213 "integrity": "sha512-jcXqNIPv26bmFxIOFh8/c3+6WLH4qBjKpq9qTQcggDPoHuV1YBydMsJLOnYPDeK8rNMKcAkFLbnDRvyJthu5yw==",
1214 "license": "Apache-2.0",
1215 "peer": true,
1216 "engines": {
1217 "node": ">=20"
1218 },
1219 "peerDependencies": {
1220 "@svta/cml-structured-field-values": "1.0.1",
1221 "@svta/cml-utils": "1.0.1"
1222 }
1223 },
1224 "node_modules/@svta/cml-dash": {
1225 "version": "1.0.1",
1226 "resolved": "https://registry.npmjs.org/@svta/cml-dash/-/cml-dash-1.0.1.tgz",
1227 "integrity": "sha512-lYnD1I7FUbbQND+xICI+kcRaRXuT+whKk27R8m8me5VMVu2sMsAMc7Yui6l9sxw2cBKt8pSETPYRm/1+n4LZkw==",
1228 "license": "Apache-2.0",
1229 "engines": {
1230 "node": ">=20"
1231 },
1232 "peerDependencies": {
1233 "@svta/cml-utils": "1.0.1"
1234 }
1235 },
1236 "node_modules/@svta/cml-id3": {
1237 "version": "1.0.1",
1238 "resolved": "https://registry.npmjs.org/@svta/cml-id3/-/cml-id3-1.0.1.tgz",
1239 "integrity": "sha512-90fGlL1qRI88CcaB89k6NG6cC3kky4Eu2jwqU4HefqK+S5k2OASUxf8JXkGz+DsdaiY7sh51vGPYdolfBZS7ug==",
1240 "license": "Apache-2.0",
1241 "engines": {
1242 "node": ">=20"
1243 },
1244 "peerDependencies": {
1245 "@svta/cml-utils": "1.0.1"
1246 }
1247 },
1248 "node_modules/@svta/cml-request": {
1249 "version": "1.0.1",
1250 "resolved": "https://registry.npmjs.org/@svta/cml-request/-/cml-request-1.0.1.tgz",
1251 "integrity": "sha512-enL19BuXUjFkDDDF9jdNwUclMNPRsagnjGAetVC7xcmpDMpEx+ZLgsDip6BFNg5p6izSEk/OyujTWW1r8bDNiA==",
1252 "license": "Apache-2.0",
1253 "engines": {
1254 "node": ">=20"
1255 },
1256 "peerDependencies": {
1257 "@svta/cml-utils": "1.0.1",
1258 "@svta/cml-xml": "1.0.1"
1259 }
1260 },
1261 "node_modules/@svta/cml-structured-field-values": {
1262 "version": "1.0.1",
1263 "resolved": "https://registry.npmjs.org/@svta/cml-structured-field-values/-/cml-structured-field-values-1.0.1.tgz",
1264 "integrity": "sha512-Kibciki59Pon3Pn/sl5uyrbJcSpZQDKqdCfDrokBvOdLoqqcd0oFrkEPsZBiuuIODX1CB80612xe8hopeFDyBA==",
1265 "license": "Apache-2.0",
1266 "peer": true,
1267 "engines": {
1268 "node": ">=20"
1269 },
1270 "peerDependencies": {
1271 "@svta/cml-utils": "1.0.1"
1272 }
1273 },
1274 "node_modules/@svta/cml-utils": {
1275 "version": "1.0.1",
1276 "resolved": "https://registry.npmjs.org/@svta/cml-utils/-/cml-utils-1.0.1.tgz",
1277 "integrity": "sha512-kso3curTJfp00I1mKFoBliBApjn4aPE+wF8cPucf7TrSDVWZDeLLuF14ASmUE9m7rnrqTTK4878VvmXaXcCCfQ==",
1278 "license": "Apache-2.0",
1279 "peer": true,
1280 "engines": {
1281 "node": ">=20"
1282 }
1283 },
1284 "node_modules/@svta/cml-xml": {
1285 "version": "1.0.1",
1286 "resolved": "https://registry.npmjs.org/@svta/cml-xml/-/cml-xml-1.0.1.tgz",
1287 "integrity": "sha512-11LkJa5kDEcsRMWkVI1ABH3KLCxGoiSVe4kQ293ItVj8ncTTQ7htmCGiJDjS+Cmy35UgF3e/vc0ysJIiWRTx2g==",
1288 "license": "Apache-2.0",
1289 "peer": true,
1290 "engines": {
1291 "node": ">=20"
1292 },
1293 "peerDependencies": {
1294 "@svta/cml-utils": "1.0.1"
1295 }
1296 },
1297 "node_modules/@tootallnate/quickjs-emscripten": {
1298 "version": "0.23.0",
1299 "resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz",
1300 "integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==",
1301 "license": "MIT"
1302 },
1303 "node_modules/@types/debug": {
1304 "version": "4.1.12",
1305 "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz",
1306 "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==",
1307 "license": "MIT",
1308 "dependencies": {
1309 "@types/ms": "*"
1310 }
1311 },
1312 "node_modules/@types/estree": {
1313 "version": "1.0.8",
1314 "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
1315 "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
1316 "license": "MIT"
1317 },
1318 "node_modules/@types/estree-jsx": {
1319 "version": "1.0.5",
1320 "resolved": "https://registry.npmjs.org/@types/estree-jsx/-/estree-jsx-1.0.5.tgz",
1321 "integrity": "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==",
1322 "license": "MIT",
1323 "dependencies": {
1324 "@types/estree": "*"
1325 }
1326 },
1327 "node_modules/@types/hast": {
1328 "version": "3.0.4",
1329 "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz",
1330 "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==",
1331 "license": "MIT",
1332 "dependencies": {
1333 "@types/unist": "*"
1334 }
1335 },
1336 "node_modules/@types/mdast": {
1337 "version": "4.0.4",
1338 "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz",
1339 "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==",
1340 "license": "MIT",
1341 "dependencies": {
1342 "@types/unist": "*"
1343 }
1344 },
1345 "node_modules/@types/mdx": {
1346 "version": "2.0.13",
1347 "resolved": "https://registry.npmjs.org/@types/mdx/-/mdx-2.0.13.tgz",
1348 "integrity": "sha512-+OWZQfAYyio6YkJb3HLxDrvnx6SWWDbC0zVPfBRzUk0/nqoDyf6dNxQi3eArPe8rJ473nobTMQ/8Zk+LxJ+Yuw==",
1349 "license": "MIT"
1350 },
1351 "node_modules/@types/ms": {
1352 "version": "2.1.0",
1353 "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz",
1354 "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==",
1355 "license": "MIT"
1356 },
1357 "node_modules/@types/node": {
1358 "version": "24.0.10",
1359 "resolved": "https://registry.npmjs.org/@types/node/-/node-24.0.10.tgz",
1360 "integrity": "sha512-ENHwaH+JIRTDIEEbDK6QSQntAYGtbvdDXnMXnZaZ6k13Du1dPMmprkEHIL7ok2Wl2aZevetwTAb5S+7yIF+enA==",
1361 "devOptional": true,
1362 "license": "MIT",
1363 "dependencies": {
1364 "undici-types": "~7.8.0"
1365 }
1366 },
1367 "node_modules/@types/unist": {
1368 "version": "3.0.3",
1369 "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz",
1370 "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==",
1371 "license": "MIT"
1372 },
1373 "node_modules/@types/yauzl": {
1374 "version": "2.10.3",
1375 "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz",
1376 "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==",
1377 "license": "MIT",
1378 "optional": true,
1379 "dependencies": {
1380 "@types/node": "*"
1381 }
1382 },
1383 "node_modules/@ungap/structured-clone": {
1384 "version": "1.3.0",
1385 "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz",
1386 "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==",
1387 "license": "ISC"
1388 },
1389 "node_modules/acorn": {
1390 "version": "8.15.0",
1391 "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
1392 "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
1393 "license": "MIT",
1394 "peer": true,
1395 "bin": {
1396 "acorn": "bin/acorn"
1397 },
1398 "engines": {
1399 "node": ">=0.4.0"
1400 }
1401 },
1402 "node_modules/acorn-jsx": {
1403 "version": "5.3.2",
1404 "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
1405 "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
1406 "license": "MIT",
1407 "peerDependencies": {
1408 "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
785 "node_modules/@types/yauzl": {
786 "version": "2.10.3",
787 "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz",
788 "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==",
789 "license": "MIT",
790 "optional": true,
791 "dependencies": {
792 "@types/node": "*"
1409793 }
1410794 },
1411795 "node_modules/agent-base": {
......@@ -1441,12 +825,33 @@
1441825 "url": "https://github.com/chalk/ansi-styles?sponsor=1"
1442826 }
1443827 },
828 "node_modules/app-module-path": {
829 "version": "2.2.0",
830 "resolved": "https://registry.npmjs.org/app-module-path/-/app-module-path-2.2.0.tgz",
831 "integrity": "sha512-gkco+qxENJV+8vFcDiiFhuoSvRXb2a/QPqpSoWhVz829VNJfOTnELbBmPmNKFxf3xdNnw4DWCkzkDaavcX/1YQ==",
832 "license": "BSD-2-Clause"
833 },
834 "node_modules/argly": {
835 "version": "1.2.0",
836 "resolved": "https://registry.npmjs.org/argly/-/argly-1.2.0.tgz",
837 "integrity": "sha512-+F3InkcH2XOGK7Jf/ZQis4cwZ4wbfmpBKo5J+SAA9bglT1gVd0e9hroHWarU076pJrAfs8JKuRPwDqwPBOKHnw==",
838 "license": "MIT"
839 },
1444840 "node_modules/argparse": {
1445841 "version": "2.0.1",
1446842 "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
1447843 "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
1448844 "license": "Python-2.0"
1449845 },
846 "node_modules/assertion-error": {
847 "version": "1.1.0",
848 "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz",
849 "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==",
850 "license": "MIT",
851 "engines": {
852 "node": "*"
853 }
854 },
1450855 "node_modules/ast-types": {
1451856 "version": "0.13.4",
1452857 "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz",
......@@ -1459,15 +864,6 @@
1459864 "node": ">=4"
1460865 }
1461866 },
1462 "node_modules/astring": {
1463 "version": "1.9.0",
1464 "resolved": "https://registry.npmjs.org/astring/-/astring-1.9.0.tgz",
1465 "integrity": "sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg==",
1466 "license": "MIT",
1467 "bin": {
1468 "astring": "bin/astring"
1469 }
1470 },
1471867 "node_modules/b4a": {
1472868 "version": "1.7.3",
1473869 "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.7.3.tgz",
......@@ -1482,15 +878,11 @@
1482878 }
1483879 }
1484880 },
1485 "node_modules/bail": {
1486 "version": "2.0.2",
1487 "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz",
1488 "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==",
1489 "license": "MIT",
1490 "funding": {
1491 "type": "github",
1492 "url": "https://github.com/sponsors/wooorm"
1493 }
881 "node_modules/balanced-match": {
882 "version": "1.0.2",
883 "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
884 "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
885 "license": "MIT"
1494886 },
1495887 "node_modules/bare-events": {
1496888 "version": "2.7.0",
......@@ -1576,9 +968,9 @@
1576968 }
1577969 },
1578970 "node_modules/baseline-browser-mapping": {
1579 "version": "2.9.14",
1580 "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.14.tgz",
1581 "integrity": "sha512-B0xUquLkiGLgHhpPBqvl7GWegWBUNuujQ6kXd/r1U38ElPT6Ok8KZ8e+FpUGEc2ZoRQUzq/aUnaKFc/svWUGSg==",
971 "version": "2.9.19",
972 "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.19.tgz",
973 "integrity": "sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==",
1582974 "license": "Apache-2.0",
1583975 "bin": {
1584976 "baseline-browser-mapping": "dist/cli.js"
......@@ -1632,10 +1024,25 @@
16321024 "url": "https://github.com/sponsors/wooorm"
16331025 }
16341026 },
1027 "node_modules/brace-expansion": {
1028 "version": "2.0.2",
1029 "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
1030 "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
1031 "license": "MIT",
1032 "dependencies": {
1033 "balanced-match": "^1.0.0"
1034 }
1035 },
1036 "node_modules/browser-refresh-client": {
1037 "version": "1.1.4",
1038 "resolved": "https://registry.npmjs.org/browser-refresh-client/-/browser-refresh-client-1.1.4.tgz",
1039 "integrity": "sha512-FM/UzMFsG7wJ1ocxCSl6U7qGAIWASEk+tlvfJLP2Pd1JfS4kQ1r4d5f+nNmQI8fB8sXSD8+u/mWErEkAMxUu3w==",
1040 "license": "MIT"
1041 },
16351042 "node_modules/browserslist": {
1636 "version": "4.26.3",
1637 "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.26.3.tgz",
1638 "integrity": "sha512-lAUU+02RFBuCKQPj/P6NgjlbCnLBMp4UtgTx7vNHd3XSIJF87s9a5rA3aH2yw3GS9DqZAUbOtZdCCiZeVRqt0w==",
1043 "version": "4.28.1",
1044 "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz",
1045 "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==",
16391046 "funding": [
16401047 {
16411048 "type": "opencollective",
......@@ -1653,11 +1060,11 @@
16531060 "license": "MIT",
16541061 "peer": true,
16551062 "dependencies": {
1656 "baseline-browser-mapping": "^2.8.9",
1657 "caniuse-lite": "^1.0.30001746",
1658 "electron-to-chromium": "^1.5.227",
1659 "node-releases": "^2.0.21",
1660 "update-browserslist-db": "^1.1.3"
1063 "baseline-browser-mapping": "^2.9.0",
1064 "caniuse-lite": "^1.0.30001759",
1065 "electron-to-chromium": "^1.5.263",
1066 "node-releases": "^2.0.27",
1067 "update-browserslist-db": "^1.2.0"
16611068 },
16621069 "bin": {
16631070 "browserslist": "cli.js"
......@@ -1691,9 +1098,9 @@
16911098 }
16921099 },
16931100 "node_modules/caniuse-lite": {
1694 "version": "1.0.30001747",
1695 "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001747.tgz",
1696 "integrity": "sha512-mzFa2DGIhuc5490Nd/G31xN1pnBnYMadtkyTjefPI7wzypqgCEpeWu9bJr0OnDsyKrW75zA9ZAt7pbQFmwLsQg==",
1101 "version": "1.0.30001770",
1102 "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001770.tgz",
1103 "integrity": "sha512-x/2CLQ1jHENRbHg5PSId2sXq1CIO1CISvwWAj027ltMVG2UNgW+w9oH2+HzgEIRFembL8bUlXtfbBHR1fCg2xw==",
16971104 "funding": [
16981105 {
16991106 "type": "opencollective",
......@@ -1710,54 +1117,18 @@
17101117 ],
17111118 "license": "CC-BY-4.0"
17121119 },
1713 "node_modules/ccount": {
1714 "version": "2.0.1",
1715 "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz",
1716 "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==",
1717 "license": "MIT",
1718 "funding": {
1719 "type": "github",
1720 "url": "https://github.com/sponsors/wooorm"
1721 }
1722 },
1723 "node_modules/character-entities": {
1724 "version": "2.0.2",
1725 "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz",
1726 "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==",
1727 "license": "MIT",
1728 "funding": {
1729 "type": "github",
1730 "url": "https://github.com/sponsors/wooorm"
1731 }
1732 },
1733 "node_modules/character-entities-html4": {
1734 "version": "2.1.0",
1735 "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz",
1736 "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==",
1737 "license": "MIT",
1738 "funding": {
1739 "type": "github",
1740 "url": "https://github.com/sponsors/wooorm"
1741 }
1742 },
1743 "node_modules/character-entities-legacy": {
1744 "version": "3.0.0",
1745 "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz",
1746 "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==",
1747 "license": "MIT",
1748 "funding": {
1749 "type": "github",
1750 "url": "https://github.com/sponsors/wooorm"
1751 }
1752 },
1753 "node_modules/character-reference-invalid": {
1754 "version": "2.0.1",
1755 "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz",
1756 "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==",
1120 "node_modules/chai": {
1121 "version": "3.5.0",
1122 "resolved": "https://registry.npmjs.org/chai/-/chai-3.5.0.tgz",
1123 "integrity": "sha512-eRYY0vPS2a9zt5w5Z0aCeWbrXTEyvk7u/Xf71EzNObrjSCPgMm1Nku/D/u2tiqHBX5j40wWhj54YJLtgn8g55A==",
17571124 "license": "MIT",
1758 "funding": {
1759 "type": "github",
1760 "url": "https://github.com/sponsors/wooorm"
1125 "dependencies": {
1126 "assertion-error": "^1.0.1",
1127 "deep-eql": "^0.1.3",
1128 "type-detect": "^1.0.0"
1129 },
1130 "engines": {
1131 "node": ">= 0.4.0"
17611132 }
17621133 },
17631134 "node_modules/chromium-bidi": {
......@@ -1808,16 +1179,6 @@
18081179 "@codemirror/view": "^6.0.0"
18091180 }
18101181 },
1811 "node_modules/collapse-white-space": {
1812 "version": "2.1.0",
1813 "resolved": "https://registry.npmjs.org/collapse-white-space/-/collapse-white-space-2.1.0.tgz",
1814 "integrity": "sha512-loKTxY1zCOuG4j9f6EPnuyyYkf58RnhhWTvRoZEokgB+WbdXehfjFviyOVYkqzEWz1Q5kRiZdBYS5SwxbQYwzw==",
1815 "license": "MIT",
1816 "funding": {
1817 "type": "github",
1818 "url": "https://github.com/sponsors/wooorm"
1819 }
1820 },
18211182 "node_modules/color-convert": {
18221183 "version": "2.0.1",
18231184 "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
......@@ -1836,16 +1197,6 @@
18361197 "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
18371198 "license": "MIT"
18381199 },
1839 "node_modules/comma-separated-tokens": {
1840 "version": "2.0.3",
1841 "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz",
1842 "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==",
1843 "license": "MIT",
1844 "funding": {
1845 "type": "github",
1846 "url": "https://github.com/sponsors/wooorm"
1847 }
1848 },
18491200 "node_modules/complain": {
18501201 "version": "1.6.1",
18511202 "resolved": "https://registry.npmjs.org/complain/-/complain-1.6.1.tgz",
......@@ -1855,12 +1206,6 @@
18551206 "error-stack-parser": "^2.0.1"
18561207 }
18571208 },
1858 "node_modules/convert-source-map": {
1859 "version": "2.0.0",
1860 "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
1861 "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
1862 "license": "MIT"
1863 },
18641209 "node_modules/cosmiconfig": {
18651210 "version": "9.0.0",
18661211 "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz",
......@@ -1894,24 +1239,18 @@
18941239 "license": "MIT"
18951240 },
18961241 "node_modules/csstype": {
1897 "version": "3.1.3",
1898 "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
1899 "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
1242 "version": "3.2.3",
1243 "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
1244 "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
19001245 "license": "MIT"
19011246 },
19021247 "node_modules/dashjs": {
1903 "version": "5.1.1",
1904 "resolved": "https://registry.npmjs.org/dashjs/-/dashjs-5.1.1.tgz",
1905 "integrity": "sha512-BzNXlUgzEjhuZ5M5hlSp1qIyQHZ7NpXAR0loP9DAAFVZj/ntL1DHeZ7qp/L3bvI4rq50X5indkAZQ3zEHWJoCA==",
1248 "version": "5.0.3",
1249 "resolved": "https://registry.npmjs.org/dashjs/-/dashjs-5.0.3.tgz",
1250 "integrity": "sha512-TXndNnCUjFjF2nYBxDVba+hWRpVkadkQ8flLp7kHkem+5+wZTfRShJCnVkPUosmjS0YPE9fVNLbYPJxHBeQZvA==",
19061251 "license": "BSD-3-Clause",
19071252 "dependencies": {
1908 "@svta/cml-608": "1.0.1",
1909 "@svta/cml-cmcd": "1.0.1",
1910 "@svta/cml-cmsd": "1.0.1",
1911 "@svta/cml-dash": "1.0.1",
1912 "@svta/cml-id3": "1.0.1",
1913 "@svta/cml-request": "1.0.1",
1914 "@svta/cml-xml": "1.0.1",
1253 "@svta/common-media-library": "^0.12.4",
19151254 "bcp-47-match": "^2.0.3",
19161255 "bcp-47-normalize": "^2.3.0",
19171256 "codem-isoboxer": "0.3.10",
......@@ -1949,17 +1288,25 @@
19491288 }
19501289 }
19511290 },
1952 "node_modules/decode-named-character-reference": {
1953 "version": "1.2.0",
1954 "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.2.0.tgz",
1955 "integrity": "sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q==",
1291 "node_modules/deep-eql": {
1292 "version": "0.1.3",
1293 "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-0.1.3.tgz",
1294 "integrity": "sha512-6sEotTRGBFiNcqVoeHwnfopbSpi5NbH1VWJmYCVkmxMmaVTT0bUTrNaGyBwhgP4MZL012W/mkzIn3Da+iDYweg==",
19561295 "license": "MIT",
19571296 "dependencies": {
1958 "character-entities": "^2.0.0"
1297 "type-detect": "0.1.1"
19591298 },
1960 "funding": {
1961 "type": "github",
1962 "url": "https://github.com/sponsors/wooorm"
1299 "engines": {
1300 "node": "*"
1301 }
1302 },
1303 "node_modules/deep-eql/node_modules/type-detect": {
1304 "version": "0.1.1",
1305 "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-0.1.1.tgz",
1306 "integrity": "sha512-5rqszGVwYgBoDkIm2oUtvkfZMQ0vk29iDMU0W2qCa3rG0vPDNczCMT4hV/bLBgLg8k8ri6+u3Zbt+S/14eMzlA==",
1307 "license": "MIT",
1308 "engines": {
1309 "node": "*"
19631310 }
19641311 },
19651312 "node_modules/degenerator": {
......@@ -1976,15 +1323,6 @@
19761323 "node": ">= 14"
19771324 }
19781325 },
1979 "node_modules/dequal": {
1980 "version": "2.0.3",
1981 "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
1982 "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==",
1983 "license": "MIT",
1984 "engines": {
1985 "node": ">=6"
1986 }
1987 },
19881326 "node_modules/detect-libc": {
19891327 "version": "2.0.4",
19901328 "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz",
......@@ -2001,19 +1339,6 @@
20011339 "integrity": "sha512-nPRkjWzzDQlsejL1WVifk5rvcFi/y1onBRxjaFMjZeR9mFpqu2gmAZ9xUB9/IEanEP/vBtGeGganC/GO1fmufg==",
20021340 "license": "MIT"
20031341 },
2004 "node_modules/devlop": {
2005 "version": "1.1.0",
2006 "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz",
2007 "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==",
2008 "license": "MIT",
2009 "dependencies": {
2010 "dequal": "^2.0.0"
2011 },
2012 "funding": {
2013 "type": "github",
2014 "url": "https://github.com/sponsors/wooorm"
2015 }
2016 },
20171342 "node_modules/devtools-protocol": {
20181343 "version": "0.0.1508733",
20191344 "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1508733.tgz",
......@@ -2022,9 +1347,9 @@
20221347 "peer": true
20231348 },
20241349 "node_modules/electron-to-chromium": {
2025 "version": "1.5.229",
2026 "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.229.tgz",
2027 "integrity": "sha512-cwhDcZKGcT/rEthLRJ9eBlMDkh1sorgsuk+6dpsehV0g9CABsIqBxU4rLRjG+d/U6pYU1s37A4lSKrVc5lSQYg==",
1350 "version": "1.5.286",
1351 "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.286.tgz",
1352 "integrity": "sha512-9tfDXhJ4RKFNerfjdCcZfufu49vg620741MNs26a9+bhLThdB+plgMeou98CAaHu/WATj2iHOOHTp1hWtABj2A==",
20281353 "license": "ISC"
20291354 },
20301355 "node_modules/emoji-regex": {
......@@ -2069,44 +1394,13 @@
20691394 "stackframe": "^1.3.4"
20701395 }
20711396 },
2072 "node_modules/esast-util-from-estree": {
2073 "version": "2.0.0",
2074 "resolved": "https://registry.npmjs.org/esast-util-from-estree/-/esast-util-from-estree-2.0.0.tgz",
2075 "integrity": "sha512-4CyanoAudUSBAn5K13H4JhsMH6L9ZP7XbLVe/dKybkxMO7eDyLsT8UHl9TRNrU2Gr9nz+FovfSIjuXWJ81uVwQ==",
2076 "license": "MIT",
2077 "dependencies": {
2078 "@types/estree-jsx": "^1.0.0",
2079 "devlop": "^1.0.0",
2080 "estree-util-visit": "^2.0.0",
2081 "unist-util-position-from-estree": "^2.0.0"
2082 },
2083 "funding": {
2084 "type": "opencollective",
2085 "url": "https://opencollective.com/unified"
2086 }
2087 },
2088 "node_modules/esast-util-from-js": {
2089 "version": "2.0.1",
2090 "resolved": "https://registry.npmjs.org/esast-util-from-js/-/esast-util-from-js-2.0.1.tgz",
2091 "integrity": "sha512-8Ja+rNJ0Lt56Pcf3TAmpBZjmx8ZcK5Ts4cAzIOjsjevg9oSXJnl6SUQ2EevU8tv3h6ZLWmoKL5H4fgWvdvfETw==",
2092 "license": "MIT",
2093 "dependencies": {
2094 "@types/estree-jsx": "^1.0.0",
2095 "acorn": "^8.0.0",
2096 "esast-util-from-estree": "^2.0.0",
2097 "vfile-message": "^4.0.0"
2098 },
2099 "funding": {
2100 "type": "opencollective",
2101 "url": "https://opencollective.com/unified"
2102 }
2103 },
21041397 "node_modules/esbuild": {
21051398 "version": "0.25.5",
21061399 "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.5.tgz",
21071400 "integrity": "sha512-P8OtKZRv/5J5hhz0cUAdu/cLuPIKXpQl1R9pZtvmHWQvrAUVd0UNIPT4IB4W3rNOqVO0rlqHmCIbSwxh/c9yUQ==",
21081401 "hasInstallScript": true,
21091402 "license": "MIT",
1403 "peer": true,
21101404 "bin": {
21111405 "esbuild": "bin/esbuild"
21121406 },
......@@ -2203,116 +1497,22 @@
22031497 "node": ">=4.0"
22041498 }
22051499 },
2206 "node_modules/estree-util-attach-comments": {
2207 "version": "3.0.0",
2208 "resolved": "https://registry.npmjs.org/estree-util-attach-comments/-/estree-util-attach-comments-3.0.0.tgz",
2209 "integrity": "sha512-cKUwm/HUcTDsYh/9FgnuFqpfquUbwIqwKM26BVCGDPVgvaCl/nDCCjUfiLlx6lsEZ3Z4RFxNbOQ60pkaEwFxGw==",
2210 "license": "MIT",
2211 "dependencies": {
2212 "@types/estree": "^1.0.0"
2213 },
2214 "funding": {
2215 "type": "opencollective",
2216 "url": "https://opencollective.com/unified"
1500 "node_modules/esutils": {
1501 "version": "2.0.3",
1502 "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
1503 "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
1504 "license": "BSD-2-Clause",
1505 "engines": {
1506 "node": ">=0.10.0"
22171507 }
22181508 },
2219 "node_modules/estree-util-build-jsx": {
2220 "version": "3.0.1",
2221 "resolved": "https://registry.npmjs.org/estree-util-build-jsx/-/estree-util-build-jsx-3.0.1.tgz",
2222 "integrity": "sha512-8U5eiL6BTrPxp/CHbs2yMgP8ftMhR5ww1eIKoWRMlqvltHF8fZn5LRDvTKuxD3DUn+shRbLGqXemcP51oFCsGQ==",
1509 "node_modules/events-light": {
1510 "version": "1.0.5",
1511 "resolved": "https://registry.npmjs.org/events-light/-/events-light-1.0.5.tgz",
1512 "integrity": "sha512-jF51LJzg5W+tkJgfZbjlbFCLcyVFEtOjU+xMCBylrXG13X5XHvfp6lNGfyBLF9u1mRTpUsMVYqSDukvpZff1mQ==",
22231513 "license": "MIT",
22241514 "dependencies": {
2225 "@types/estree-jsx": "^1.0.0",
2226 "devlop": "^1.0.0",
2227 "estree-util-is-identifier-name": "^3.0.0",
2228 "estree-walker": "^3.0.0"
2229 },
2230 "funding": {
2231 "type": "opencollective",
2232 "url": "https://opencollective.com/unified"
2233 }
2234 },
2235 "node_modules/estree-util-is-identifier-name": {
2236 "version": "3.0.0",
2237 "resolved": "https://registry.npmjs.org/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz",
2238 "integrity": "sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==",
2239 "license": "MIT",
2240 "funding": {
2241 "type": "opencollective",
2242 "url": "https://opencollective.com/unified"
2243 }
2244 },
2245 "node_modules/estree-util-scope": {
2246 "version": "1.0.0",
2247 "resolved": "https://registry.npmjs.org/estree-util-scope/-/estree-util-scope-1.0.0.tgz",
2248 "integrity": "sha512-2CAASclonf+JFWBNJPndcOpA8EMJwa0Q8LUFJEKqXLW6+qBvbFZuF5gItbQOs/umBUkjviCSDCbBwU2cXbmrhQ==",
2249 "license": "MIT",
2250 "dependencies": {
2251 "@types/estree": "^1.0.0",
2252 "devlop": "^1.0.0"
2253 },
2254 "funding": {
2255 "type": "opencollective",
2256 "url": "https://opencollective.com/unified"
2257 }
2258 },
2259 "node_modules/estree-util-to-js": {
2260 "version": "2.0.0",
2261 "resolved": "https://registry.npmjs.org/estree-util-to-js/-/estree-util-to-js-2.0.0.tgz",
2262 "integrity": "sha512-WDF+xj5rRWmD5tj6bIqRi6CkLIXbbNQUcxQHzGysQzvHmdYG2G7p/Tf0J0gpxGgkeMZNTIjT/AoSvC9Xehcgdg==",
2263 "license": "MIT",
2264 "dependencies": {
2265 "@types/estree-jsx": "^1.0.0",
2266 "astring": "^1.8.0",
2267 "source-map": "^0.7.0"
2268 },
2269 "funding": {
2270 "type": "opencollective",
2271 "url": "https://opencollective.com/unified"
2272 }
2273 },
2274 "node_modules/estree-util-value-to-estree": {
2275 "version": "3.4.1",
2276 "resolved": "https://registry.npmjs.org/estree-util-value-to-estree/-/estree-util-value-to-estree-3.4.1.tgz",
2277 "integrity": "sha512-E4fEc8KLhDXnbyDa5XrbdT9PbgSMt0AGZPFUsGFok8N2Q7DTO+F6xAFJjIdw71EkidRg186I1mQCKzZ1ZbEsCw==",
2278 "license": "MIT",
2279 "dependencies": {
2280 "@types/estree": "^1.0.0"
2281 },
2282 "funding": {
2283 "url": "https://github.com/sponsors/remcohaszing"
2284 }
2285 },
2286 "node_modules/estree-util-visit": {
2287 "version": "2.0.0",
2288 "resolved": "https://registry.npmjs.org/estree-util-visit/-/estree-util-visit-2.0.0.tgz",
2289 "integrity": "sha512-m5KgiH85xAhhW8Wta0vShLcUvOsh3LLPI2YVwcbio1l7E09NTLL1EyMZFM1OyWowoH0skScNbhOPl4kcBgzTww==",
2290 "license": "MIT",
2291 "dependencies": {
2292 "@types/estree-jsx": "^1.0.0",
2293 "@types/unist": "^3.0.0"
2294 },
2295 "funding": {
2296 "type": "opencollective",
2297 "url": "https://opencollective.com/unified"
2298 }
2299 },
2300 "node_modules/estree-walker": {
2301 "version": "3.0.3",
2302 "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz",
2303 "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==",
2304 "license": "MIT",
2305 "dependencies": {
2306 "@types/estree": "^1.0.0"
2307 }
2308 },
2309 "node_modules/esutils": {
2310 "version": "2.0.3",
2311 "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
2312 "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
2313 "license": "BSD-2-Clause",
2314 "engines": {
2315 "node": ">=0.10.0"
1515 "chai": "^3.5.0"
23161516 }
23171517 },
23181518 "node_modules/events-universal": {
......@@ -2324,12 +1524,6 @@
23241524 "bare-events": "^2.7.0"
23251525 }
23261526 },
2327 "node_modules/extend": {
2328 "version": "3.0.2",
2329 "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
2330 "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==",
2331 "license": "MIT"
2332 },
23331527 "node_modules/extract-zip": {
23341528 "version": "2.0.1",
23351529 "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz",
......@@ -2371,15 +1565,6 @@
23711565 "pend": "~1.2.0"
23721566 }
23731567 },
2374 "node_modules/gensync": {
2375 "version": "1.0.0-beta.2",
2376 "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
2377 "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
2378 "license": "MIT",
2379 "engines": {
2380 "node": ">=6.9.0"
2381 }
2382 },
23831568 "node_modules/get-caller-file": {
23841569 "version": "2.0.5",
23851570 "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
......@@ -2418,93 +1603,6 @@
24181603 "node": ">= 14"
24191604 }
24201605 },
2421 "node_modules/hast-util-properties-to-mdx-jsx-attributes": {
2422 "version": "1.0.1",
2423 "resolved": "https://registry.npmjs.org/hast-util-properties-to-mdx-jsx-attributes/-/hast-util-properties-to-mdx-jsx-attributes-1.0.1.tgz",
2424 "integrity": "sha512-ZzxhjHZ+gyxaPIFp/nuRpVL4GIFoqzfH6vNgjaA3CuUAV6XCxYwAQfRczrZRkgL6msi6DdOl+/QEduOdzszvbg==",
2425 "license": "MIT",
2426 "dependencies": {
2427 "@types/estree": "^1.0.0",
2428 "@types/hast": "^3.0.0",
2429 "comma-separated-tokens": "^2.0.0",
2430 "estree-util-value-to-estree": "^3.0.0",
2431 "mdast-util-mdx-jsx": "^3.0.0",
2432 "property-information": "^7.0.0",
2433 "space-separated-tokens": "^2.0.0",
2434 "style-to-js": "^1.0.0"
2435 },
2436 "funding": {
2437 "url": "https://github.com/sponsors/remcohaszing"
2438 }
2439 },
2440 "node_modules/hast-util-to-estree": {
2441 "version": "3.1.3",
2442 "resolved": "https://registry.npmjs.org/hast-util-to-estree/-/hast-util-to-estree-3.1.3.tgz",
2443 "integrity": "sha512-48+B/rJWAp0jamNbAAf9M7Uf//UVqAoMmgXhBdxTDJLGKY+LRnZ99qcG+Qjl5HfMpYNzS5v4EAwVEF34LeAj7w==",
2444 "license": "MIT",
2445 "dependencies": {
2446 "@types/estree": "^1.0.0",
2447 "@types/estree-jsx": "^1.0.0",
2448 "@types/hast": "^3.0.0",
2449 "comma-separated-tokens": "^2.0.0",
2450 "devlop": "^1.0.0",
2451 "estree-util-attach-comments": "^3.0.0",
2452 "estree-util-is-identifier-name": "^3.0.0",
2453 "hast-util-whitespace": "^3.0.0",
2454 "mdast-util-mdx-expression": "^2.0.0",
2455 "mdast-util-mdx-jsx": "^3.0.0",
2456 "mdast-util-mdxjs-esm": "^2.0.0",
2457 "property-information": "^7.0.0",
2458 "space-separated-tokens": "^2.0.0",
2459 "style-to-js": "^1.0.0",
2460 "unist-util-position": "^5.0.0",
2461 "zwitch": "^2.0.0"
2462 },
2463 "funding": {
2464 "type": "opencollective",
2465 "url": "https://opencollective.com/unified"
2466 }
2467 },
2468 "node_modules/hast-util-to-jsx-runtime": {
2469 "version": "2.3.6",
2470 "resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.6.tgz",
2471 "integrity": "sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==",
2472 "license": "MIT",
2473 "dependencies": {
2474 "@types/estree": "^1.0.0",
2475 "@types/hast": "^3.0.0",
2476 "@types/unist": "^3.0.0",
2477 "comma-separated-tokens": "^2.0.0",
2478 "devlop": "^1.0.0",
2479 "estree-util-is-identifier-name": "^3.0.0",
2480 "hast-util-whitespace": "^3.0.0",
2481 "mdast-util-mdx-expression": "^2.0.0",
2482 "mdast-util-mdx-jsx": "^3.0.0",
2483 "mdast-util-mdxjs-esm": "^2.0.0",
2484 "property-information": "^7.0.0",
2485 "space-separated-tokens": "^2.0.0",
2486 "style-to-js": "^1.0.0",
2487 "unist-util-position": "^5.0.0",
2488 "vfile-message": "^4.0.0"
2489 },
2490 "funding": {
2491 "type": "opencollective",
2492 "url": "https://opencollective.com/unified"
2493 }
2494 },
2495 "node_modules/hast-util-whitespace": {
2496 "version": "3.0.0",
2497 "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz",
2498 "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==",
2499 "license": "MIT",
2500 "dependencies": {
2501 "@types/hast": "^3.0.0"
2502 },
2503 "funding": {
2504 "type": "opencollective",
2505 "url": "https://opencollective.com/unified"
2506 }
2507 },
25081606 "node_modules/he": {
25091607 "version": "1.2.0",
25101608 "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
......@@ -2521,9 +1619,9 @@
25211619 "license": "Apache-2.0"
25221620 },
25231621 "node_modules/hono": {
2524 "version": "4.11.7",
2525 "resolved": "https://registry.npmjs.org/hono/-/hono-4.11.7.tgz",
2526 "integrity": "sha512-l7qMiNee7t82bH3SeyUCt9UF15EVmaBvsppY2zQtrbIhl/yzBTny+YUxsVjSjQ6gaqaeVtZmGocom8TzBlA4Yw==",
1622 "version": "4.11.9",
1623 "resolved": "https://registry.npmjs.org/hono/-/hono-4.11.9.tgz",
1624 "integrity": "sha512-Eaw2YTGM6WOxA6CXbckaEvslr2Ne4NFsKrvc0v97JD5awbmeBLO5w9Ho9L9kmKonrwF9RJlW6BxT1PVv/agBHQ==",
25271625 "license": "MIT",
25281626 "peer": true,
25291627 "engines": {
......@@ -2547,9 +1645,9 @@
25471645 "license": "MIT"
25481646 },
25491647 "node_modules/htmljs-parser": {
2550 "version": "5.7.4",
2551 "resolved": "https://registry.npmjs.org/htmljs-parser/-/htmljs-parser-5.7.4.tgz",
2552 "integrity": "sha512-PCGI7/Z10oBkpKKqbQ9Wgd0GZjSQ/dV7QlBjOmaqD1FkBsHWk730BW9mJCbnXfRbtZCdLh8M8IA8YeKPu9tGaQ==",
1648 "version": "5.10.0",
1649 "resolved": "https://registry.npmjs.org/htmljs-parser/-/htmljs-parser-5.10.0.tgz",
1650 "integrity": "sha512-5aAmTYhhiyBaBArw51R9c1m5BxqN3QBRpvhKRanWwTW1I59Y9YHvfGOBU12dfy7dPqgkwFKCUh0LX+ia34HHFw==",
25531651 "license": "MIT"
25541652 },
25551653 "node_modules/http-proxy-agent": {
......@@ -2618,12 +1716,6 @@
26181716 "sax": "1.2.1"
26191717 }
26201718 },
2621 "node_modules/inline-style-parser": {
2622 "version": "0.2.4",
2623 "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.4.tgz",
2624 "integrity": "sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q==",
2625 "license": "MIT"
2626 },
26271719 "node_modules/ip-address": {
26281720 "version": "10.0.1",
26291721 "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.0.1.tgz",
......@@ -2682,28 +1774,6 @@
26821774 "node": ">=8"
26831775 }
26841776 },
2685 "node_modules/is-hexadecimal": {
2686 "version": "2.0.1",
2687 "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz",
2688 "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==",
2689 "license": "MIT",
2690 "funding": {
2691 "type": "github",
2692 "url": "https://github.com/sponsors/wooorm"
2693 }
2694 },
2695 "node_modules/is-plain-obj": {
2696 "version": "4.1.0",
2697 "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz",
2698 "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==",
2699 "license": "MIT",
2700 "engines": {
2701 "node": ">=12"
2702 },
2703 "funding": {
2704 "url": "https://github.com/sponsors/sindresorhus"
2705 }
2706 },
27071777 "node_modules/js-tokens": {
27081778 "version": "4.0.0",
27091779 "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
......@@ -2740,18 +1810,6 @@
27401810 "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==",
27411811 "license": "MIT"
27421812 },
2743 "node_modules/json5": {
2744 "version": "2.2.3",
2745 "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
2746 "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
2747 "license": "MIT",
2748 "bin": {
2749 "json5": "lib/cli.js"
2750 },
2751 "engines": {
2752 "node": ">=6"
2753 }
2754 },
27551813 "node_modules/kleur": {
27561814 "version": "4.1.5",
27571815 "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz",
......@@ -2798,834 +1856,73 @@
27981856 "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
27991857 "license": "MIT"
28001858 },
1859 "node_modules/listener-tracker": {
1860 "version": "2.0.0",
1861 "resolved": "https://registry.npmjs.org/listener-tracker/-/listener-tracker-2.0.0.tgz",
1862 "integrity": "sha512-U6NLzBRyrAsJs9AAjuBYifXtNYnAIDPIp81rNpxNoypXBR7qi/LhsuUWX5399zuTg1sBEQyOnWDYFrBQ28vk/w==",
1863 "license": "Apache-2.0"
1864 },
28011865 "node_modules/localforage": {
28021866 "version": "1.10.0",
28031867 "resolved": "https://registry.npmjs.org/localforage/-/localforage-1.10.0.tgz",
2804 "integrity": "sha512-14/H1aX7hzBBmmh7sGPd+AOMkkIrHM3Z1PAyGgZigA1H1p5O5ANnMyWzvpAETtG68/dC4pC0ncy3+PPGzXZHPg==",
2805 "license": "Apache-2.0",
2806 "dependencies": {
2807 "lie": "3.1.1"
2808 }
2809 },
2810 "node_modules/longest-streak": {
2811 "version": "3.1.0",
2812 "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz",
2813 "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==",
2814 "license": "MIT",
2815 "funding": {
2816 "type": "github",
2817 "url": "https://github.com/sponsors/wooorm"
2818 }
2819 },
2820 "node_modules/lru-cache": {
2821 "version": "5.1.1",
2822 "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
2823 "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
2824 "license": "ISC",
2825 "dependencies": {
2826 "yallist": "^3.0.2"
2827 }
2828 },
2829 "node_modules/magic-string": {
2830 "version": "0.30.17",
2831 "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz",
2832 "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==",
2833 "license": "MIT",
2834 "dependencies": {
2835 "@jridgewell/sourcemap-codec": "^1.5.0"
2836 }
2837 },
2838 "node_modules/markdown-extensions": {
2839 "version": "2.0.0",
2840 "resolved": "https://registry.npmjs.org/markdown-extensions/-/markdown-extensions-2.0.0.tgz",
2841 "integrity": "sha512-o5vL7aDWatOTX8LzaS1WMoaoxIiLRQJuIKKe2wAw6IeULDHaqbiqiggmx+pKvZDb1Sj+pE46Sn1T7lCqfFtg1Q==",
2842 "license": "MIT",
2843 "engines": {
2844 "node": ">=16"
2845 },
2846 "funding": {
2847 "url": "https://github.com/sponsors/sindresorhus"
2848 }
2849 },
2850 "node_modules/marko": {
2851 "version": "6.0.82",
2852 "resolved": "https://registry.npmjs.org/marko/-/marko-6.0.82.tgz",
2853 "integrity": "sha512-xnBm299wmqrK8++12dhGHBUonm2SZgQVPNhHZjfp3Wc7k4U/qPbKrc7H1DJgU7JrYv8sSdxkconisynDqBYC1g==",
2854 "license": "MIT",
2855 "dependencies": {
2856 "@marko/compiler": "^5.39.40",
2857 "csstype": "^3.1.3",
2858 "magic-string": "^0.30.17"
2859 },
2860 "engines": {
2861 "node": "18 || 20 || >=22"
2862 }
2863 },
2864 "node_modules/mdast-util-from-markdown": {
2865 "version": "2.0.2",
2866 "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.2.tgz",
2867 "integrity": "sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==",
2868 "license": "MIT",
2869 "dependencies": {
2870 "@types/mdast": "^4.0.0",
2871 "@types/unist": "^3.0.0",
2872 "decode-named-character-reference": "^1.0.0",
2873 "devlop": "^1.0.0",
2874 "mdast-util-to-string": "^4.0.0",
2875 "micromark": "^4.0.0",
2876 "micromark-util-decode-numeric-character-reference": "^2.0.0",
2877 "micromark-util-decode-string": "^2.0.0",
2878 "micromark-util-normalize-identifier": "^2.0.0",
2879 "micromark-util-symbol": "^2.0.0",
2880 "micromark-util-types": "^2.0.0",
2881 "unist-util-stringify-position": "^4.0.0"
2882 },
2883 "funding": {
2884 "type": "opencollective",
2885 "url": "https://opencollective.com/unified"
2886 }
2887 },
2888 "node_modules/mdast-util-mdx": {
2889 "version": "3.0.0",
2890 "resolved": "https://registry.npmjs.org/mdast-util-mdx/-/mdast-util-mdx-3.0.0.tgz",
2891 "integrity": "sha512-JfbYLAW7XnYTTbUsmpu0kdBUVe+yKVJZBItEjwyYJiDJuZ9w4eeaqks4HQO+R7objWgS2ymV60GYpI14Ug554w==",
2892 "license": "MIT",
2893 "dependencies": {
2894 "mdast-util-from-markdown": "^2.0.0",
2895 "mdast-util-mdx-expression": "^2.0.0",
2896 "mdast-util-mdx-jsx": "^3.0.0",
2897 "mdast-util-mdxjs-esm": "^2.0.0",
2898 "mdast-util-to-markdown": "^2.0.0"
2899 },
2900 "funding": {
2901 "type": "opencollective",
2902 "url": "https://opencollective.com/unified"
2903 }
2904 },
2905 "node_modules/mdast-util-mdx-expression": {
2906 "version": "2.0.1",
2907 "resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz",
2908 "integrity": "sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==",
2909 "license": "MIT",
2910 "dependencies": {
2911 "@types/estree-jsx": "^1.0.0",
2912 "@types/hast": "^3.0.0",
2913 "@types/mdast": "^4.0.0",
2914 "devlop": "^1.0.0",
2915 "mdast-util-from-markdown": "^2.0.0",
2916 "mdast-util-to-markdown": "^2.0.0"
2917 },
2918 "funding": {
2919 "type": "opencollective",
2920 "url": "https://opencollective.com/unified"
2921 }
2922 },
2923 "node_modules/mdast-util-mdx-jsx": {
2924 "version": "3.2.0",
2925 "resolved": "https://registry.npmjs.org/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.2.0.tgz",
2926 "integrity": "sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==",
2927 "license": "MIT",
2928 "dependencies": {
2929 "@types/estree-jsx": "^1.0.0",
2930 "@types/hast": "^3.0.0",
2931 "@types/mdast": "^4.0.0",
2932 "@types/unist": "^3.0.0",
2933 "ccount": "^2.0.0",
2934 "devlop": "^1.1.0",
2935 "mdast-util-from-markdown": "^2.0.0",
2936 "mdast-util-to-markdown": "^2.0.0",
2937 "parse-entities": "^4.0.0",
2938 "stringify-entities": "^4.0.0",
2939 "unist-util-stringify-position": "^4.0.0",
2940 "vfile-message": "^4.0.0"
2941 },
2942 "funding": {
2943 "type": "opencollective",
2944 "url": "https://opencollective.com/unified"
2945 }
2946 },
2947 "node_modules/mdast-util-mdxjs-esm": {
2948 "version": "2.0.1",
2949 "resolved": "https://registry.npmjs.org/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-2.0.1.tgz",
2950 "integrity": "sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==",
2951 "license": "MIT",
2952 "dependencies": {
2953 "@types/estree-jsx": "^1.0.0",
2954 "@types/hast": "^3.0.0",
2955 "@types/mdast": "^4.0.0",
2956 "devlop": "^1.0.0",
2957 "mdast-util-from-markdown": "^2.0.0",
2958 "mdast-util-to-markdown": "^2.0.0"
2959 },
2960 "funding": {
2961 "type": "opencollective",
2962 "url": "https://opencollective.com/unified"
2963 }
2964 },
2965 "node_modules/mdast-util-phrasing": {
2966 "version": "4.1.0",
2967 "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz",
2968 "integrity": "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==",
2969 "license": "MIT",
2970 "dependencies": {
2971 "@types/mdast": "^4.0.0",
2972 "unist-util-is": "^6.0.0"
2973 },
2974 "funding": {
2975 "type": "opencollective",
2976 "url": "https://opencollective.com/unified"
2977 }
2978 },
2979 "node_modules/mdast-util-to-hast": {
2980 "version": "13.2.1",
2981 "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.1.tgz",
2982 "integrity": "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==",
2983 "license": "MIT",
2984 "dependencies": {
2985 "@types/hast": "^3.0.0",
2986 "@types/mdast": "^4.0.0",
2987 "@ungap/structured-clone": "^1.0.0",
2988 "devlop": "^1.0.0",
2989 "micromark-util-sanitize-uri": "^2.0.0",
2990 "trim-lines": "^3.0.0",
2991 "unist-util-position": "^5.0.0",
2992 "unist-util-visit": "^5.0.0",
2993 "vfile": "^6.0.0"
2994 },
2995 "funding": {
2996 "type": "opencollective",
2997 "url": "https://opencollective.com/unified"
2998 }
2999 },
3000 "node_modules/mdast-util-to-markdown": {
3001 "version": "2.1.2",
3002 "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz",
3003 "integrity": "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==",
3004 "license": "MIT",
3005 "dependencies": {
3006 "@types/mdast": "^4.0.0",
3007 "@types/unist": "^3.0.0",
3008 "longest-streak": "^3.0.0",
3009 "mdast-util-phrasing": "^4.0.0",
3010 "mdast-util-to-string": "^4.0.0",
3011 "micromark-util-classify-character": "^2.0.0",
3012 "micromark-util-decode-string": "^2.0.0",
3013 "unist-util-visit": "^5.0.0",
3014 "zwitch": "^2.0.0"
3015 },
3016 "funding": {
3017 "type": "opencollective",
3018 "url": "https://opencollective.com/unified"
3019 }
3020 },
3021 "node_modules/mdast-util-to-string": {
3022 "version": "4.0.0",
3023 "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz",
3024 "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==",
3025 "license": "MIT",
3026 "dependencies": {
3027 "@types/mdast": "^4.0.0"
3028 },
3029 "funding": {
3030 "type": "opencollective",
3031 "url": "https://opencollective.com/unified"
3032 }
3033 },
3034 "node_modules/micromark": {
3035 "version": "4.0.2",
3036 "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz",
3037 "integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==",
3038 "funding": [
3039 {
3040 "type": "GitHub Sponsors",
3041 "url": "https://github.com/sponsors/unifiedjs"
3042 },
3043 {
3044 "type": "OpenCollective",
3045 "url": "https://opencollective.com/unified"
3046 }
3047 ],
3048 "license": "MIT",
3049 "dependencies": {
3050 "@types/debug": "^4.0.0",
3051 "debug": "^4.0.0",
3052 "decode-named-character-reference": "^1.0.0",
3053 "devlop": "^1.0.0",
3054 "micromark-core-commonmark": "^2.0.0",
3055 "micromark-factory-space": "^2.0.0",
3056 "micromark-util-character": "^2.0.0",
3057 "micromark-util-chunked": "^2.0.0",
3058 "micromark-util-combine-extensions": "^2.0.0",
3059 "micromark-util-decode-numeric-character-reference": "^2.0.0",
3060 "micromark-util-encode": "^2.0.0",
3061 "micromark-util-normalize-identifier": "^2.0.0",
3062 "micromark-util-resolve-all": "^2.0.0",
3063 "micromark-util-sanitize-uri": "^2.0.0",
3064 "micromark-util-subtokenize": "^2.0.0",
3065 "micromark-util-symbol": "^2.0.0",
3066 "micromark-util-types": "^2.0.0"
3067 }
3068 },
3069 "node_modules/micromark-core-commonmark": {
3070 "version": "2.0.3",
3071 "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz",
3072 "integrity": "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==",
3073 "funding": [
3074 {
3075 "type": "GitHub Sponsors",
3076 "url": "https://github.com/sponsors/unifiedjs"
3077 },
3078 {
3079 "type": "OpenCollective",
3080 "url": "https://opencollective.com/unified"
3081 }
3082 ],
3083 "license": "MIT",
3084 "dependencies": {
3085 "decode-named-character-reference": "^1.0.0",
3086 "devlop": "^1.0.0",
3087 "micromark-factory-destination": "^2.0.0",
3088 "micromark-factory-label": "^2.0.0",
3089 "micromark-factory-space": "^2.0.0",
3090 "micromark-factory-title": "^2.0.0",
3091 "micromark-factory-whitespace": "^2.0.0",
3092 "micromark-util-character": "^2.0.0",
3093 "micromark-util-chunked": "^2.0.0",
3094 "micromark-util-classify-character": "^2.0.0",
3095 "micromark-util-html-tag-name": "^2.0.0",
3096 "micromark-util-normalize-identifier": "^2.0.0",
3097 "micromark-util-resolve-all": "^2.0.0",
3098 "micromark-util-subtokenize": "^2.0.0",
3099 "micromark-util-symbol": "^2.0.0",
3100 "micromark-util-types": "^2.0.0"
3101 }
3102 },
3103 "node_modules/micromark-extension-mdx-expression": {
3104 "version": "3.0.1",
3105 "resolved": "https://registry.npmjs.org/micromark-extension-mdx-expression/-/micromark-extension-mdx-expression-3.0.1.tgz",
3106 "integrity": "sha512-dD/ADLJ1AeMvSAKBwO22zG22N4ybhe7kFIZ3LsDI0GlsNr2A3KYxb0LdC1u5rj4Nw+CHKY0RVdnHX8vj8ejm4Q==",
3107 "funding": [
3108 {
3109 "type": "GitHub Sponsors",
3110 "url": "https://github.com/sponsors/unifiedjs"
3111 },
3112 {
3113 "type": "OpenCollective",
3114 "url": "https://opencollective.com/unified"
3115 }
3116 ],
3117 "license": "MIT",
3118 "dependencies": {
3119 "@types/estree": "^1.0.0",
3120 "devlop": "^1.0.0",
3121 "micromark-factory-mdx-expression": "^2.0.0",
3122 "micromark-factory-space": "^2.0.0",
3123 "micromark-util-character": "^2.0.0",
3124 "micromark-util-events-to-acorn": "^2.0.0",
3125 "micromark-util-symbol": "^2.0.0",
3126 "micromark-util-types": "^2.0.0"
3127 }
3128 },
3129 "node_modules/micromark-extension-mdx-jsx": {
3130 "version": "3.0.2",
3131 "resolved": "https://registry.npmjs.org/micromark-extension-mdx-jsx/-/micromark-extension-mdx-jsx-3.0.2.tgz",
3132 "integrity": "sha512-e5+q1DjMh62LZAJOnDraSSbDMvGJ8x3cbjygy2qFEi7HCeUT4BDKCvMozPozcD6WmOt6sVvYDNBKhFSz3kjOVQ==",
3133 "license": "MIT",
3134 "dependencies": {
3135 "@types/estree": "^1.0.0",
3136 "devlop": "^1.0.0",
3137 "estree-util-is-identifier-name": "^3.0.0",
3138 "micromark-factory-mdx-expression": "^2.0.0",
3139 "micromark-factory-space": "^2.0.0",
3140 "micromark-util-character": "^2.0.0",
3141 "micromark-util-events-to-acorn": "^2.0.0",
3142 "micromark-util-symbol": "^2.0.0",
3143 "micromark-util-types": "^2.0.0",
3144 "vfile-message": "^4.0.0"
3145 },
3146 "funding": {
3147 "type": "opencollective",
3148 "url": "https://opencollective.com/unified"
3149 }
3150 },
3151 "node_modules/micromark-extension-mdx-md": {
3152 "version": "2.0.0",
3153 "resolved": "https://registry.npmjs.org/micromark-extension-mdx-md/-/micromark-extension-mdx-md-2.0.0.tgz",
3154 "integrity": "sha512-EpAiszsB3blw4Rpba7xTOUptcFeBFi+6PY8VnJ2hhimH+vCQDirWgsMpz7w1XcZE7LVrSAUGb9VJpG9ghlYvYQ==",
3155 "license": "MIT",
3156 "dependencies": {
3157 "micromark-util-types": "^2.0.0"
3158 },
3159 "funding": {
3160 "type": "opencollective",
3161 "url": "https://opencollective.com/unified"
3162 }
3163 },
3164 "node_modules/micromark-extension-mdxjs": {
3165 "version": "3.0.0",
3166 "resolved": "https://registry.npmjs.org/micromark-extension-mdxjs/-/micromark-extension-mdxjs-3.0.0.tgz",
3167 "integrity": "sha512-A873fJfhnJ2siZyUrJ31l34Uqwy4xIFmvPY1oj+Ean5PHcPBYzEsvqvWGaWcfEIr11O5Dlw3p2y0tZWpKHDejQ==",
3168 "license": "MIT",
3169 "dependencies": {
3170 "acorn": "^8.0.0",
3171 "acorn-jsx": "^5.0.0",
3172 "micromark-extension-mdx-expression": "^3.0.0",
3173 "micromark-extension-mdx-jsx": "^3.0.0",
3174 "micromark-extension-mdx-md": "^2.0.0",
3175 "micromark-extension-mdxjs-esm": "^3.0.0",
3176 "micromark-util-combine-extensions": "^2.0.0",
3177 "micromark-util-types": "^2.0.0"
3178 },
3179 "funding": {
3180 "type": "opencollective",
3181 "url": "https://opencollective.com/unified"
3182 }
3183 },
3184 "node_modules/micromark-extension-mdxjs-esm": {
3185 "version": "3.0.0",
3186 "resolved": "https://registry.npmjs.org/micromark-extension-mdxjs-esm/-/micromark-extension-mdxjs-esm-3.0.0.tgz",
3187 "integrity": "sha512-DJFl4ZqkErRpq/dAPyeWp15tGrcrrJho1hKK5uBS70BCtfrIFg81sqcTVu3Ta+KD1Tk5vAtBNElWxtAa+m8K9A==",
3188 "license": "MIT",
3189 "dependencies": {
3190 "@types/estree": "^1.0.0",
3191 "devlop": "^1.0.0",
3192 "micromark-core-commonmark": "^2.0.0",
3193 "micromark-util-character": "^2.0.0",
3194 "micromark-util-events-to-acorn": "^2.0.0",
3195 "micromark-util-symbol": "^2.0.0",
3196 "micromark-util-types": "^2.0.0",
3197 "unist-util-position-from-estree": "^2.0.0",
3198 "vfile-message": "^4.0.0"
3199 },
3200 "funding": {
3201 "type": "opencollective",
3202 "url": "https://opencollective.com/unified"
3203 }
3204 },
3205 "node_modules/micromark-factory-destination": {
3206 "version": "2.0.1",
3207 "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz",
3208 "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==",
3209 "funding": [
3210 {
3211 "type": "GitHub Sponsors",
3212 "url": "https://github.com/sponsors/unifiedjs"
3213 },
3214 {
3215 "type": "OpenCollective",
3216 "url": "https://opencollective.com/unified"
3217 }
3218 ],
3219 "license": "MIT",
3220 "dependencies": {
3221 "micromark-util-character": "^2.0.0",
3222 "micromark-util-symbol": "^2.0.0",
3223 "micromark-util-types": "^2.0.0"
3224 }
3225 },
3226 "node_modules/micromark-factory-label": {
3227 "version": "2.0.1",
3228 "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz",
3229 "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==",
3230 "funding": [
3231 {
3232 "type": "GitHub Sponsors",
3233 "url": "https://github.com/sponsors/unifiedjs"
3234 },
3235 {
3236 "type": "OpenCollective",
3237 "url": "https://opencollective.com/unified"
3238 }
3239 ],
3240 "license": "MIT",
3241 "dependencies": {
3242 "devlop": "^1.0.0",
3243 "micromark-util-character": "^2.0.0",
3244 "micromark-util-symbol": "^2.0.0",
3245 "micromark-util-types": "^2.0.0"
3246 }
3247 },
3248 "node_modules/micromark-factory-mdx-expression": {
3249 "version": "2.0.3",
3250 "resolved": "https://registry.npmjs.org/micromark-factory-mdx-expression/-/micromark-factory-mdx-expression-2.0.3.tgz",
3251 "integrity": "sha512-kQnEtA3vzucU2BkrIa8/VaSAsP+EJ3CKOvhMuJgOEGg9KDC6OAY6nSnNDVRiVNRqj7Y4SlSzcStaH/5jge8JdQ==",
3252 "funding": [
3253 {
3254 "type": "GitHub Sponsors",
3255 "url": "https://github.com/sponsors/unifiedjs"
3256 },
3257 {
3258 "type": "OpenCollective",
3259 "url": "https://opencollective.com/unified"
3260 }
3261 ],
3262 "license": "MIT",
3263 "dependencies": {
3264 "@types/estree": "^1.0.0",
3265 "devlop": "^1.0.0",
3266 "micromark-factory-space": "^2.0.0",
3267 "micromark-util-character": "^2.0.0",
3268 "micromark-util-events-to-acorn": "^2.0.0",
3269 "micromark-util-symbol": "^2.0.0",
3270 "micromark-util-types": "^2.0.0",
3271 "unist-util-position-from-estree": "^2.0.0",
3272 "vfile-message": "^4.0.0"
3273 }
3274 },
3275 "node_modules/micromark-factory-space": {
3276 "version": "2.0.1",
3277 "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz",
3278 "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==",
3279 "funding": [
3280 {
3281 "type": "GitHub Sponsors",
3282 "url": "https://github.com/sponsors/unifiedjs"
3283 },
3284 {
3285 "type": "OpenCollective",
3286 "url": "https://opencollective.com/unified"
3287 }
3288 ],
3289 "license": "MIT",
3290 "dependencies": {
3291 "micromark-util-character": "^2.0.0",
3292 "micromark-util-types": "^2.0.0"
3293 }
3294 },
3295 "node_modules/micromark-factory-title": {
3296 "version": "2.0.1",
3297 "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz",
3298 "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==",
3299 "funding": [
3300 {
3301 "type": "GitHub Sponsors",
3302 "url": "https://github.com/sponsors/unifiedjs"
3303 },
3304 {
3305 "type": "OpenCollective",
3306 "url": "https://opencollective.com/unified"
3307 }
3308 ],
3309 "license": "MIT",
3310 "dependencies": {
3311 "micromark-factory-space": "^2.0.0",
3312 "micromark-util-character": "^2.0.0",
3313 "micromark-util-symbol": "^2.0.0",
3314 "micromark-util-types": "^2.0.0"
3315 }
3316 },
3317 "node_modules/micromark-factory-whitespace": {
3318 "version": "2.0.1",
3319 "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz",
3320 "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==",
3321 "funding": [
3322 {
3323 "type": "GitHub Sponsors",
3324 "url": "https://github.com/sponsors/unifiedjs"
3325 },
3326 {
3327 "type": "OpenCollective",
3328 "url": "https://opencollective.com/unified"
3329 }
3330 ],
3331 "license": "MIT",
3332 "dependencies": {
3333 "micromark-factory-space": "^2.0.0",
3334 "micromark-util-character": "^2.0.0",
3335 "micromark-util-symbol": "^2.0.0",
3336 "micromark-util-types": "^2.0.0"
3337 }
3338 },
3339 "node_modules/micromark-util-character": {
3340 "version": "2.1.1",
3341 "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz",
3342 "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==",
3343 "funding": [
3344 {
3345 "type": "GitHub Sponsors",
3346 "url": "https://github.com/sponsors/unifiedjs"
3347 },
3348 {
3349 "type": "OpenCollective",
3350 "url": "https://opencollective.com/unified"
3351 }
3352 ],
3353 "license": "MIT",
3354 "dependencies": {
3355 "micromark-util-symbol": "^2.0.0",
3356 "micromark-util-types": "^2.0.0"
3357 }
3358 },
3359 "node_modules/micromark-util-chunked": {
3360 "version": "2.0.1",
3361 "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz",
3362 "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==",
3363 "funding": [
3364 {
3365 "type": "GitHub Sponsors",
3366 "url": "https://github.com/sponsors/unifiedjs"
3367 },
3368 {
3369 "type": "OpenCollective",
3370 "url": "https://opencollective.com/unified"
3371 }
3372 ],
3373 "license": "MIT",
3374 "dependencies": {
3375 "micromark-util-symbol": "^2.0.0"
3376 }
3377 },
3378 "node_modules/micromark-util-classify-character": {
3379 "version": "2.0.1",
3380 "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz",
3381 "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==",
3382 "funding": [
3383 {
3384 "type": "GitHub Sponsors",
3385 "url": "https://github.com/sponsors/unifiedjs"
3386 },
3387 {
3388 "type": "OpenCollective",
3389 "url": "https://opencollective.com/unified"
3390 }
3391 ],
3392 "license": "MIT",
3393 "dependencies": {
3394 "micromark-util-character": "^2.0.0",
3395 "micromark-util-symbol": "^2.0.0",
3396 "micromark-util-types": "^2.0.0"
3397 }
3398 },
3399 "node_modules/micromark-util-combine-extensions": {
3400 "version": "2.0.1",
3401 "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz",
3402 "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==",
3403 "funding": [
3404 {
3405 "type": "GitHub Sponsors",
3406 "url": "https://github.com/sponsors/unifiedjs"
3407 },
3408 {
3409 "type": "OpenCollective",
3410 "url": "https://opencollective.com/unified"
3411 }
3412 ],
3413 "license": "MIT",
3414 "dependencies": {
3415 "micromark-util-chunked": "^2.0.0",
3416 "micromark-util-types": "^2.0.0"
3417 }
3418 },
3419 "node_modules/micromark-util-decode-numeric-character-reference": {
3420 "version": "2.0.2",
3421 "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz",
3422 "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==",
3423 "funding": [
3424 {
3425 "type": "GitHub Sponsors",
3426 "url": "https://github.com/sponsors/unifiedjs"
3427 },
3428 {
3429 "type": "OpenCollective",
3430 "url": "https://opencollective.com/unified"
3431 }
3432 ],
3433 "license": "MIT",
3434 "dependencies": {
3435 "micromark-util-symbol": "^2.0.0"
3436 }
3437 },
3438 "node_modules/micromark-util-decode-string": {
3439 "version": "2.0.1",
3440 "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz",
3441 "integrity": "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==",
3442 "funding": [
3443 {
3444 "type": "GitHub Sponsors",
3445 "url": "https://github.com/sponsors/unifiedjs"
3446 },
3447 {
3448 "type": "OpenCollective",
3449 "url": "https://opencollective.com/unified"
3450 }
3451 ],
3452 "license": "MIT",
3453 "dependencies": {
3454 "decode-named-character-reference": "^1.0.0",
3455 "micromark-util-character": "^2.0.0",
3456 "micromark-util-decode-numeric-character-reference": "^2.0.0",
3457 "micromark-util-symbol": "^2.0.0"
3458 }
3459 },
3460 "node_modules/micromark-util-encode": {
3461 "version": "2.0.1",
3462 "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz",
3463 "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==",
3464 "funding": [
3465 {
3466 "type": "GitHub Sponsors",
3467 "url": "https://github.com/sponsors/unifiedjs"
3468 },
3469 {
3470 "type": "OpenCollective",
3471 "url": "https://opencollective.com/unified"
3472 }
3473 ],
3474 "license": "MIT"
3475 },
3476 "node_modules/micromark-util-events-to-acorn": {
3477 "version": "2.0.3",
3478 "resolved": "https://registry.npmjs.org/micromark-util-events-to-acorn/-/micromark-util-events-to-acorn-2.0.3.tgz",
3479 "integrity": "sha512-jmsiEIiZ1n7X1Rr5k8wVExBQCg5jy4UXVADItHmNk1zkwEVhBuIUKRu3fqv+hs4nxLISi2DQGlqIOGiFxgbfHg==",
3480 "funding": [
3481 {
3482 "type": "GitHub Sponsors",
3483 "url": "https://github.com/sponsors/unifiedjs"
3484 },
3485 {
3486 "type": "OpenCollective",
3487 "url": "https://opencollective.com/unified"
3488 }
3489 ],
3490 "license": "MIT",
3491 "dependencies": {
3492 "@types/estree": "^1.0.0",
3493 "@types/unist": "^3.0.0",
3494 "devlop": "^1.0.0",
3495 "estree-util-visit": "^2.0.0",
3496 "micromark-util-symbol": "^2.0.0",
3497 "micromark-util-types": "^2.0.0",
3498 "vfile-message": "^4.0.0"
3499 }
3500 },
3501 "node_modules/micromark-util-html-tag-name": {
3502 "version": "2.0.1",
3503 "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz",
3504 "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==",
3505 "funding": [
3506 {
3507 "type": "GitHub Sponsors",
3508 "url": "https://github.com/sponsors/unifiedjs"
3509 },
3510 {
3511 "type": "OpenCollective",
3512 "url": "https://opencollective.com/unified"
3513 }
3514 ],
3515 "license": "MIT"
3516 },
3517 "node_modules/micromark-util-normalize-identifier": {
3518 "version": "2.0.1",
3519 "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz",
3520 "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==",
3521 "funding": [
3522 {
3523 "type": "GitHub Sponsors",
3524 "url": "https://github.com/sponsors/unifiedjs"
3525 },
3526 {
3527 "type": "OpenCollective",
3528 "url": "https://opencollective.com/unified"
3529 }
3530 ],
3531 "license": "MIT",
3532 "dependencies": {
3533 "micromark-util-symbol": "^2.0.0"
3534 }
3535 },
3536 "node_modules/micromark-util-resolve-all": {
3537 "version": "2.0.1",
3538 "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz",
3539 "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==",
3540 "funding": [
3541 {
3542 "type": "GitHub Sponsors",
3543 "url": "https://github.com/sponsors/unifiedjs"
3544 },
3545 {
3546 "type": "OpenCollective",
3547 "url": "https://opencollective.com/unified"
3548 }
3549 ],
3550 "license": "MIT",
1868 "integrity": "sha512-14/H1aX7hzBBmmh7sGPd+AOMkkIrHM3Z1PAyGgZigA1H1p5O5ANnMyWzvpAETtG68/dC4pC0ncy3+PPGzXZHPg==",
1869 "license": "Apache-2.0",
35511870 "dependencies": {
3552 "micromark-util-types": "^2.0.0"
1871 "lie": "3.1.1"
35531872 }
35541873 },
3555 "node_modules/micromark-util-sanitize-uri": {
3556 "version": "2.0.1",
3557 "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz",
3558 "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==",
3559 "funding": [
3560 {
3561 "type": "GitHub Sponsors",
3562 "url": "https://github.com/sponsors/unifiedjs"
3563 },
3564 {
3565 "type": "OpenCollective",
3566 "url": "https://opencollective.com/unified"
3567 }
3568 ],
1874 "node_modules/magic-string": {
1875 "version": "0.30.21",
1876 "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
1877 "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
35691878 "license": "MIT",
35701879 "dependencies": {
3571 "micromark-util-character": "^2.0.0",
3572 "micromark-util-encode": "^2.0.0",
3573 "micromark-util-symbol": "^2.0.0"
1880 "@jridgewell/sourcemap-codec": "^1.5.5"
35741881 }
35751882 },
3576 "node_modules/micromark-util-subtokenize": {
3577 "version": "2.1.0",
3578 "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz",
3579 "integrity": "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==",
3580 "funding": [
3581 {
3582 "type": "GitHub Sponsors",
3583 "url": "https://github.com/sponsors/unifiedjs"
3584 },
3585 {
3586 "type": "OpenCollective",
3587 "url": "https://opencollective.com/unified"
3588 }
3589 ],
1883 "node_modules/marko": {
1884 "version": "5.38.24",
1885 "resolved": "https://registry.npmjs.org/marko/-/marko-5.38.24.tgz",
1886 "integrity": "sha512-/F/ZiziJK8T+XMsbfF0/GkXBvqEAnVkZDWHw016Ey6qiFJcSpR/zE5AJ8cXLAqk9WTM4/gRo51wlm2XocpBajg==",
35901887 "license": "MIT",
35911888 "dependencies": {
3592 "devlop": "^1.0.0",
3593 "micromark-util-chunked": "^2.0.0",
3594 "micromark-util-symbol": "^2.0.0",
3595 "micromark-util-types": "^2.0.0"
1889 "@marko/compiler": "^5.39.53",
1890 "@marko/runtime-tags": "^6.0.145",
1891 "app-module-path": "^2.2.0",
1892 "argly": "^1.2.0",
1893 "browser-refresh-client": "1.1.4",
1894 "complain": "^1.6.1",
1895 "csstype": "^3.2.3",
1896 "events-light": "^1.0.5",
1897 "listener-tracker": "^2.0.0",
1898 "magic-string": "^0.30.21",
1899 "minimatch": "^9.0.5",
1900 "raptor-util": "^3.2.0",
1901 "resolve-from": "^5.0.0",
1902 "self-closing-tags": "^1.0.1",
1903 "warp10": "^2.1.0"
1904 },
1905 "bin": {
1906 "markoc": "bin/markoc"
1907 },
1908 "engines": {
1909 "node": "18 || 20 || >=22"
35961910 }
35971911 },
3598 "node_modules/micromark-util-symbol": {
3599 "version": "2.0.1",
3600 "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz",
3601 "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==",
3602 "funding": [
3603 {
3604 "type": "GitHub Sponsors",
3605 "url": "https://github.com/sponsors/unifiedjs"
3606 },
3607 {
3608 "type": "OpenCollective",
3609 "url": "https://opencollective.com/unified"
3610 }
3611 ],
3612 "license": "MIT"
3613 },
3614 "node_modules/micromark-util-types": {
3615 "version": "2.0.2",
3616 "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz",
3617 "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==",
3618 "funding": [
3619 {
3620 "type": "GitHub Sponsors",
3621 "url": "https://github.com/sponsors/unifiedjs"
3622 },
3623 {
3624 "type": "OpenCollective",
3625 "url": "https://opencollective.com/unified"
3626 }
3627 ],
3628 "license": "MIT"
1912 "node_modules/minimatch": {
1913 "version": "9.0.5",
1914 "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
1915 "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
1916 "license": "ISC",
1917 "dependencies": {
1918 "brace-expansion": "^2.0.1"
1919 },
1920 "engines": {
1921 "node": ">=16 || 14 >=14.17"
1922 },
1923 "funding": {
1924 "url": "https://github.com/sponsors/isaacs"
1925 }
36291926 },
36301927 "node_modules/mitt": {
36311928 "version": "3.0.1",
......@@ -3695,9 +1992,9 @@
36951992 }
36961993 },
36971994 "node_modules/node-releases": {
3698 "version": "2.0.21",
3699 "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.21.tgz",
3700 "integrity": "sha512-5b0pgg78U3hwXkCM8Z9b2FJdPZlr9Psr9V2gQPESdGHqbntyFJKFW4r5TeWGFzafGY3hzs1JC62VEQMbl1JFkw==",
1995 "version": "2.0.27",
1996 "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz",
1997 "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==",
37011998 "license": "MIT"
37021999 },
37032000 "node_modules/once": {
......@@ -3753,31 +2050,6 @@
37532050 "node": ">=6"
37542051 }
37552052 },
3756 "node_modules/parse-entities": {
3757 "version": "4.0.2",
3758 "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.2.tgz",
3759 "integrity": "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==",
3760 "license": "MIT",
3761 "dependencies": {
3762 "@types/unist": "^2.0.0",
3763 "character-entities-legacy": "^3.0.0",
3764 "character-reference-invalid": "^2.0.0",
3765 "decode-named-character-reference": "^1.0.0",
3766 "is-alphanumerical": "^2.0.0",
3767 "is-decimal": "^2.0.0",
3768 "is-hexadecimal": "^2.0.0"
3769 },
3770 "funding": {
3771 "type": "github",
3772 "url": "https://github.com/sponsors/wooorm"
3773 }
3774 },
3775 "node_modules/parse-entities/node_modules/@types/unist": {
3776 "version": "2.0.11",
3777 "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz",
3778 "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==",
3779 "license": "MIT"
3780 },
37812053 "node_modules/parse-json": {
37822054 "version": "5.2.0",
37832055 "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz",
......@@ -3823,16 +2095,6 @@
38232095 "node": ">=0.4.0"
38242096 }
38252097 },
3826 "node_modules/property-information": {
3827 "version": "7.1.0",
3828 "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz",
3829 "integrity": "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==",
3830 "license": "MIT",
3831 "funding": {
3832 "type": "github",
3833 "url": "https://github.com/sponsors/wooorm"
3834 }
3835 },
38362098 "node_modules/proxy-agent": {
38372099 "version": "6.5.0",
38382100 "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.5.0.tgz",
......@@ -3934,156 +2196,12 @@
39342196 "integrity": "sha512-uEDMMkBCJvjTqYMBnJNxn+neiS6a0rhybQNA9RaexGor1uvKjwyHA5VcbZMZEuqXhKUWbL+WNS7PhuZVZNB7pw==",
39352197 "license": "Apache-2.0"
39362198 },
3937 "node_modules/recma-build-jsx": {
3938 "version": "1.0.0",
3939 "resolved": "https://registry.npmjs.org/recma-build-jsx/-/recma-build-jsx-1.0.0.tgz",
3940 "integrity": "sha512-8GtdyqaBcDfva+GUKDr3nev3VpKAhup1+RvkMvUxURHpW7QyIvk9F5wz7Vzo06CEMSilw6uArgRqhpiUcWp8ew==",
3941 "license": "MIT",
3942 "dependencies": {
3943 "@types/estree": "^1.0.0",
3944 "estree-util-build-jsx": "^3.0.0",
3945 "vfile": "^6.0.0"
3946 },
3947 "funding": {
3948 "type": "opencollective",
3949 "url": "https://opencollective.com/unified"
3950 }
3951 },
3952 "node_modules/recma-jsx": {
3953 "version": "1.0.0",
3954 "resolved": "https://registry.npmjs.org/recma-jsx/-/recma-jsx-1.0.0.tgz",
3955 "integrity": "sha512-5vwkv65qWwYxg+Atz95acp8DMu1JDSqdGkA2Of1j6rCreyFUE/gp15fC8MnGEuG1W68UKjM6x6+YTWIh7hZM/Q==",
3956 "license": "MIT",
3957 "dependencies": {
3958 "acorn-jsx": "^5.0.0",
3959 "estree-util-to-js": "^2.0.0",
3960 "recma-parse": "^1.0.0",
3961 "recma-stringify": "^1.0.0",
3962 "unified": "^11.0.0"
3963 },
3964 "funding": {
3965 "type": "opencollective",
3966 "url": "https://opencollective.com/unified"
3967 }
3968 },
3969 "node_modules/recma-parse": {
3970 "version": "1.0.0",
3971 "resolved": "https://registry.npmjs.org/recma-parse/-/recma-parse-1.0.0.tgz",
3972 "integrity": "sha512-OYLsIGBB5Y5wjnSnQW6t3Xg7q3fQ7FWbw/vcXtORTnyaSFscOtABg+7Pnz6YZ6c27fG1/aN8CjfwoUEUIdwqWQ==",
3973 "license": "MIT",
3974 "dependencies": {
3975 "@types/estree": "^1.0.0",
3976 "esast-util-from-js": "^2.0.0",
3977 "unified": "^11.0.0",
3978 "vfile": "^6.0.0"
3979 },
3980 "funding": {
3981 "type": "opencollective",
3982 "url": "https://opencollective.com/unified"
3983 }
3984 },
3985 "node_modules/recma-stringify": {
3986 "version": "1.0.0",
3987 "resolved": "https://registry.npmjs.org/recma-stringify/-/recma-stringify-1.0.0.tgz",
3988 "integrity": "sha512-cjwII1MdIIVloKvC9ErQ+OgAtwHBmcZ0Bg4ciz78FtbT8In39aAYbaA7zvxQ61xVMSPE8WxhLwLbhif4Js2C+g==",
3989 "license": "MIT",
3990 "dependencies": {
3991 "@types/estree": "^1.0.0",
3992 "estree-util-to-js": "^2.0.0",
3993 "unified": "^11.0.0",
3994 "vfile": "^6.0.0"
3995 },
3996 "funding": {
3997 "type": "opencollective",
3998 "url": "https://opencollective.com/unified"
3999 }
4000 },
4001 "node_modules/rehype-mdx-code-props": {
4002 "version": "3.0.1",
4003 "resolved": "https://registry.npmjs.org/rehype-mdx-code-props/-/rehype-mdx-code-props-3.0.1.tgz",
4004 "integrity": "sha512-BWWKn0N6r7/qd7lbLgv5J8of7imz1l1PyCNoY7BH0AOR9JdJlQIfA9cKqTZVEb2h2GPKh473qrBajF0i01fq3A==",
4005 "license": "MIT",
4006 "dependencies": {
4007 "@types/hast": "^3.0.0",
4008 "hast-util-properties-to-mdx-jsx-attributes": "^1.0.0",
4009 "mdast-util-from-markdown": "^2.0.0",
4010 "mdast-util-mdx": "^3.0.0",
4011 "micromark-extension-mdxjs": "^3.0.0",
4012 "unified": "^11.0.0",
4013 "unist-util-visit-parents": "^6.0.0"
4014 },
4015 "funding": {
4016 "url": "https://github.com/sponsors/remcohaszing"
4017 }
4018 },
4019 "node_modules/rehype-recma": {
4020 "version": "1.0.0",
4021 "resolved": "https://registry.npmjs.org/rehype-recma/-/rehype-recma-1.0.0.tgz",
4022 "integrity": "sha512-lqA4rGUf1JmacCNWWZx0Wv1dHqMwxzsDWYMTowuplHF3xH0N/MmrZ/G3BDZnzAkRmxDadujCjaKM2hqYdCBOGw==",
4023 "license": "MIT",
4024 "dependencies": {
4025 "@types/estree": "^1.0.0",
4026 "@types/hast": "^3.0.0",
4027 "hast-util-to-estree": "^3.0.0"
4028 },
4029 "funding": {
4030 "type": "opencollective",
4031 "url": "https://opencollective.com/unified"
4032 }
4033 },
40342199 "node_modules/relative-import-path": {
40352200 "version": "1.0.0",
40362201 "resolved": "https://registry.npmjs.org/relative-import-path/-/relative-import-path-1.0.0.tgz",
40372202 "integrity": "sha512-ZvbtoduKQmD4PZeJPfH6Ql21qUWhaMxiHkIsH+FUnZqKDwNIXBtGg5zRZyHWomiGYk8n5+KMBPK7Mi4D0XWfNg==",
40382203 "license": "MIT"
40392204 },
4040 "node_modules/remark-mdx": {
4041 "version": "3.1.0",
4042 "resolved": "https://registry.npmjs.org/remark-mdx/-/remark-mdx-3.1.0.tgz",
4043 "integrity": "sha512-Ngl/H3YXyBV9RcRNdlYsZujAmhsxwzxpDzpDEhFBVAGthS4GDgnctpDjgFl/ULx5UEDzqtW1cyBSNKqYYrqLBA==",
4044 "license": "MIT",
4045 "dependencies": {
4046 "mdast-util-mdx": "^3.0.0",
4047 "micromark-extension-mdxjs": "^3.0.0"
4048 },
4049 "funding": {
4050 "type": "opencollective",
4051 "url": "https://opencollective.com/unified"
4052 }
4053 },
4054 "node_modules/remark-parse": {
4055 "version": "11.0.0",
4056 "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz",
4057 "integrity": "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==",
4058 "license": "MIT",
4059 "dependencies": {
4060 "@types/mdast": "^4.0.0",
4061 "mdast-util-from-markdown": "^2.0.0",
4062 "micromark-util-types": "^2.0.0",
4063 "unified": "^11.0.0"
4064 },
4065 "funding": {
4066 "type": "opencollective",
4067 "url": "https://opencollective.com/unified"
4068 }
4069 },
4070 "node_modules/remark-rehype": {
4071 "version": "11.1.2",
4072 "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-11.1.2.tgz",
4073 "integrity": "sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==",
4074 "license": "MIT",
4075 "dependencies": {
4076 "@types/hast": "^3.0.0",
4077 "@types/mdast": "^4.0.0",
4078 "mdast-util-to-hast": "^13.0.0",
4079 "unified": "^11.0.0",
4080 "vfile": "^6.0.0"
4081 },
4082 "funding": {
4083 "type": "opencollective",
4084 "url": "https://opencollective.com/unified"
4085 }
4086 },
40872205 "node_modules/require-directory": {
40882206 "version": "2.1.1",
40892207 "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
......@@ -4117,15 +2235,6 @@
41172235 "node": ">=0.12.0"
41182236 }
41192237 },
4120 "node_modules/semver": {
4121 "version": "6.3.1",
4122 "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
4123 "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
4124 "license": "ISC",
4125 "bin": {
4126 "semver": "bin/semver.js"
4127 }
4128 },
41292238 "node_modules/smart-buffer": {
41302239 "version": "4.2.0",
41312240 "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz",
......@@ -4164,15 +2273,6 @@
41642273 "node": ">= 14"
41652274 }
41662275 },
4167 "node_modules/source-map": {
4168 "version": "0.7.4",
4169 "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz",
4170 "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==",
4171 "license": "BSD-3-Clause",
4172 "engines": {
4173 "node": ">= 8"
4174 }
4175 },
41762276 "node_modules/source-map-support": {
41772277 "version": "0.5.21",
41782278 "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
......@@ -4192,16 +2292,6 @@
41922292 "node": ">=0.10.0"
41932293 }
41942294 },
4195 "node_modules/space-separated-tokens": {
4196 "version": "2.0.2",
4197 "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz",
4198 "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==",
4199 "license": "MIT",
4200 "funding": {
4201 "type": "github",
4202 "url": "https://github.com/sponsors/wooorm"
4203 }
4204 },
42052295 "node_modules/stackframe": {
42062296 "version": "1.3.4",
42072297 "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.3.4.tgz",
......@@ -4233,20 +2323,6 @@
42332323 "node": ">=8"
42342324 }
42352325 },
4236 "node_modules/stringify-entities": {
4237 "version": "4.0.4",
4238 "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz",
4239 "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==",
4240 "license": "MIT",
4241 "dependencies": {
4242 "character-entities-html4": "^2.0.0",
4243 "character-entities-legacy": "^3.0.0"
4244 },
4245 "funding": {
4246 "type": "github",
4247 "url": "https://github.com/sponsors/wooorm"
4248 }
4249 },
42502326 "node_modules/strip-ansi": {
42512327 "version": "6.0.1",
42522328 "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
......@@ -4265,24 +2341,6 @@
42652341 "integrity": "sha512-wnD1HyVqpJUI2+eKZ+eo1UwghftP6yuFheBqqe+bWCotBjC2K1YnteJILRMs3SM4V/0dLEW1SC27MWP5y+mwmw==",
42662342 "license": "MIT"
42672343 },
4268 "node_modules/style-to-js": {
4269 "version": "1.1.17",
4270 "resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.17.tgz",
4271 "integrity": "sha512-xQcBGDxJb6jjFCTzvQtfiPn6YvvP2O8U1MDIPNfJQlWMYfktPy+iGsHE7cssjs7y84d9fQaK4UF3RIJaAHSoYA==",
4272 "license": "MIT",
4273 "dependencies": {
4274 "style-to-object": "1.0.9"
4275 }
4276 },
4277 "node_modules/style-to-object": {
4278 "version": "1.0.9",
4279 "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.9.tgz",
4280 "integrity": "sha512-G4qppLgKu/k6FwRpHiGiKPaPTFcG3g4wNVX/Qsfu+RqQM30E7Tyu/TEgxcL9PNLF5pdRLwQdE3YKKf+KF2Dzlw==",
4281 "license": "MIT",
4282 "dependencies": {
4283 "inline-style-parser": "0.2.4"
4284 }
4285 },
42862344 "node_modules/tar-fs": {
42872345 "version": "3.1.1",
42882346 "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.1.1.tgz",
......@@ -4317,32 +2375,21 @@
43172375 "b4a": "^1.6.4"
43182376 }
43192377 },
4320 "node_modules/trim-lines": {
4321 "version": "3.0.1",
4322 "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz",
4323 "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==",
4324 "license": "MIT",
4325 "funding": {
4326 "type": "github",
4327 "url": "https://github.com/sponsors/wooorm"
4328 }
4329 },
4330 "node_modules/trough": {
4331 "version": "2.2.0",
4332 "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz",
4333 "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==",
4334 "license": "MIT",
4335 "funding": {
4336 "type": "github",
4337 "url": "https://github.com/sponsors/wooorm"
4338 }
4339 },
43402378 "node_modules/tslib": {
43412379 "version": "2.8.1",
43422380 "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
43432381 "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
43442382 "license": "0BSD"
43452383 },
2384 "node_modules/type-detect": {
2385 "version": "1.0.0",
2386 "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-1.0.0.tgz",
2387 "integrity": "sha512-f9Uv6ezcpvCQjJU0Zqbg+65qdcszv3qUQsZfjdRbWiZ7AMenrX1u0lNk9EoWWX6e1F+NULyg27mtdeZ5WhpljA==",
2388 "license": "MIT",
2389 "engines": {
2390 "node": "*"
2391 }
2392 },
43462393 "node_modules/typed-query-selector": {
43472394 "version": "2.12.0",
43482395 "resolved": "https://registry.npmjs.org/typed-query-selector/-/typed-query-selector-2.12.0.tgz",
......@@ -4353,7 +2400,6 @@
43532400 "version": "5.8.3",
43542401 "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz",
43552402 "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
4356 "devOptional": true,
43572403 "license": "Apache-2.0",
43582404 "peer": true,
43592405 "bin": {
......@@ -4394,28 +2440,8 @@
43942440 "version": "7.8.0",
43952441 "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.8.0.tgz",
43962442 "integrity": "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==",
4397 "devOptional": true,
43982443 "license": "MIT"
43992444 },
4400 "node_modules/unified": {
4401 "version": "11.0.5",
4402 "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz",
4403 "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==",
4404 "license": "MIT",
4405 "dependencies": {
4406 "@types/unist": "^3.0.0",
4407 "bail": "^2.0.0",
4408 "devlop": "^1.0.0",
4409 "extend": "^3.0.0",
4410 "is-plain-obj": "^4.0.0",
4411 "trough": "^2.0.0",
4412 "vfile": "^6.0.0"
4413 },
4414 "funding": {
4415 "type": "opencollective",
4416 "url": "https://opencollective.com/unified"
4417 }
4418 },
44192445 "node_modules/unique-names-generator": {
44202446 "version": "4.7.1",
44212447 "resolved": "https://registry.npmjs.org/unique-names-generator/-/unique-names-generator-4.7.1.tgz",
......@@ -4425,91 +2451,10 @@
44252451 "node": ">=8"
44262452 }
44272453 },
4428 "node_modules/unist-util-is": {
4429 "version": "6.0.0",
4430 "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.0.tgz",
4431 "integrity": "sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==",
4432 "license": "MIT",
4433 "dependencies": {
4434 "@types/unist": "^3.0.0"
4435 },
4436 "funding": {
4437 "type": "opencollective",
4438 "url": "https://opencollective.com/unified"
4439 }
4440 },
4441 "node_modules/unist-util-position": {
4442 "version": "5.0.0",
4443 "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz",
4444 "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==",
4445 "license": "MIT",
4446 "dependencies": {
4447 "@types/unist": "^3.0.0"
4448 },
4449 "funding": {
4450 "type": "opencollective",
4451 "url": "https://opencollective.com/unified"
4452 }
4453 },
4454 "node_modules/unist-util-position-from-estree": {
4455 "version": "2.0.0",
4456 "resolved": "https://registry.npmjs.org/unist-util-position-from-estree/-/unist-util-position-from-estree-2.0.0.tgz",
4457 "integrity": "sha512-KaFVRjoqLyF6YXCbVLNad/eS4+OfPQQn2yOd7zF/h5T/CSL2v8NpN6a5TPvtbXthAGw5nG+PuTtq+DdIZr+cRQ==",
4458 "license": "MIT",
4459 "dependencies": {
4460 "@types/unist": "^3.0.0"
4461 },
4462 "funding": {
4463 "type": "opencollective",
4464 "url": "https://opencollective.com/unified"
4465 }
4466 },
4467 "node_modules/unist-util-stringify-position": {
4468 "version": "4.0.0",
4469 "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz",
4470 "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==",
4471 "license": "MIT",
4472 "dependencies": {
4473 "@types/unist": "^3.0.0"
4474 },
4475 "funding": {
4476 "type": "opencollective",
4477 "url": "https://opencollective.com/unified"
4478 }
4479 },
4480 "node_modules/unist-util-visit": {
4481 "version": "5.0.0",
4482 "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.0.0.tgz",
4483 "integrity": "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==",
4484 "license": "MIT",
4485 "dependencies": {
4486 "@types/unist": "^3.0.0",
4487 "unist-util-is": "^6.0.0",
4488 "unist-util-visit-parents": "^6.0.0"
4489 },
4490 "funding": {
4491 "type": "opencollective",
4492 "url": "https://opencollective.com/unified"
4493 }
4494 },
4495 "node_modules/unist-util-visit-parents": {
4496 "version": "6.0.1",
4497 "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.1.tgz",
4498 "integrity": "sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==",
4499 "license": "MIT",
4500 "dependencies": {
4501 "@types/unist": "^3.0.0",
4502 "unist-util-is": "^6.0.0"
4503 },
4504 "funding": {
4505 "type": "opencollective",
4506 "url": "https://opencollective.com/unified"
4507 }
4508 },
45092454 "node_modules/update-browserslist-db": {
4510 "version": "1.1.3",
4511 "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz",
4512 "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==",
2455 "version": "1.2.3",
2456 "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz",
2457 "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==",
45132458 "funding": [
45142459 {
45152460 "type": "opencollective",
......@@ -4536,34 +2481,6 @@
45362481 "browserslist": ">= 4.21.0"
45372482 }
45382483 },
4539 "node_modules/vfile": {
4540 "version": "6.0.3",
4541 "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz",
4542 "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==",
4543 "license": "MIT",
4544 "dependencies": {
4545 "@types/unist": "^3.0.0",
4546 "vfile-message": "^4.0.0"
4547 },
4548 "funding": {
4549 "type": "opencollective",
4550 "url": "https://opencollective.com/unified"
4551 }
4552 },
4553 "node_modules/vfile-message": {
4554 "version": "4.0.2",
4555 "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.2.tgz",
4556 "integrity": "sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==",
4557 "license": "MIT",
4558 "dependencies": {
4559 "@types/unist": "^3.0.0",
4560 "unist-util-stringify-position": "^4.0.0"
4561 },
4562 "funding": {
4563 "type": "opencollective",
4564 "url": "https://opencollective.com/unified"
4565 }
4566 },
45672484 "node_modules/vscode-oniguruma": {
45682485 "version": "2.0.1",
45692486 "resolved": "https://registry.npmjs.org/vscode-oniguruma/-/vscode-oniguruma-2.0.1.tgz",
......@@ -4582,6 +2499,12 @@
45822499 "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==",
45832500 "license": "MIT"
45842501 },
2502 "node_modules/warp10": {
2503 "version": "2.1.0",
2504 "resolved": "https://registry.npmjs.org/warp10/-/warp10-2.1.0.tgz",
2505 "integrity": "sha512-krhkqzJdUxAZv2Cx0Gz6dN1r7TTrG9RDewkDHBbJQIqbNTCdB5ZUHVh7VkA4DgrKW4ZXPPUQKCwmI/3btDse9A==",
2506 "license": "MIT"
2507 },
45852508 "node_modules/webdriver-bidi-protocol": {
45862509 "version": "0.3.6",
45872510 "resolved": "https://registry.npmjs.org/webdriver-bidi-protocol/-/webdriver-bidi-protocol-0.3.6.tgz",
......@@ -4641,12 +2564,6 @@
46412564 "node": ">=10"
46422565 }
46432566 },
4644 "node_modules/yallist": {
4645 "version": "3.1.1",
4646 "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
4647 "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
4648 "license": "ISC"
4649 },
46502567 "node_modules/yargs": {
46512568 "version": "17.7.2",
46522569 "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
......@@ -4692,16 +2609,6 @@
46922609 "funding": {
46932610 "url": "https://github.com/sponsors/colinhacks"
46942611 }
4695 },
4696 "node_modules/zwitch": {
4697 "version": "2.0.4",
4698 "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz",
4699 "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==",
4700 "license": "MIT",
4701 "funding": {
4702 "type": "github",
4703 "url": "https://github.com/sponsors/wooorm"
4704 }
47052612 }
47062613 }
47072614}
package.json+10-19
......@@ -1,47 +1,38 @@
11{
22 "type": "module",
33 "dependencies": {
4 "@clo/markodown": "npm:@paperclover/markodown@rc",
45 "@hono/node-server": "1.14.3",
5 "@mdx-js/mdx": "3.1.0",
6 "@marko/compiler": "^5.39.55",
7 "@types/node": "24.0.10",
8 "browserslist": "4.28.1",
69 "codemirror": "6.0.1",
7 "dashjs": "^5.0.3",
8 "devalue": "^5.3.2",
10 "dashjs": "5.0.3",
11 "devalue": "5.6.2",
912 "esbuild": "0.25.5",
1013 "hls.js": "1.6.5",
11 "hono": "^4.10.3",
14 "hono": "4.11.9",
1215 "lib": "file:lib",
13 "marko": "6.0.82",
16 "marko": "^5.38.24",
1417 "msgpackr": "1.11.5",
1518 "puppeteer": "24.23.0",
16 "rehype-mdx-code-props": "^3.0.1",
1719 "source-map-support": "0.5.21",
20 "typescript": "5.8.3",
1821 "unique-names-generator": "4.7.1",
1922 "vscode-oniguruma": "2.0.1",
2023 "vscode-textmate": "9.2.0"
2124 },
22 "devDependencies": {
23 "@types/node": "24.0.10",
24 "baseline-browser-mapping": "^2.9.14",
25 "typescript": "5.8.3"
26 },
2725 "imports": {
2826 "#sitegen": "./framework/lib/sitegen.ts",
2927 "#sitegen/*": "./framework/lib/*.ts",
3028 "#jsx/jsx-runtime": "./lib/render/jsx-runtime.ts",
3129 "#jsx/jsx-dev-runtime": "./lib/render/jsx-runtime.ts",
32 "#marko/runtime": "./framework/engine/marko-runtime.ts",
33 "#marko/html": {
34 "types": "marko/html",
35 "production": "marko/production",
36 "node": "marko/debug/html"
37 },
3830 "#hono": "hono",
3931 "#hono/conninfo": {
4032 "bun": "hono/bun",
4133 "deno": "hono/deno",
4234 "node": "@hono/node-server/conninfo",
4335 "worker": "hono/cloudflare-workers"
44 },
45 "#mdx": "./framework/mdx-hack.tsx"
36 }
4637 }
4738}
src/blog/drafts/webdev/marko-intro.markodown+2-2
......@@ -322,7 +322,7 @@ export const transitionDate = 1735639200000;
322322 >${formatQuestionTimestamp(date)}</time>
323323 </a>
324324
325 <CloverMarkdown ...{ text } />
325 <QuestionMarkdown=text/>
326326</>
327327
328328// this singleton script will make all the '<time>' tags clickable.
......@@ -330,7 +330,7 @@ client import "./clickable-links.client.ts";
330330
331331import type { Question } from "@/q+a/models/Question.ts";
332332import { formatQuestionTimestamp, formatQuestionISOTimestamp } from "@/q+a/format.ts";
333import { CloverMarkdown } from "@/q+a/clover-markdown.tsx";
333import { QuestionMarkdown } from "@/q+a/markdown.tsx";
334334```
335335
336336The integration is great, `client import` is quite a magical concept, and I've
src/blog/pages/community-translations.mdo created+18
......@@ -0,0 +1,18 @@
1---
2meta:
3 title: clover's community translations
4---
5
6<main>
7<nav>[back to the home page](/)</nav>
8
9# blog community translations
10
11clover blog posts are translated into other languages by my friends or other
12members of the (world's) community. if you natively speak a language that i
13don't have a blog page translated into, and would like to help spread my words
14to more readers, please get in touch.
15
16[`me@paperclover.net`](mailto:me@paperclover.net)
17
18</main>
src/blog/pages/community-translations.mdx deleted-14
......@@ -1,14 +0,0 @@
1export const meta = { title: "about clover's community translations" };
2export const layout = { default: ({ content }) => <main>{content}</main> };
3
4<a href="/">back to the home page</a>
5
6# blog community translations
7
8clover blog posts are translated into other languages by my friends or other
9members of the (world's) community. if you natively speak a language that i
10don't have a blog page translated into, and would like to help spread my words
11to more readers, please get in touch.
12
13<code><a href="mailto:me@paperclover.net">me@paperclover.net</a></code>
14
src/blog/pages/webdev/one-year-next-app-router/en.mdo created+742
......@@ -0,0 +1,742 @@
1---
2layout: "@/blog/tags/blog-layout.marko"
3meta:
4 title: One Year with Next.js App Router — Why We're Moving On
5 description: A critique of React Server Components and Next.js 15.
6 keywords: ["webdev", "technical analysis", "opinion"]
7 authors: ["clover caruso"]
8 embed:
9 thumbnail: /open-graph/next-js.png
10 canonical: /blog/webdev/one-year-next-app-router
11date: "Oct 21st, 2025"
12---
13
14As I've been using [Next.js] professionally on my employer's web app, I find the
15core design of their App Router and [React Server Components] (RSC) to be
16extremely frustrating. And it's not small bugs or that the API is confusing,
17but large disagreements about the fundamental design decisions that Vercel and
18the React team made when building it.
19
20The more webdev events I go to, the more I see people who dislike Next.js, but
21still get stuck using it. By the end of this article, I will share how me and
22my colleagues escaped this hell, seamlessly migrating our entire frontend to
23[TanStack Start].
24
25[Next.js]: https://nextjs.org
26[React Server Components]: https://react.dev/reference/rsc/server-components
27
28<table-of-contents />
29
30<h2#technical-review>A Technical Review: What are Server Components?</>
31
32The pitch of RSC is that components are put into two categories,
33<b.server>"server"</b> components and <b.client>"client"</b>
34components. Server components don't have `useState`, `useEffect`, but can be
35`async function`s and refer to backend tools like directly calling into a
36database. Client components are the existing
37model, where there is code on the backend to generate HTML text and frontend
38code to manage the DOM using `window.document.*`.
39
40> The first disaster: naming!! React is now using the wors
41> <b.server>"server"</b> and <b.client>"client"</b> to refer to
42> a very specific things, ignoring their existing definitions. This would be
43> fine, except <b.client>Client</b> components can run on the backend
44> too! In this article, I'll be using the terms <b>"backend"</b> and
45> <b>"frontend"</b> to describe the two execution environments that web apps
46> exist in: a Node.js process and a Web browser, respectively.
47
48This <b.server>Server</b>/<b.client>Client</b> component model
49is interesting. Since built-ins like `<Suspense />` get serialized across the
50network, data fetching can be very trivially modeled with async
51<b.server>server components</b>, and the fallback UI works as if it were
52client-side.
53
54```tsx filename="src/app/[username]/page.tsx" tint="server"
55// For this article, server components will be highlighted in red
56export default async function Page({ params }) {
57 // Page params are given as a resolved promise
58 const { username } = await params;
59
60 // The components `UserInfo` and `UserPostList` will be run at the same
61 // time. Once `UserInfo` is ready, the visitor will see the page with a
62 // `PostListSkeleton` if the post list is not yet ready.
63 return <main>
64 <UserInfo username={username} />
65
66 <Suspense fallback={<PostListSkeleton />}>
67 <UserPostList username={username} />
68 </Suspense>
69 </main>
70}
71
72// Waterfalls are avoided by having multiple components, which
73// are all evaluated at the same time.
74
75async function UserInfo({ username }) {
76 const user = await fetchUserInfo(username);
77 return <>
78 <h1>{user.displayName}</h1>
79 {user.bio ? <Markdown content={user.bio} /> : ""}
80 </>
81}
82
83async function UserPostList({ username }) {
84 const posts = await fetchUserPostList(username);
85 return /* post list ui omitted for brevity */;
86}
87```
88
89If we ignore the 40kB gzipped bundle size of React itself, the above example
90has zero JavaScript for the UI and data fetching &mdash; it just streams the
91markup! For example, the imagined markdown parser within the `<Markdown />`
92component stays on the backend. When an interactive frontend is needed, <b.client>Client
93components</b> can be created by putting them in a file starting with `"use
94client"`.
95
96```tsx filename="src/components/CopyButton.tsx" tint="client"
97"use client"; // This comment marks the file for client-side bundling.
98
99export function CopyButton({ url }) {
100 return <>
101 <span>{url}</span>
102 <button onClick={() => {
103 const full = new URL(url, location.href);
104 navigator.clipboard.writeText(full.href);
105 // omitting error handling, success ui, styles
106 }}>copy</button>
107 </>
108}
109```
110```tsx filename="src/app/q+a/Card.tsx" tint="server"
111export function Card() {
112 return <article>
113 <header>
114 {/* Make the browser import the copy button */}
115 <CopyButton url="/q+a/2506010139" />
116 </header>
117 <p>
118 {/* Process markdown on the backend */}
119 <Markdown content=".........." />
120 </p>
121 </article>
122}
123```
124
125<h2 id="real-world-pitfalls">Real-world Pitfalls of the App Router</>
126
127After quitting [Bun] as a runtime engineer (I implemented [Server Components
128bundling] and [a RSC template][bun-rsc] there), I joined a small company working on the
129front lines: a Next.js app with a Hono backend. The following notes are
130simplifications from the real world problems I've encountered when trying to
131maintain and develop new features. As a result of all of these, everyone's time
132is wasted either working around design flaws, or explaining to each other why
133what should be a non-issue is an immovable object.
134
135[Bun]: https://bun.com
136[Server Components bundling]: https://github.com/oven-sh/bun/blob/67f0c3e016aa479738469adac2b79a1862b88122/src/bake/bake.d.ts
137[bun-rsc]: https://github.com/oven-sh/bun/tree/e7790894d92b730758ecadf971cb935063508dfb/src/bake/bun-framework-react
138
139<h3 id='optimistic-updates'>Optimistic Updates are Impossible</>
140
141The Next.js documentation for performing mutations [does not mention optimistic
142updates][nextjs-updating-data]; it appears this case was not thought about.
143Components rendered by the <b.server>React Server</b>, by design, can
144not be modified after mounting. Elements that could change need to be inside a
145client component, but data fetching cannot happen on the client components,
146even during SSR on the backend. This results in awkwardly small server
147components that only do data fetching and then have a client component that
148contains a mostly-static version of the page.
149
150```tsx filename="src/app/user/[username]/page.tsx" tint="server"
151
152export default async function Page() {
153 const user = await fetchUserInfo(username);
154 return <ProfileLayout>
155 <UserProfile user={user} />
156 </ProfileLayout>;
157}
158```
159```tsx filename="src/app/user/[username]/UserProfile.tsx" tint="client"
160
161"use client"; // Must separate the client code into a second file!
162
163export function UserProfile({ user: initialUser }) {
164 // There are many great state management libraries out there;
165 // for simplicity, this example will use one state cell.
166 const [user, optimisticUpdateUser] = useState(initialUser);
167
168 async function onEdit(newUser) {
169 optimisticUpdateUser(newUser);
170 const resp = await fetch("...", {
171 method: 'POST',
172 body: JSON.stringify(newUser),
173 ... // (headers, credentials, tracing, and more)
174 })
175 if (!resp.ok) /* always remember to test for errors! */
176 }
177
178 return <main>{/* user interface with editable fields... */}</main>:
179}
180```
181
182As more of the page needs interactivity, it gets messier trying to keep the
183static parts truly server-side. On the work app, nearly every piece of UI
184displays some dynamic data. A [`WebSocket`][ws] synchronizes data live as it
185updates (for example, a user card's online state along with their basic
186profile). Since these component setups are harder to understand and maintain
187for engineers, almost all of our pages are entirely `"use client"` with a
188`page.tsx` that defines the data fetching.
189
190A more concrete example of what this looks like in practice with the
191data-fetching library we use at work, [TanStack Query].
192
193[TanStack Query]: https://github.com/tanstack/query#readme
194
195```ts filename="src/queries/users.ts"
196// At work, there is a helper function `defineQuery` for type safety.
197// Fetchers are trivial and can run on the backend or the frontend.
198export const queryUserInfo = (username) => ({
199 queryKey: ['user', username],
200 queryFn: async ({ ... }) => /* fetch data */
201});
202```
203```tsx filename="src/app/user/[username]/page.tsx" tint="server"
204export default async function Page({ params }) {
205 const { username } = await params;
206
207 // There's no global state in the React Server. Since layouts
208 // are executed in parallel, the TanStack `QueryClient` has to
209 // be reconstructed multiple times per route.
210 const queryClient = new QueryClient();
211 await queryClient.ensureQueryData(queryUserInfo(username));
212
213 // HydrationBoundary is a client component that passes JSON
214 // data from the React server to the client component.
215 return <HydrationBoundary state={dehydrate(queryClient)}>
216 <ClientPage />
217 </HydrationBoundary>;
218}
219```
220```tsx filename="src/app/user/[username]/ClientPage.tsx" tint="client"
221"use client";
222export function ClientPage() {
223 const { username } = useParams();
224 const { data: user } = useSuspenseQuery(queryUserInfo(username));
225
226 // ... some hooks
227
228 return <main>
229 {/* ... an interactive web page */}
230 </main>;
231}
232```
233
234This example has to be three separate files because of the rules of server
235component bundling. (The client component needs `"use client"`, and server
236component files often can't be imported on the client due to server-only
237imports.). In the Pages router, this could've been a single file because of the
238tree-shaking that `getStaticProps` and `getServerSideProps` has.
239
240[ws]: https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API
241[nextjs-updating-data]: https://nextjs.org/docs/app/getting-started/updating-data
242
243<h3 id='redundant-fetches'>Every Navigation is Another Fetch</>
244
245Since the App Router starts every page as a server component, with (ideally)
246small areas of interactivity, a navigation to a new page *has* to fetch the
247Next.js server, regardless of what data the client already has available! Even
248with a a `loading.tsx` file, opening `/`, navigating to `/other`, and then
249going back to `/` will show the loading state while it re-fetches the homepage.
250
251The only case this works is for **perfectly static content**, where instant
252navigations and prefetching work great. But **web apps are not static**, they
253have lots of dynamic content. Being logged in affects the homepage, which is
254infuriating because the client literally has everything needed to display the
255page instantly. It's not like the cookies changed.
256
257> **aside**: In further testing on a blank project, I observe cases where the
258> Next frontend code would pre-fetch routes, but **without any real contents**.
259> On the hello world example, this was a 1.8kB RSC payload that pointed to 2
260> different JS chunks 4 separate times. This is just pure waste of our
261> bandwidth and egress, especially considering all of this information is
262> re-fetched when I actually click the link.
263>
264> ```json whitespace="pre-wrap"
265> 1:"$Sreact.fragment"
266> 2:I[39756,["/_next/static/chunks/ff1a16fafef87110.js","/_next/static/chunks/7dd66bdf8a7e5707.js"],"default"]
267> 3:I[37457,["/_next/static/chunks/ff1a16fafef87110.js","/_next/static/chunks/7dd66bdf8a7e5707.js"],"default"]
268> 4:I[97367,["/_next/static/chunks/ff1a16fafef87110.js","/_next/static/chunks/7dd66bdf8a7e5707.js"],"ViewportBoundary"]
269> 6:I[97367,["/_next/static/chunks/ff1a16fafef87110.js","/_next/static/chunks/7dd66bdf8a7e5707.js"],"MetadataBoundary"]
270> 7:"$Sreact.suspense"
271> 0:{"b":"TdwnOXsfOJapNex_HjHGt","f":[["children","other",["other",{"children":["__PAGE__",{}]}],["other",["$","$1","c",{"children":[null,["$","$L2",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L3",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","forbidden":"$undefined","unauthorized":"$undefined"}]]}],{"children":null},[["$","div","l",{"children":"loading..."}],[],[]],false],["$","$1","h",{"children":[null,["$","$1","KCFxAJdIDH3BlYXAHsbcVv",{"children":[["$","$L4",null,{"children":"$L5"}],["$","meta",null,{"name":"next-size-adjust","content":""}]]}],["$","$L6","KCFxAJdIDH3BlYXAHsbcVm",{"children":["$","div",null,{"hidden":true,"children":["$","$7",null,{"fallback":null,"children":"$L8"}]}]}]]}],false]],"S":false}
272> 5:[["$","meta","0",{"charSet":"utf-8"}],["$","meta","1",{"name":"viewport","content":"width=device-width, initial-scale=1"}]]
273> 9:I[27201,["/_next/static/chunks/ff1a16fafef87110.js","/_next/static/chunks/7dd66bdf8a7e5707.js"],"IconMark"]
274> 8:[["$","title","0",{"children":"Create Next App"}],["$","meta","1",{"name":"description","content":"Generated by create next app"}],["$","link","2",{"rel":"icon","href":"/favicon.ico?favicon.0b3bf435.ico","sizes":"256x256","type":"image/x-icon"}],["$","$L9","3",{}]]
275> ```
276>
277> In review, I found there is actually some content in here: the loading state.
278> Do you see it?
279>
280> ```json
281> ["$","div","l",{"children":"loading..."}]
282> ```
283>
284> It's still a lot of waste, since all of this data gets re-emitted in the
285> actual page RSC.
286
287The solution to this appears to be [`staleTime`][nextjs-stale], but it's marked
288experimental and "not recommended for production". The fact this is a
289non-default afterthought configuration option is embarrassing. Even if we used
290it, you cannot make multiple pages that refer to the same underlying data share
291any of it.
292
293One form of loading state that cannot be represented with the App Router is
294having a page such as a page like a git project's issue page, and clicking on a
295user name to navigate to their profile page. With `loading.tsx`, the entire
296page is a skeleton, but when modeling these queries with TanStack Query it is
297possible to show the username and avatar instantly while the user's bio and
298repositories are fetched in. Server components don't support this form of
299navigation because the data is only available in rendered components, so it
300must be re-fetched.
301
302[nextjs-stale]: https://nextjs.org/docs/app/api-reference/config/next-config-js/staleTimes
303[nextjs-stale-bug]: https://github.com/vercel/next.js/issues/70661
304
305In our Next.js site, we have this line of code on our server component data
306fetchers to make soft navigations faster by skipping the data fetch phase all
307together.
308
309```tsx filename="src/util/tanstack-query-helpers.server.ts" tint="server"
310export function serverSidePrefetchQueries(queries) {
311 if ((await headers()).get("next-url")) {
312 // This is a soft-navigation. SKIP the prefetching to make it faster.
313 // The client might already have this data, and if not, they have the
314 // loading state. Ideally, this server request wouldn't exist -- The
315 // client side has nearly ALL the code since the app is written mostly
316 // as client components. Kind of a design flaw of the App router TBH.
317 return;
318 }
319 // ... data prefetching-logic ...
320}
321```
322
323In addition to this, `loading.tsx` should contain the `useQuery` calls so that
324while the network request for the empty RSC happens, the data is being fetched
325if it actually is needed. In fact, the `loading.tsx` state can just be the
326actual client component, and you'll see the client page.
327
328```tsx filename="src/app/user/[username]/loading.tsx" tint="client"
329"use client";
330export default function PageLoadingSkeleton() {
331 return <ClientPage />;
332}
333```
334
335> At work, we just make our `loading.tsx` files contain the `useQuery`
336> calls and show a skeleton. This is because when Next.js loads the actual Server
337> Component, no matter what, the entire page re-mounts. No VDOM diffing here,
338> meaning all hooks (`useState`) will reset slightly after the request
339> completes. I tried to reproduce a simple case where I was *begging* Next.js to
340> just *update the existing DOM* and preserve state, but it just doesn't.
341> Thankfully, the time the blank RSC call takes is short enough.
342
343<h3 id='layout-restrictions'>Layouts are Artificially Restricted</>
344
345Layouts can perform data fetching, but they can't observe or alter the request
346in any way. This is done so that Next.js can fetch and cache layouts whenever they
347want. In every other framework, layouts are just regular components that have
348no feature difference compared to page components.
349
350Fetching layouts in isolation is a cute idea, but it ends up being silly
351because it also means that any data fetching has to be re-done per layout. You
352can't share a `QueryClient`; instead, you must rely on their [monkey-patched
353`fetch`][nextjs-fetch] to cache the same `GET` request like they promise.
354
355When a coworker asks me about why Next.js rejects some code, I've given up on
356explaining the technical intricacies and just say *"It's a Next.js Skill Issue,
357I'm going to blow it up soon don't worry."* These rules are too hard for normal
358developers to understand.
359
360[nextjs-fetch]: https://nextjs.org/docs/app/api-reference/functions/fetch
361
362<h3 id='rsc-payload'>You Still Download All the Content Twice</>
363
364Unlike the ["Islands Architecture"][islands], Server Components still have to
365be hydrated on the frontend to support `Suspense` and preserving client
366component state. When doing soft navigations, the "RSC Payload" (which is not
367HTML at all) is retrieved by `fetch`. On a fresh reload, HTML is needed for the
368[first paint], but the information about Client components and `Suspense` is
369not contained within that HTML. React's solution is to **send a second copy of
370the entire page's markup**. An example of what a Next.js production server
371would send in a dynamic page render would be something like this:
372
373[first paint]: https://web.dev/articles/fcp
374
375```html filename="GET /user/clover"
376<!DOCTYPE html>
377<html>
378<head>
379 {link and meta tags}
380</head>
381<body>
382 {server side render}
383 <script>
384 // a bootstrap script that sets up global `__next_f` as
385 // an array. once React loads, this `.push` function
386 // gets overwritten to write new chunks directly to the
387 // RSC decoder. this script has some dom helpers too
388 (self.__next_f=self.__next_f||[]).push([0])
389 </script>
390 <script>
391 // the RSC payload for the application shell.
392 self.__next_f.push([1,"1:\"$Sreact.fragment\"\n2:I[658993,[\"/_next/st{...}"])
393 </script>
394
395 <!--
396 the closing </body> is NOT written yet, since there is a
397 suspense boundary not resolved. time passes, and only
398 then is more data is written
399 -->
400 <div class="user-post-list">
401 {server side render of a Suspense boundary}
402 </div>
403 <script>
404 // the RSC payload for the suspense boundary
405 self.__next_f.push([2,"14:[\"$\",\"div\",null,{\"children\":[[\"$\",\"h4\"{...}"])
406 </script>
407
408 <!-- HTML and script tags repeat until the entire page is done -->
409</body>
410</html>
411```
412
413This solution **doubles the size of the initial HTML payload**. Except it's
414worse, because the RSC payload includes JSON quoted in JS string literals,
415which is a is much less efficient format than HTML. While it seems to compress
416fine with brotli and render fast in the browser, this is wasteful. With the
417hydration pattern, at least the data locally could be re-used for interactivity
418and other pages.
419
420Even on pages that have little to no interactivity, you pay the cost. To use
421the Next.js documentation as an example, loading [its
422homepage](https://nextjs.org/docs) loads an page that is around 750kB (250kB of
423HTML and the 500kB of script tags), and content is in there twice.
424
425You can verify that by pressing <kbd>Cmd</kbd> + <kbd>Opt</kbd> + <kbd>u</kbd>
426on Mac or <kbd>Ctrl</kbd> + <kbd>u</kbd> on other platforms. And then
427<kbd>Cmd</kbd> / <kbd>Ctrl</kbd> + <kbd>f</kbd> to locate any string of the
428blog, such as "building full-stack web applications". It's there twice. And
429**there is no way around this**, since it's a fundamental piece of React Server
430Components.
431
432This RSC format certainly has more waste. But I really don't feel like digging into
433why the string `/_next/static/chunks/6192a3719cda7dcc.js` appears 27 separate
434times. What the hell, guys? Is your bandwidth free???
435
436[islands]: https://www.patterns.dev/vanilla/islands-architecture/
437
438<h3 id='turbopack'>Turbopack Sucks</>
439
440This section is not constructive.
441
442- Turbopack isn't fast
443- Turbopack emits code that is hard to debug in a debugger (in development mode)
444- Turbopack throws bad error messages in many cases
445
446I wouldn't have given this point a section in the blog normally, but I want to
447point out three actual examples directly from the project.
448
449The first is a place where during some refactoring to satisfy the Server/Client
450component models, I accidentally made a Client component `async`. This one was
451quite annoying because it didn't say at all where the issue was, but only
452contained the <b.server>server</b> stack trace.
453
454![Next.js error](/file/2025/blog-everyone-hates-nextjs/asyncerror.png)
455
456Another case of a terrible error message:
457
458![Next.js error](/file/2025/blog-everyone-hates-nextjs/nexterror.png)
459
460> After fixing the underlying issue in this second error (which I cannot recall),
461> the Dev server hung and had to be restarted to recover.
462
463The final one is the dozen times I place a debugger breakpoint and the
464variable name `hello` gets turned into
465`__TURBOPACK__imported__module__$5b$project$5d2f$client$2f$src$2f$utils$2f$filename$2e$ts__$5b$app$2d$client$5d$__$28$ecmascript$29$__["hello"]`
466and other bullshit.
467
468Okay. This all sucks. What can we do?
469
470<h2 id='ditching-nextjs'>Seamlessly Ditching Next.js and Vercel at Work</>
471
472There are two types of web projects:
473
474- A web site with mostly static content.
475- A web app with majorly dynamic and interactive components.
476
477And Next.js is the wrong tool for both of these jobs. If you're in the first
478category with a static web site, go for [Astro] or [Fresh]. For everyone who
479needs the full power of React, this section is about how I replaced the vendor
480locked Next with [TanStack Start], incrementally and seamlessly.
481
482[Astro]: https://astro.build/
483[Fresh]: https://fresh.deno.dev/
484[TanStack Start]: https://tanstack.com/start/latest
485
486It started with this Vite config.
487
488```ts filename="vite.config.ts"
489const config = defineConfig(({ mode }) => {
490 const env = loadEnv(mode, process.cwd(), "NEXT_PUBLIC_");
491 return {
492 // Use the Next.js default port 3000
493 server: { port: 3000 },
494 // Use the Next.js default env prefix "NEXT_PUBLIC_"
495 define: Object.fromEntries(Object.entries(env).map(
496 ([k, v]) => [`process.env.${k}`, JSON.stringify(v)])),
497 plugins: [
498 viteTsConfigPaths({ projects: ["./tsconfig.json"] }),
499 tailwindcss(),
500 // For ease of understanding from coworkers, I started porting
501 // the routes in `src/tanstack-routes`. When the migration was
502 // done, it would go back to the default `src/routes`.
503 tanstackStart({
504 router: { routesDirectory: "src/tanstack-routes" },
505 }),
506 viteReact(),
507 ],
508 resolve: {
509 // The key to the incremental migration: redirect `next` elsewhere
510 alias: { next: path.resolve("./src/tanstack-next/") },
511 conditions: ["tanstack"],
512 extensions: [
513 // Allow a file named like `utils/session.tanstack.ts` to
514 // override `utils/session.ts` when imported.
515 ".tanstack.tsx", ".tanstack.ts",
516 // Default import extensions
517 ".mjs", ".js", ".mts", ".ts",
518 ".jsx", ".tsx", ".json",
519 ],
520 },
521 };
522});
523```
524
525Then, I looked for every usage of a Next.js API, and either removed it or made
526a stub for TanStack. For example, `src/tanstack-next/link.tsx` implements
527`next/link`:
528
529```tsx filename="src/tanstack-next/link.tsx"
530import { Link } from "@tanstack/react-router";
531import type { LinkProps } from "next/link";
532
533export default function LinkAdapter({ href, ...rest }: LinkProps) {
534 return <Link {...rest} to={href as unknown as any} />;
535}
536```
537
538> Some of these stubs can be extremely simple. Starting out, my implementation
539> of `useRouter` was just `return {}`, but later I had to add a couple methods
540> to the object. The code here doesn't have to be clean, because it is
541> temporary.
542
543Now, the new site can import nearly every client component by either stubbing
544out the Next.js APIs it needs, or by using the `.tanstack.ts` extension to
545re-implement logic on a file-by-file basis. And shortly after, I got the site's
546homepage to work in TanStack Start, and we merged the branch.
547
548![My "nextgate" PR](/file/2025/blog-everyone-hates-nextjs/pr.png)
549
550> This first PR only supported one of our pages, and was able to do it in a
551> thousand lines of added code, and 40 lines deleted. I had previous patches to
552> remove the few uses of `next/image` and `next/font`.
553
554What was left was porting every other route over. The one thing we lose in
555migrating from Next.js to any other framework is the ability to `await`
556data-fetching functions in the UI. In practice, moving every route into a
557`loader` function made it much more clear what happened when a page was SSR'd.
558For pages that had multiple fetches, these could be combined into a single,
559special API call that would return all of the relevant data for that page.
560
561To re-iterate in bold font: <strong style='color:var(--secondary)'>The
562migration path from Server Components is to just simplify your code &mdash; RSC
563inherently drives you down a chaotic road of things you do not need</strong>.
564Nearly every complex part of our site got easier to understand for all
565engineers. The exception to this was having everyone get used to the new file
566system routing conventions. With enough examples, we all got the hang of it.
567
568With the incremental migration in place, new code did not break the existing
569deployment. TanStack slowly took over the codebase, and we eventually deleted
570all of the Next.js stubs and gained all of the beautiful [type-safety features]
571that the TanStack Router provides. At the end, the site performed faster from
572every angle: Development Mode, Production page load times, Soft navigations,
573and at a lower price than our Next depoyment with Vercel.
574
575[type-safety features]: https://tanstack.com/router/v1/docs/framework/react/guide/type-safety
576
577We're not the only ones seeing the change. While I try and keep myself off of
578social media, someone sent me [the results of Brian Anglin's work at
579Superwall][superwall-twitter], showing incredible CPU reductions on TanStack
580Start. I also recall ChatGPT switching from Next.js to Remix (random online
581chatter: [[1][chatgpt-1]] [[2][chatgpt-2]] [[3][chatgpt-3]]) a year ago.
582
583[superwall-twitter]: https://twitter.com/BriansAngles/status/1978834116079436242#m
584[chatgpt-1]: https://xcancel.com/ryanflorence/status/1831379475654947233
585[chatgpt-2]: https://old.reddit.com/r/reactjs/comments/1f97zgr/chatgpt_migrates_from_nextjs_to_remix
586[chatgpt-3]: https://old.reddit.com/r/nextjs/comments/1f92jdv/chatgptcom_switched_from_nextjs_to_remix
587
588<h3 id='next-metadata'>`next/metadata` is Great</>
589
590In my opinion, this is one of the only good APIs Next.js has, and was the one
591place in our code where moving to TanStack made things harder to do. Instead of
592worsening the code, I just ported their metadata API into a regular function,
593so everyone can use it. Originally, I had a 1:1 port on NPM, but earlier this
594year I simplified it's API into one short and understandable
595file. As of this blog post, I have added a TanStack-compatible
596`meta.toTags` API, which can be installed from [JSR][lib-jsr], [NPM][lib-npm],
597or simply copied into your project.
598
599> **notice**: Due to time constraints with writing this article, the library
600> has not yet been updated. I'll probably get around to it by the ~~end of this
601> week (Oct 24th)~~ some time soon... As a placeholder, I'm able to share the
602> version that is used at work to my website:
603> [`meta.tanstack.ts`](https://paperclover.net/file/2025/blog-everyone-hates-nextjs/meta.tanstack.ts).
604
605```tsx
606// once in your project
607import * as meta from "@clo/lib/meta.ts";
608
609export const defineHead = meta.toTags.bind(null, {
610 // site-wide options
611 base: new URL("https://paperclover.net"),
612 titleTemplate: (title) => [title, "paper clover"]
613 .filter(Boolean).join(' | '),
614 // ...
615});
616
617// for each page...
618export const Route = createFileRoute("/blog")({
619 head: () =>
620 defineHead({
621 title: "clover's blog", // templated with `titleTemplate`
622 description: "a catgirl meows about her technology viewpoints",
623 canonical: "/blog", // joined with `base`
624
625 // When specified, configures Open Graph and Twitter embed,
626 // using the page title and description as the default.
627 // The defaults are good, but it supports more options.
628 embed: {
629 image: "/img/blog.webp",
630 },
631
632 // Every exotic meta tag is done with a JSX fragment. This
633 // doesn't render React, it just loops through the tags.
634 // My goal was to cover the most common 99% of uses.
635 extra: <>
636 <meta name="site-verification" content="waffles" />,
637 </>,
638 }),
639
640 component: Page,
641});
642
643function Page() {
644 ...
645}
646```
647
648My version wasn't concerned with covering the entire space of Next.js's metadata
649object, but instead uses inline JSX to fill that gap.
650
651[lib-jsr]: https://jsr.io/@clo/lib
652[lib-npm]: https://npmjs.com/@paperclover/lib
653
654<h3 id='vercel-og'>`next/og` is Good Too</>
655
656No strong opinions. I just want to remind everyone that the `@vercel/og` package exists.
657
658<h2 id='experience-feels-like-the-usual'>My Experience Feels like the Usual</>
659
660At the Next.js Conf 2024, everyone there was raving about Server Components. I
661forget exactly who I talked to, but the big people were all in on this. I,
662having implemented the bundler end of RSC, saw a couple of the problems in the
663format. With Next 15 "stabilizing" the App Router last year, many companies are
664building their products on it, realizing these pitfalls first-hand.
665
666I came into the Next.js game late, only starting in June with version 15.
667But everyone I've talked to at events sympathize with my notes. All the people
668I talked to on the subject at Bun's 1.3 Party agreed with me. Even some people
669at Vercel told me they don't like how Next.js is to actually use.
670
671I hope as TanStack Start stabilizes, it becomes the Next.js replacement everyone
672wants.
673
674<h2 id='prefer-respectful-tools'>Prefer Tools that Respect You</>
675
676A lot of in the JavaScript ecosystem is a mess. That mess is why web
677development gets made fun of. There were a lot of times I thought that working
678with the web was an unrecoverable mess, but the mess was actually just the
679commonly-used libraries I surrounded myself with. When that is peeled back,
680modern web development technologies are awesome.
681
682I've been making this website from scratch without any framework since late
6832024, by writing systems like my own [TUI progress widget][progress], [static
684file proxy][file-cache], incremental build system, and many more components.
685Working on this code has produced some of my best coding sessions (by
686happiness) in years. The viewers of *[paper clover]* get a better quality
687website; the mini-libraries I create get [extracted for public use][lib],
688everyone wins.
689
690This level of from-scratch is too much for most people, especially at the
691workplace. I say that at the minimum, we should only give our attention and
692money to high quality tools that respect us. And Next.js and the company behind
693it, Vercel, are not that.
694
695If you use Next.js, and feel that the experience doesn't remind you of respect
696too, consider whether you and your colleagues want to continue supporting their
697[serverless empire]. The Vite ecosystem seems pretty decent to build on right
698now, but I still have little experience in using their tools at scale in
699production. The [Vite+ launch from Void0][vite-plus] seems interesting, but
700only time will tell if these venture-funded tools will respect us (end-users
701and developers) long term.
702
703Next.js Conf 2025, as of writing, is [tomorrow][next-conf]. Instead of
704purchasing a $800 ticket, I decided to put that money [toward the TanStack
705team][tanstack-donate] for [respecting and improving the web development
706ecosystem][tanstack-ethos].
707
708[paper clover]: https://paperclover.net/
709[lib]: https://git.paperclover.net/clo/sitegen/src/branch/master/lib#readme
710[progress]: https://git.paperclover.net/clo/sitegen/src/branch/master/lib/progress.ts
711[file-cache]: https://git.paperclover.net/clo/sitegen/src/branch/master/src/file-viewer/cache.ts
712
713[next-conf]: https://nextjs.org/conf
714[vite-plus]: https://viteplus.dev/
715[tanstack-ethos]: https://tanstack.com/ethos
716[tanstack-donate]: https://github.com/sponsors/tannerlinsley
717[serverless empire]: https://youtu.be/SCIfWhAheVw
718
719## What the Future Holds
720
721Slowly, I've been replacing many pieces of software that disrespect me with
722better alternatives. Some examples of this are GitHub, Visual Studio Code,
723DaVinci Resolve, Discord, Google Drive/Workspace, along many more. I plan to
724write more on this blog about the technical things I do (that progress library,
725the purpose of my own site generator, learnings from my current job), including
726some of my past projects at Bun (details on HMR, the crash reporter, and the
727crazy system for bundling built-in modules). If it interests you, please
728subscribe to the email list:
729
730<a href="mailto:subscribe@paperclover.net?subject=paper%20clover%20mailing%20list&body=I%20would%20like%20to%20be%20subscribed%20to%20the%20following%20mailing%20lists%3A%0A%0A-%20Technical%20Blog%20Posts%20-%20YES%0A-%20Art%20(Original%20Music%2FVideo)%20-%20YES%0A%0A(feel%20free%20to%20write%20whatever%20else%20you%20want)">click here to send an email to <code>subscribe@paperclover.net</code>, requesting that you would like to be added to the mailing list.</a> (i manage this mailing list manually)
731
732[back to top](#top) &mdash; [ask a question about this article](/q+a)
733
734<br />
735<br />
736<br />
737<br />
738<br />
739<br />
740<footer>
7412025 (c) paper clover
742</footer>
src/blog/pages/webdev/one-year-next-app-router/en.mdx deleted-836
......@@ -1,836 +0,0 @@
1import Heading from "@/blog/tags/heading.tsx";
2import TableOfContents from "@/blog/tags/table-of-contents.tsx";
3import { Layout } from "@/blog/tags/layout.tsx";
4export { theme } from "@/blog/tags/layout.tsx";
5
6export const meta = {
7 title: "One Year with Next.js App Router — Why We're Moving On",
8 description: "A critique of React Server Components and Next.js 15.",
9 keywords: ["webdev", "technical analysis", "opinion"],
10 authors: ["clover caruso"],
11 embed: {
12 thumbnail: "/open-graph/next-js.png"
13 },
14 twitter: {
15 image: "https://paperclover.net/open-graph/next-js.png"
16 },
17 canonical: "/blog/webdev/one-year-next-app-router"
18};
19
20<Layout
21 meta={meta}
22 date={'Oct 21st, 2025'}
23 slug="webdev/one-year-next-app-router"
24 tags={meta.keywords}
25>
26
27As I've been using [Next.js] professionally on my employer's web app, I find the
28core design of their App Router and [React Server Components] (RSC) to be
29extremely frustrating. And it's not small bugs or that the API is confusing,
30but large disagreements about the fundamental design decisions that Vercel and
31the React team made when building it.
32
33The more webdev events I go to, the more I see people who dislike Next.js, but
34still get stuck using it. By the end of this article, I will share how me and
35my colleagues escaped this hell, seamlessly migrating our entire frontend to
36[TanStack Start].
37
38[Next.js]: https://nextjs.org
39[React Server Components]: https://react.dev/reference/rsc/server-components
40
41<TableOfContents>
42
43- [A Technical Review: What are Server Components?][§1]
44- [Real-world Pitfalls of the App Router][§2]
45 - [Optimistic Updates are Impossible][§2.1]
46 - [Every Navigation is Another Fetch][§2.2]
47 - [Layouts are Artificially Restricted][§2.3]
48 - [You Still Download All the Content Twice][§2.4]
49 - [Turbopack Sucks][§2.5]
50- [Seamlessly Ditching Next.js and Vercel at Work][§3]
51 - [`next/metadata` is Great][§3.1]
52 - [`next/og` is Good Too][§3.2]
53- [My Experience Feels Like the Usual][§4]
54- [Prefer Tools that Respect You][§5]
55
56</TableOfContents>
57
58[§1]: #technical-review
59
60<Heading
61 level='h2'
62 slug='technical-review'
63>A Technical Review: What are Server Components?</Heading>
64
65The pitch of RSC is that components are put into two categories,
66<b class='server'>"server"</b> components and <b class='client'>"client"</b>
67components. Server components don't have `useState`, `useEffect`, but can be
68`async function`s and refer to backend tools like directly calling into a
69database. Client components are the existing
70model, where there is code on the backend to generate HTML text and frontend
71code to manage the DOM using `window.document.*`.
72
73> The first disaster: naming!! React is now using the words
74> <b class='server'>"server"</b> and <b class='client'>"client"</b> to refer to
75> a very specific things, ignoring their existing definitions. This would be
76> fine, except <b class='client'>Client</b> components can run on the backend
77> too! In this article, I'll be using the terms <b>"backend"</b> and
78> <b>"frontend"</b> to describe the two execution environments that web apps
79> exist in: a Node.js process and a Web browser, respectively.
80
81This <b class='server'>Server</b>/<b class='client'>Client</b> component model
82is interesting. Since built-ins like `<Suspense />` get serialized across the
83network, data fetching can be very trivially modeled with async <b
84class='server'>server components</b>, and the fallback UI works as if it were
85client-side.
86
87```tsx filename="src/app/[username]/page.tsx" tint="server"
88// For this article, server components will be highlighted in red
89export default async function Page({ params }) {
90 // Page params are given as a resolved promise
91 const { username } = await params;
92
93 // The components `UserInfo` and `UserPostList` will be run at the same
94 // time. Once `UserInfo` is ready, the visitor will see the page with a
95 // `PostListSkeleton` if the post list is not yet ready.
96 return <main>
97 <UserInfo username={username} />
98
99 <Suspense fallback={<PostListSkeleton />}>
100 <UserPostList username={username} />
101 </Suspense>
102 </main>
103}
104
105// Waterfalls are avoided by having multiple components, which
106// are all evaluated at the same time.
107
108async function UserInfo({ username }) {
109 const user = await fetchUserInfo(username);
110 return <>
111 <h1>{user.displayName}</h1>
112 {user.bio ? <Markdown content={user.bio} /> : ""}
113 </>
114}
115
116async function UserPostList({ username }) {
117 const posts = await fetchUserPostList(username);
118 return /* post list ui omitted for brevity */;
119}
120```
121
122If we ignore the 40kB gzipped bundle size of React itself, the above example
123has zero JavaScript for the UI and data fetching &mdash; it just streams the
124markup! For example, the imagined markdown parser within the `<Markdown />`
125component stays on the backend. When an interactive frontend is needed, <b class='client'>Client
126components</b> can be created by putting them in a file starting with `"use
127client"`.
128
129```tsx filename="src/components/CopyButton.tsx" tint="client"
130"use client"; // This comment marks the file for client-side bundling.
131
132export function CopyButton({ url }) {
133 return <>
134 <span>{url}</span>
135 <button onClick={() => {
136 const full = new URL(url, location.href);
137 navigator.clipboard.writeText(full.href);
138 // omitting error handling, success ui, styles
139 }}>copy</button>
140 </>
141}
142```
143```tsx filename="src/app/q+a/Card.tsx" tint="server"
144export function Card() {
145 return <article>
146 <header>
147 {/* Make the browser import the copy button */}
148 <CopyButton url="/q+a/2506010139" />
149 </header>
150 <p>
151 {/* Process markdown on the backend */}
152 <Markdown content=".........." />
153 </p>
154 </article>
155}
156```
157
158[§2]: #real-world-pitfalls
159
160<Heading
161 level='h2'
162 slug='real-world-pitfalls'
163>Real-world Pitfalls of the App Router</Heading>
164
165After quitting [Bun] as a runtime engineer (I implemented [Server Components
166bundling] and [a RSC template][bun-rsc] there), I joined a small company working on the
167front lines: a Next.js app with a Hono backend. The following notes are
168simplifications from the real world problems I've encountered when trying to
169maintain and develop new features. As a result of all of these, everyone's time
170is wasted either working around design flaws, or explaining to each other why
171what should be a non-issue is an immovable object.
172
173[Bun]: https://bun.com
174[Server Components bundling]: https://github.com/oven-sh/bun/blob/67f0c3e016aa479738469adac2b79a1862b88122/src/bake/bake.d.ts
175[bun-rsc]: https://github.com/oven-sh/bun/tree/e7790894d92b730758ecadf971cb935063508dfb/src/bake/bun-framework-react
176
177[§2.1]: #optimistic-updates
178
179<Heading
180 level='h3'
181 slug='optimistic-updates'
182>Optimistic Updates are Impossible</Heading>
183
184The Next.js documentation for performing mutations [does not mention optimistic
185updates][nextjs-updating-data]; it appears this case was not thought about.
186Components rendered by the <b class='server'>React Server</b>, by design, can
187not be modified after mounting. Elements that could change need to be inside a
188client component, but data fetching cannot happen on the client components,
189even during SSR on the backend. This results in awkwardly small server
190components that only do data fetching and then have a client component that
191contains a mostly-static version of the page.
192
193```tsx filename="src/app/user/[username]/page.tsx" tint="server"
194
195export default async function Page() {
196 const user = await fetchUserInfo(username);
197 return <ProfileLayout>
198 <UserProfile user={user} />
199 </ProfileLayout>;
200}
201```
202```tsx filename="src/app/user/[username]/UserProfile.tsx" tint="client"
203
204"use client"; // Must separate the client code into a second file!
205
206export function UserProfile({ user: initialUser }) {
207 // There are many great state management libraries out there;
208 // for simplicity, this example will use one state cell.
209 const [user, optimisticUpdateUser] = useState(initialUser);
210
211 async function onEdit(newUser) {
212 optimisticUpdateUser(newUser);
213 const resp = await fetch("...", {
214 method: 'POST',
215 body: JSON.stringify(newUser),
216 ... // (headers, credentials, tracing, and more)
217 })
218 if (!resp.ok) /* always remember to test for errors! */
219 }
220
221 return <main>{/* user interface with editable fields... */}</main>:
222}
223```
224
225As more of the page needs interactivity, it gets messier trying to keep the
226static parts truly server-side. On the work app, nearly every piece of UI
227displays some dynamic data. A [`WebSocket`][ws] synchronizes data live as it
228updates (for example, a user card's online state along with their basic
229profile). Since these component setups are harder to understand and maintain
230for engineers, almost all of our pages are entirely `"use client"` with a
231`page.tsx` that defines the data fetching.
232
233A more concrete example of what this looks like in practice with the
234data-fetching library we use at work, [TanStack Query].
235
236[TanStack Query]: https://github.com/tanstack/query#readme
237
238```ts filename="src/queries/users.ts"
239// At work, there is a helper function `defineQuery` for type safety.
240// Fetchers are trivial and can run on the backend or the frontend.
241export const queryUserInfo = (username) => ({
242 queryKey: ['user', username],
243 queryFn: async ({ ... }) => /* fetch data */
244});
245```
246```tsx filename="src/app/user/[username]/page.tsx" tint="server"
247export default async function Page({ params }) {
248 const { username } = await params;
249
250 // There's no global state in the React Server. Since layouts
251 // are executed in parallel, the TanStack `QueryClient` has to
252 // be reconstructed multiple times per route.
253 const queryClient = new QueryClient();
254 await queryClient.ensureQueryData(queryUserInfo(username));
255
256 // HydrationBoundary is a client component that passes JSON
257 // data from the React server to the client component.
258 return <HydrationBoundary state={dehydrate(queryClient)}>
259 <ClientPage />
260 </HydrationBoundary>;
261}
262```
263```tsx filename="src/app/user/[username]/ClientPage.tsx" tint="client"
264"use client";
265export function ClientPage() {
266 const { username } = useParams();
267 const { data: user } = useSuspenseQuery(queryUserInfo(username));
268
269 // ... some hooks
270
271 return <main>
272 {/* ... an interactive web page */}
273 </main>;
274}
275```
276
277This example has to be three separate files because of the rules of server
278component bundling. (The client component needs `"use client"`, and server
279component files often can't be imported on the client due to server-only
280imports.). In the Pages router, this could've been a single file because of the
281tree-shaking that `getStaticProps` and `getServerSideProps` has.
282
283[ws]: https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API
284[nextjs-updating-data]: https://nextjs.org/docs/app/getting-started/updating-data
285
286
287[§2.2]: #redundant-fetches
288
289<Heading
290 level='h3'
291 slug='redundant-fetches'
292>Every Navigation is Another Fetch</Heading>
293
294Since the App Router starts every page as a server component, with (ideally)
295small areas of interactivity, a navigation to a new page *has* to fetch the
296Next.js server, regardless of what data the client already has available! Even
297with a a `loading.tsx` file, opening `/`, navigating to `/other`, and then
298going back to `/` will show the loading state while it re-fetches the homepage.
299
300The only case this works is for **perfectly static content**, where instant
301navigations and prefetching work great. But **web apps are not static**, they
302have lots of dynamic content. Being logged in affects the homepage, which is
303infuriating because the client literally has everything needed to display the
304page instantly. It's not like the cookies changed.
305
306> **aside**: In further testing on a blank project, I observe cases where the
307> Next frontend code would pre-fetch routes, but **without any real contents**.
308> On the hello world example, this was a 1.8kB RSC payload that pointed to 2
309> different JS chunks 4 separate times. This is just pure waste of our
310> bandwidth and egress, especially considering all of this information is
311> re-fetched when I actually click the link.
312>
313> ```json whitespace="pre-wrap"
314> 1:"$Sreact.fragment"
315> 2:I[39756,["/_next/static/chunks/ff1a16fafef87110.js","/_next/static/chunks/7dd66bdf8a7e5707.js"],"default"]
316> 3:I[37457,["/_next/static/chunks/ff1a16fafef87110.js","/_next/static/chunks/7dd66bdf8a7e5707.js"],"default"]
317> 4:I[97367,["/_next/static/chunks/ff1a16fafef87110.js","/_next/static/chunks/7dd66bdf8a7e5707.js"],"ViewportBoundary"]
318> 6:I[97367,["/_next/static/chunks/ff1a16fafef87110.js","/_next/static/chunks/7dd66bdf8a7e5707.js"],"MetadataBoundary"]
319> 7:"$Sreact.suspense"
320> 0:{"b":"TdwnOXsfOJapNex_HjHGt","f":[["children","other",["other",{"children":["__PAGE__",{}]}],["other",["$","$1","c",{"children":[null,["$","$L2",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L3",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","forbidden":"$undefined","unauthorized":"$undefined"}]]}],{"children":null},[["$","div","l",{"children":"loading..."}],[],[]],false],["$","$1","h",{"children":[null,["$","$1","KCFxAJdIDH3BlYXAHsbcVv",{"children":[["$","$L4",null,{"children":"$L5"}],["$","meta",null,{"name":"next-size-adjust","content":""}]]}],["$","$L6","KCFxAJdIDH3BlYXAHsbcVm",{"children":["$","div",null,{"hidden":true,"children":["$","$7",null,{"fallback":null,"children":"$L8"}]}]}]]}],false]],"S":false}
321> 5:[["$","meta","0",{"charSet":"utf-8"}],["$","meta","1",{"name":"viewport","content":"width=device-width, initial-scale=1"}]]
322> 9:I[27201,["/_next/static/chunks/ff1a16fafef87110.js","/_next/static/chunks/7dd66bdf8a7e5707.js"],"IconMark"]
323> 8:[["$","title","0",{"children":"Create Next App"}],["$","meta","1",{"name":"description","content":"Generated by create next app"}],["$","link","2",{"rel":"icon","href":"/favicon.ico?favicon.0b3bf435.ico","sizes":"256x256","type":"image/x-icon"}],["$","$L9","3",{}]]
324> ```
325>
326> In review, I found there is actually some content in here: the loading state.
327> Do you see it?
328>
329> ```json
330> ["$","div","l",{"children":"loading..."}]
331> ```
332>
333> It's still a lot of waste, since all of this data gets re-emitted in the
334> actual page RSC.
335
336The solution to this appears to be [`staleTime`][nextjs-stale], but it's marked
337experimental and "not recommended for production". The fact this is a
338non-default afterthought configuration option is embarrassing. Even if we used
339it, you cannot make multiple pages that refer to the same underlying data share
340any of it.
341
342One form of loading state that cannot be represented with the App Router is
343having a page such as a page like a git project's issue page, and clicking on a
344user name to navigate to their profile page. With `loading.tsx`, the entire
345page is a skeleton, but when modeling these queries with TanStack Query it is
346possible to show the username and avatar instantly while the user's bio and
347repositories are fetched in. Server components don't support this form of
348navigation because the data is only available in rendered components, so it
349must be re-fetched.
350
351[nextjs-stale]: https://nextjs.org/docs/app/api-reference/config/next-config-js/staleTimes
352[nextjs-stale-bug]: https://github.com/vercel/next.js/issues/70661
353
354In our Next.js site, we have this line of code on our server component data
355fetchers to make soft navigations faster by skipping the data fetch phase all
356together.
357
358```tsx filename="src/util/tanstack-query-helpers.server.ts" tint="server"
359export function serverSidePrefetchQueries(queries) {
360 if ((await headers()).get("next-url")) {
361 // This is a soft-navigation. SKIP the prefetching to make it faster.
362 // The client might already have this data, and if not, they have the
363 // loading state. Ideally, this server request wouldn't exist -- The
364 // client side has nearly ALL the code since the app is written mostly
365 // as client components. Kind of a design flaw of the App router TBH.
366 return;
367 }
368 // ... data prefetching-logic ...
369}
370```
371
372In addition to this, `loading.tsx` should contain the `useQuery` calls so that
373while the network request for the empty RSC happens, the data is being fetched
374if it actually is needed. In fact, the `loading.tsx` state can just be the
375actual client component, and you'll see the client page.
376
377```tsx filename="src/app/user/[username]/loading.tsx" tint="client"
378"use client";
379export default function PageLoadingSkeleton() {
380 return <ClientPage />;
381}
382```
383
384> At work, we just make our `loading.tsx` files contain the `useQuery`
385> calls and show a skeleton. This is because when Next.js loads the actual Server
386> Component, no matter what, the entire page re-mounts. No VDOM diffing here,
387> meaning all hooks (`useState`) will reset slightly after the request
388> completes. I tried to reproduce a simple case where I was *begging* Next.js to
389> just *update the existing DOM* and preserve state, but it just doesn't.
390> Thankfully, the time the blank RSC call takes is short enough.
391
392[§2.3]: #layout-restrictions
393
394<Heading
395 level='h3'
396 slug='layout-restrictions'
397>Layouts are Artificially Restricted</Heading>
398
399Layouts can perform data fetching, but they can't observe or alter the request
400in any way. This is done so that Next.js can fetch and cache layouts whenever they
401want. In every other framework, layouts are just regular components that have
402no feature difference compared to page components.
403
404Fetching layouts in isolation is a cute idea, but it ends up being silly
405because it also means that any data fetching has to be re-done per layout. You
406can't share a `QueryClient`; instead, you must rely on their [monkey-patched
407`fetch`][nextjs-fetch] to cache the same `GET` request like they promise.
408
409When a coworker asks me about why Next.js rejects some code, I've given up on
410explaining the technical intricacies and just say *"It's a Next.js Skill Issue,
411I'm going to blow it up soon don't worry."* These rules are too hard for normal
412developers to understand.
413
414[nextjs-fetch]: https://nextjs.org/docs/app/api-reference/functions/fetch
415
416[§2.4]: #rsc-payload
417
418<Heading
419 level='h3'
420 slug='rsc-payload'
421>You Still Download All the Content Twice</Heading>
422
423Unlike the ["Islands Architecture"][islands], Server Components still have to
424be hydrated on the frontend to support `Suspense` and preserving client
425component state. When doing soft navigations, the "RSC Payload" (which is not
426HTML at all) is retrieved by `fetch`. On a fresh reload, HTML is needed for the
427[first paint], but the information about Client components and `Suspense` is
428not contained within that HTML. React's solution is to **send a second copy of
429the entire page's markup**. An example of what a Next.js production server
430would send in a dynamic page render would be something like this:
431
432[first paint]: https://web.dev/articles/fcp
433
434```html filename="GET /user/clover"
435<!DOCTYPE html>
436<html>
437<head>
438 {link and meta tags}
439</head>
440<body>
441 {server side render}
442 <script>
443 // a bootstrap script that sets up global `__next_f` as
444 // an array. once React loads, this `.push` function
445 // gets overwritten to write new chunks directly to the
446 // RSC decoder. this script has some dom helpers too
447 (self.__next_f=self.__next_f||[]).push([0])
448 </script>
449 <script>
450 // the RSC payload for the application shell.
451 self.__next_f.push([1,"1:\"$Sreact.fragment\"\n2:I[658993,[\"/_next/st{...}"])
452 </script>
453
454 <!--
455 the closing </body> is NOT written yet, since there is a
456 suspense boundary not resolved. time passes, and only
457 then is more data is written
458 -->
459 <div class="user-post-list">
460 {server side render of a Suspense boundary}
461 </div>
462 <script>
463 // the RSC payload for the suspense boundary
464 self.__next_f.push([2,"14:[\"$\",\"div\",null,{\"children\":[[\"$\",\"h4\"{...}"])
465 </script>
466
467 <!-- HTML and script tags repeat until the entire page is done -->
468</body>
469</html>
470```
471
472This solution **doubles the size of the initial HTML payload**. Except it's
473worse, because the RSC payload includes JSON quoted in JS string literals,
474which is a is much less efficient format than HTML. While it seems to compress
475fine with brotli and render fast in the browser, this is wasteful. With the
476hydration pattern, at least the data locally could be re-used for interactivity
477and other pages.
478
479Even on pages that have little to no interactivity, you pay the cost. To use
480the Next.js documentation as an example, loading [its
481homepage](https://nextjs.org/docs) loads an page that is around 750kB (250kB of
482HTML and the 500kB of script tags), and content is in there twice.
483
484You can verify that by pressing <kbd>Cmd</kbd> + <kbd>Opt</kbd> + <kbd>u</kbd>
485on Mac or <kbd>Ctrl</kbd> + <kbd>u</kbd> on other platforms. And then
486<kbd>Cmd</kbd> / <kbd>Ctrl</kbd> + <kbd>f</kbd> to locate any string of the
487blog, such as "building full-stack web applications". It's there twice. And
488**there is no way around this**, since it's a fundamental piece of React Server
489Components.
490
491This RSC format certainly has more waste. But I really don't feel like digging into
492why the string `/_next/static/chunks/6192a3719cda7dcc.js` appears 27 separate
493times. What the hell, guys? Is your bandwidth free???
494
495[islands]: https://www.patterns.dev/vanilla/islands-architecture/
496
497[§2.5]: #turbopack
498
499<Heading
500 level='h3'
501 slug='turbopack'
502>Turbopack Sucks</Heading>
503
504This section is not constructive.
505
506- Turbopack isn't fast
507- Turbopack emits code that is hard to debug in a debugger (in development mode)
508- Turbopack throws bad error messages in many cases
509
510I wouldn't have given this point a section in the blog normally, but I want to
511point out three actual examples directly from the project.
512
513The first is a place where during some refactoring to satisfy the Server/Client
514component models, I accidentally made a Client component `async`. This one was
515quite annoying because it didn't say at all where the issue was, but only
516contained the <b class='server'>server</b> stack trace.
517
518![Next.js error](/file/2025/blog-everyone-hates-nextjs/asyncerror.png)
519
520Another case of a terrible error message:
521
522![Next.js error](/file/2025/blog-everyone-hates-nextjs/nexterror.png)
523
524> After fixing the underlying issue in this second error (which I cannot recall),
525> the Dev server hung and had to be restarted to recover.
526
527The final one is the dozen times I place a debugger breakpoint and the
528variable name `hello` gets turned into
529`__TURBOPACK__imported__module__$5b$project$5d2f$client$2f$src$2f$utils$2f$filename$2e$ts__$5b$app$2d$client$5d$__$28$ecmascript$29$__["hello"]`
530and other bullshit.
531
532Okay. This all sucks. What can we do?
533
534[§3]: #ditching-nextjs
535
536<Heading
537 level='h2'
538 slug='ditching-nextjs'
539>Seamlessly Ditching Next.js and Vercel at Work</Heading>
540
541There are two types of web projects:
542
543- A web site with mostly static content.
544- A web app with majorly dynamic and interactive components.
545
546And Next.js is the wrong tool for both of these jobs. If you're in the first
547category with a static web site, go for [Astro] or [Fresh]. For everyone who
548needs the full power of React, this section is about how I replaced the vendor
549locked Next with [TanStack Start], incrementally and seamlessly.
550
551[Astro]: https://astro.build/
552[Fresh]: https://fresh.deno.dev/
553[TanStack Start]: https://tanstack.com/start/latest
554
555It started with this Vite config.
556
557```ts filename="vite.config.ts"
558const config = defineConfig(({ mode }) => {
559 const env = loadEnv(mode, process.cwd(), "NEXT_PUBLIC_");
560 return {
561 // Use the Next.js default port 3000
562 server: { port: 3000 },
563 // Use the Next.js default env prefix "NEXT_PUBLIC_"
564 define: Object.fromEntries(Object.entries(env).map(
565 ([k, v]) => [`process.env.${k}`, JSON.stringify(v)])),
566 plugins: [
567 viteTsConfigPaths({ projects: ["./tsconfig.json"] }),
568 tailwindcss(),
569 // For ease of understanding from coworkers, I started porting
570 // the routes in `src/tanstack-routes`. When the migration was
571 // done, it would go back to the default `src/routes`.
572 tanstackStart({
573 router: { routesDirectory: "src/tanstack-routes" },
574 }),
575 viteReact(),
576 ],
577 resolve: {
578 // The key to the incremental migration: redirect `next` elsewhere
579 alias: { next: path.resolve("./src/tanstack-next/") },
580 conditions: ["tanstack"],
581 extensions: [
582 // Allow a file named like `utils/session.tanstack.ts` to
583 // override `utils/session.ts` when imported.
584 ".tanstack.tsx", ".tanstack.ts",
585 // Default import extensions
586 ".mjs", ".js", ".mts", ".ts",
587 ".jsx", ".tsx", ".json",
588 ],
589 },
590 };
591});
592```
593
594Then, I looked for every usage of a Next.js API, and either removed it or made
595a stub for TanStack. For example, `src/tanstack-next/link.tsx` implements
596`next/link`:
597
598```tsx filename="src/tanstack-next/link.tsx"
599import { Link } from "@tanstack/react-router";
600import type { LinkProps } from "next/link";
601
602export default function LinkAdapter({ href, ...rest }: LinkProps) {
603 return <Link {...rest} to={href as unknown as any} />;
604}
605```
606
607> Some of these stubs can be extremely simple. Starting out, my implementation
608> of `useRouter` was just `return {}`, but later I had to add a couple methods
609> to the object. The code here doesn't have to be clean, because it is
610> temporary.
611
612Now, the new site can import nearly every client component by either stubbing
613out the Next.js APIs it needs, or by using the `.tanstack.ts` extension to
614re-implement logic on a file-by-file basis. And shortly after, I got the site's
615homepage to work in TanStack Start, and we merged the branch.
616
617![My "nextgate" PR](/file/2025/blog-everyone-hates-nextjs/pr.png)
618
619> This first PR only supported one of our pages, and was able to do it in a
620> thousand lines of added code, and 40 lines deleted. I had previous patches to
621> remove the few uses of `next/image` and `next/font`.
622
623What was left was porting every other route over. The one thing we lose in
624migrating from Next.js to any other framework is the ability to `await`
625data-fetching functions in the UI. In practice, moving every route into a
626`loader` function made it much more clear what happened when a page was SSR'd.
627For pages that had multiple fetches, these could be combined into a single,
628special API call that would return all of the relevant data for that page.
629
630To re-iterate in bold font: <strong style='color:var(--secondary)'>The
631migration path from Server Components is to just simplify your code &mdash; RSC
632inherently drives you down a chaotic road of things you do not need</strong>.
633Nearly every complex part of our site got easier to understand for all
634engineers. The exception to this was having everyone get used to the new file
635system routing conventions. With enough examples, we all got the hang of it.
636
637With the incremental migration in place, new code did not break the existing
638deployment. TanStack slowly took over the codebase, and we eventually deleted
639all of the Next.js stubs and gained all of the beautiful [type-safety features]
640that the TanStack Router provides. At the end, the site performed faster from
641every angle: Development Mode, Production page load times, Soft navigations,
642and at a lower price than our Next depoyment with Vercel.
643
644[type-safety features]: https://tanstack.com/router/v1/docs/framework/react/guide/type-safety
645
646We're not the only ones seeing the change. While I try and keep myself off of
647social media, someone sent me [the results of Brian Anglin's work at
648Superwall][superwall-twitter], showing incredible CPU reductions on TanStack
649Start. I also recall ChatGPT switching from Next.js to Remix (random online
650chatter: [[1][chatgpt-1]] [[2][chatgpt-2]] [[3][chatgpt-3]]) a year ago.
651
652[superwall-twitter]: https://twitter.com/BriansAngles/status/1978834116079436242#m
653[chatgpt-1]: https://xcancel.com/ryanflorence/status/1831379475654947233
654[chatgpt-2]: https://old.reddit.com/r/reactjs/comments/1f97zgr/chatgpt_migrates_from_nextjs_to_remix
655[chatgpt-3]: https://old.reddit.com/r/nextjs/comments/1f92jdv/chatgptcom_switched_from_nextjs_to_remix
656
657[§3.1]: #next-metadata
658
659<Heading
660 level='h3'
661 slug='next-metadata'
662><code>next/metadata</code> is Great</Heading>
663
664In my opinion, this is one of the only good APIs Next.js has, and was the one
665place in our code where moving to TanStack made things harder to do. Instead of
666worsening the code, I just ported their metadata API into a regular function,
667so everyone can use it. Originally, I had a 1:1 port on NPM, but earlier this
668year I simplified it's API into one short and understandable
669file. As of this blog post, I have added a TanStack-compatible
670`meta.toTags` API, which can be installed from [JSR][lib-jsr], [NPM][lib-npm],
671or simply copied into your project.
672
673> **notice**: Due to time constraints with writing this article, the library
674> has not yet been updated. I'll probably get around to it by the ~~end of this
675> week (Oct 24th)~~ some time soon... As a placeholder, I'm able to share the
676> version that is used at work to my website:
677> [`meta.tanstack.ts`](https://paperclover.net/file/2025/blog-everyone-hates-nextjs/meta.tanstack.ts).
678
679```tsx
680// once in your project
681import * as meta from "@clo/lib/meta.ts";
682
683export const defineHead = meta.toTags.bind(null, {
684 // site-wide options
685 base: new URL("https://paperclover.net"),
686 titleTemplate: (title) => [title, "paper clover"]
687 .filter(Boolean).join(' | '),
688 // ...
689});
690
691// for each page...
692export const Route = createFileRoute("/blog")({
693 head: () =>
694 defineHead({
695 title: "clover's blog", // templated with `titleTemplate`
696 description: "a catgirl meows about her technology viewpoints",
697 canonical: "/blog", // joined with `base`
698
699 // When specified, configures Open Graph and Twitter embed,
700 // using the page title and description as the default.
701 // The defaults are good, but it supports more options.
702 embed: {
703 image: "/img/blog.webp",
704 },
705
706 // Every exotic meta tag is done with a JSX fragment. This
707 // doesn't render React, it just loops through the tags.
708 // My goal was to cover the most common 99% of uses.
709 extra: <>
710 <meta name="site-verification" content="waffles" />,
711 </>,
712 }),
713
714 component: Page,
715});
716
717function Page() {
718 ...
719}
720```
721
722My version wasn't concerned with covering the entire space of Next.js's metadata
723object, but instead uses inline JSX to fill that gap.
724
725[lib-jsr]: https://jsr.io/@clo/lib
726[lib-npm]: https://npmjs.com/@paperclover/lib
727
728[§3.2]: #vercel-og
729
730<Heading
731 level='h3'
732 slug='ditching-nextjs'
733><code>next/og</code> is Good Too</Heading>
734
735No strong opinions. I just want to remind everyone that the `@vercel/og` package exists.
736
737[§4]: #experience-feels-like-the-usual
738
739<Heading
740 level='h2'
741 slug='experience-feels-like-the-usual'
742>My Experience Feels like the Usual</Heading>
743
744At the Next.js Conf 2024, everyone there was raving about Server Components. I
745forget exactly who I talked to, but the big people were all in on this. I,
746having implemented the bundler end of RSC, saw a couple of the problems in the
747format. With Next 15 "stabilizing" the App Router last year, many companies are
748building their products on it, realizing these pitfalls first-hand.
749
750I came into the Next.js game late, only starting in June with version 15.
751But everyone I've talked to at events sympathize with my notes. All the people
752I talked to on the subject at Bun's 1.3 Party agreed with me. Even some people
753at Vercel told me they don't like how Next.js is to actually use.
754
755I hope as TanStack Start stabilizes, it becomes the Next.js replacement everyone
756wants.
757
758[§5]: #prefer-respectful-tools
759
760<Heading
761 level='h2'
762 slug='prefer-respectful-tools'
763>Prefer Tools that Respect You</Heading>
764
765A lot of in the JavaScript ecosystem is a mess. That mess is why web
766development gets made fun of. There were a lot of times I thought that working
767with the web was an unrecoverable mess, but the mess was actually just the
768commonly-used libraries I surrounded myself with. When that is peeled back,
769modern web development technologies are awesome.
770
771I've been making this website from scratch without any framework since late
7722024, by writing systems like my own [TUI progress widget][progress], [static
773file proxy][file-cache], incremental build system, and many more components.
774Working on this code has produced some of my best coding sessions (by
775happiness) in years. The viewers of *[paper clover]* get a better quality
776website; the mini-libraries I create get [extracted for public use][lib],
777everyone wins.
778
779This level of from-scratch is too much for most people, especially at the
780workplace. I say that at the minimum, we should only give our attention and
781money to high quality tools that respect us. And Next.js and the company behind
782it, Vercel, are not that.
783
784If you use Next.js, and feel that the experience doesn't remind you of respect
785too, consider whether you and your colleagues want to continue supporting their
786[serverless empire]. The Vite ecosystem seems pretty decent to build on right
787now, but I still have little experience in using their tools at scale in
788production. The [Vite+ launch from Void0][vite-plus] seems interesting, but
789only time will tell if these venture-funded tools will respect us (end-users
790and developers) long term.
791
792Next.js Conf 2025, as of writing, is [tomorrow][next-conf]. Instead of
793purchasing a $800 ticket, I decided to put that money [toward the TanStack
794team][tanstack-donate] for [respecting and improving the web development
795ecosystem][tanstack-ethos].
796
797[paper clover]: https://paperclover.net/
798[lib]: https://git.paperclover.net/clo/sitegen/src/branch/master/lib#readme
799[progress]: https://git.paperclover.net/clo/sitegen/src/branch/master/lib/progress.ts
800[file-cache]: https://git.paperclover.net/clo/sitegen/src/branch/master/src/file-viewer/cache.ts
801
802[next-conf]: https://nextjs.org/conf
803[vite-plus]: https://viteplus.dev/
804[tanstack-ethos]: https://tanstack.com/ethos
805[tanstack-donate]: https://github.com/sponsors/tannerlinsley
806[serverless empire]: https://youtu.be/SCIfWhAheVw
807
808## What the Future Holds
809
810Slowly, I've been replacing many pieces of software that disrespect me with
811better alternatives. Some examples of this are GitHub, Visual Studio Code,
812DaVinci Resolve, Discord, Google Drive/Workspace, along many more. I plan to
813write more on this blog about the technical things I do (that progress library,
814the purpose of my own site generator, learnings from my current job), including
815some of my past projects at Bun (details on HMR, the crash reporter, and the
816crazy system for bundling built-in modules). If it interests you, please
817subscribe to the email list:
818
819<a href="mailto:subscribe@paperclover.net?subject=paper%20clover%20mailing%20list&body=I%20would%20like%20to%20be%20subscribed%20to%20the%20following%20mailing%20lists%3A%0A%0A-%20Technical%20Blog%20Posts%20-%20YES%0A-%20Art%20(Original%20Music%2FVideo)%20-%20YES%0A%0A(feel%20free%20to%20write%20whatever%20else%20you%20want)">click here to send an email to <code>subscribe@paperclover.net</code>, requesting that you would like to be added to the mailing list.</a> (i manage this mailing list manually)
820
821[back to top](#top) &mdash; [ask a question about this article](/q+a)
822
823<br />
824<br />
825<br />
826<br />
827<br />
828<br />
829<footer>
8302025 (c) paper clover
831</footer>
832
833</Layout>
834
835<br />
836
src/blog/pages/webdev/one-year-next-app-router/es.mdo created+773
......@@ -0,0 +1,773 @@
1---
2layout: "@/blog/tags/blog-layout.marko"
3meta:
4 title: Un año con el enrutador de aplicación (App Router) de Next.js — por qué decidimos cambiar
5 description: Una crítica de los componentes de servidor de React y Next.js 15.
6 keywords: ["webdev", "technical analysis", "opinion"]
7 authors: ["clover caruso", "trubiso"]
8 embed:
9 thumbnail: /open-graph/next-js.es.png
10 canonical: /blog/webdev/one-year-next-app-router.es
11date: "Oct 21st, 2025"
12translation:
13 lang: es
14 author: "@trubiso"
15 href: null
16 date: Jan 13th, 2026
17---
18
19He estado usando [Next.js] profesionalmente para el desarrollo de aplicaciones
20web en mi trabajo y el diseño de su enrutador de aplicación (App Router) y los
21[componentes de servidor (React Server Components/RSC)][rsc] me parece
22extremadamente frustrante a nivel fundamental. No es por pequeños bugs o porque
23la API me parezca confusa, sino por grandes discrepancias en las decisiones de
24diseño fundamentales que tomaron Vercel y el equipo de React en su creación.
25
26Cuanto más asisto a eventos de desarrollo web, más veo a gente a quien no le
27gusta Next.js, pero que aún así debe usarlo. Al final del artículo, compartiré
28cómo escapamos de este infierno, migrando todo nuestro frontend a [TanStack
29Start] sin problemas.
30
31[Next.js]: https://nextjs.org
32[rsc]: https://react.dev/reference/rsc/server-components
33
34<table-of-contents />
35
36<h2#technical-review>Un repaso técnico: ¿qué son los componentes de servidor?</>
37
38El punto clave de RSC es que los componentes se dividen en dos categorías,
39componentes de <b class='server'>"servidor"</b> y componentes de <b class='client'>"cliente"</b>. Los componentes de servidor pueden usar ni
40`useState` ni `useEffect`, pero pueden hacer uso de funciones `async` y emplear
41herramientas de backend, permitiendo por ejemplo llamar directamente a una base
42de datos. Los componentes de cliente constituyen el modelo tradicional, en el
43que hay código en el backend para generar HTML y en el frontend para administrar
44el DOM usando `window.document.*`.
45
46> Ahí va el primer desastre: ¡la nomenclatura! React usa las palabras <b class='server'>"servidor"</b> y <b class='client'>"cliente"</b> para referirse a cosas muy específicas, ignorando sus definiciones existentes. No habría ningún problema, si no fuera por que ¡los componentes de <b class='client'>cliente</b> también pueden correr en el backend! En este artículo, utilizaré los términos <b>"backend"</b> y <b>"frontend"</b> para describir los ámbitos de ejecución en los que existen las aplicaciones web: un proceso de Node.js y un navegador web, respectivamente.
47
48Este modelo de componentes de <b class='server'>servidor</b> y de <b class='client'>cliente</b> es curioso. Dado que las funciones integradas, como
49`<Suspense />`, se serializan a través de la red, la obtención de datos se puede
50modelar de manera trivial con <b class='server'>componentes de servidor</b>
51asíncronos, y la interfaz de usuario alternativa funciona como si fuera del lado
52del cliente.
53
54```tsx filename="src/app/[username]/page.tsx" tint="server"
55// En este artículo, los componentes de servidor están resaltados en rojo.
56export default async function Page({ params }) {
57 // Los parámetros de la página se pasan como una Promise resuelta.
58 const { username } = await params;
59
60 // Los componentes `UserInfo` y `UserPostList` serán corridos a la vez. En
61 // cuanto `UserInfo` esté listo, el visitante verá la página con un
62 // `PostListSkeleton` si la lista de publicaciones no está lista.
63 return <main>
64 <UserInfo username={username} />
65
66 <Suspense fallback={<PostListSkeleton />}>
67 <UserPostList username={username} />
68 </Suspense>
69 </main>
70}
71
72// Evitamos waterfalls teniendo varios componentes evaluados a la vez.
73
74async function UserInfo({ username }) {
75 const user = await fetchUserInfo(username);
76 return <>
77 <h1>{user.displayName}</h1>
78 {user.bio ? <Markdown content={user.bio} /> : ""}
79 </>
80}
81
82async function UserPostList({ username }) {
83 const posts = await fetchUserPostList(username);
84 return /* interfaz de usuario de la lista de publicaciones omitida por brevedad */;
85}
86```
87
88El ejemplo anterior no usa nada de JavaScript (sin contar el tamaño del empaquetado
89_gzipped_ de 40kB del propio React) para la interfaz de usuario ni para la
90obtención de datos &mdash; ¡sólo envía el HTML! Por ejemplo, el _parser_ (analizador)
91imaginario de Markdown dentro del componente `<Markdown />` se queda en el
92backend. En aquellos casos en los que se necesita un frontend interactivo,
93simplemente se puede crear un <b class='client'>componente de cliente</b>
94poniéndolo en un archivo empezando con `"use client"`.
95
96```tsx filename="src/components/CopyButton.tsx" tint="client"
97"use client"; // Este comentario marca el archivo para el empaquetado del lado del cliente.
98
99export function CopyButton({ url }) {
100 return <>
101 <span>{url}</span>
102 <button onClick={() => {
103 const full = new URL(url, location.href);
104 navigator.clipboard.writeText(full.href);
105 // omitiendo todo el manejo de errores, la interfaz de éxito, los estilos...
106 }}>copy</button>
107 </>
108}
109```
110```tsx filename="src/app/q+a/Card.tsx" tint="server"
111export function Card() {
112 return <article>
113 <header>
114 {/* Hace que el navegador importe el botón de copiar */}
115 <CopyButton url="/q+a/2506010139" />
116 </header>
117 <p>
118 {/* Procesa el Markdown en el backend */}
119 <Markdown content=".........." />
120 </p>
121 </article>
122}
123```
124
125<h2#real-world-pitfalls>Los inconvenientes del enrutador de aplicación</>
126
127Tras dejar [Bun] como ingeniero de runtime (donde implementé el [empaquetado de
128componentes de servidor][Server Components bundling] y [una plantilla de
129RSC][bun-rsc]), me uní a una pequeña compañía, trabajando en la línea de fuego:
130una aplicación de Next.js con un backend de Hono. Lo siguiente es una versión
131simplificada de los problemas con los que me enfrenté tratando de mantener y
132desarrollar características nuevas. Como resultado de todos ellos, perdimos
133todos el tiempo intentando eludir fallos de diseño o explicándonos por qué algo
134que no debería ser un problema para empezar se ha convertido en un obstáculo
135inamovible.
136
137[Bun]: https://bun.com
138[Server Components bundling]: https://github.com/oven-sh/bun/blob/67f0c3e016aa479738469adac2b79a1862b88122/src/bake/bake.d.ts
139[bun-rsc]: https://github.com/oven-sh/bun/tree/e7790894d92b730758ecadf971cb935063508dfb/src/bake/bun-framework-react
140
141<h3#optimistic-updates>Las actualizaciones optimistas son imposibles</>
142
143La documentación de Next.js respecto a la mutación [no menciona las
144actualizaciones optimistas][nextjs-updating-data], parece ser que no pensaron en
145este caso. Los componentes renderizados por el <b class='server'>servidor de
146React</b> no pueden ser modificados tras ser montados por diseño. Los elementos
147que puedan tener cambios deben estar dentro de un componente de cliente, pero no
148puede estos componentes no pueden dar lugar a obtención de datos, incluso
149durante el renderizado del lado del servidor (SSR) en el backend. Esto deriva en
150componentes de servidor incómodamente diminutos que sólo se encargan de obtener
151ciertos datos y tienen un componente homólogo de cliente que contiene una
152versión prácticamente estática de la página.
153
154```tsx filename="src/app/user/[username]/page.tsx" tint="server"
155
156export default async function Page() {
157 const user = await fetchUserInfo(username);
158 return <ProfileLayout>
159 <UserProfile user={user} />
160 </ProfileLayout>;
161}
162```
163```tsx filename="src/app/user/[username]/UserProfile.tsx" tint="client"
164
165"use client"; // Toca mover el código de cliente a otro archivo!
166
167export function UserProfile({ user: initialUser }) {
168 // Existen muchas librerías de gestión de estado excelentes;
169 // para simplificar, usaremos una celda de estado.
170 const [user, optimisticUpdateUser] = useState(initialUser);
171
172 async function onEdit(newUser) {
173 optimisticUpdateUser(newUser);
174 const resp = await fetch("...", {
175 method: 'POST',
176 body: JSON.stringify(newUser),
177 ... // (encabezados, credenciales, trazado y más)
178 })
179 if (!resp.ok) /* ¡recuerda siempre comprobar si se ha dado algún error! */
180 }
181
182 return <main>{/* interfaz de usuario con campos editables... */}</main>:
183}
184```
185
186A medida que más partes de la página necesitan interactividad, se vuelve más
187lioso mantener las partes estáticas puramente del lado del servidor. En la
188aplicación de trabajo, casi cada componente de la interfaz de usuario muestra
189datos dinámicos. Un [`WebSocket`][ws] sincroniza los datos en tiempo real a
190medida que se actualizan (por ejemplo, el estado en línea y el perfil básico de
191una tarjeta de usuario). Como estas configuraciones de componentes son más
192difíciles de entender y mantener para los ingenieros, casi todas nuestras
193páginas están marcadas como `"use client"` con un `page.tsx` que define la
194obtención de datos necesaria.
195
196Un ejemplo más concreto de cómo queda esto en la práctica con la librería de
197obtención de datos que usamos en el trabajo, [TanStack Query].
198
199[TanStack Query]: https://github.com/tanstack/query#readme
200
201```ts filename="src/queries/users.ts"
202// Tenemos una función auxiliar `defineQuery` para la seguridad de tipos en el trabajo.
203// Los obtenedores de datos son triviales y pueden correr tanto en el backend como en el frontend.
204export const queryUserInfo = (username) => ({
205 queryKey: ['user', username],
206 queryFn: async ({ ... }) => /* obtener datos */
207});
208```
209```tsx filename="src/app/user/[username]/page.tsx" tint="server"
210export default async function Page({ params }) {
211 const { username } = await params;
212
213 // No hay estado global en el servidor de React. Como los layouts
214 // se ejecutan en paralelo, toca reconstruir el `QueryClient` de
215 // TanStack varias veces por ruta.
216 const queryClient = new QueryClient();
217 await queryClient.ensureQueryData(queryUserInfo(username));
218
219 // HydrationBoundary es un componente de cliente que pasa datos
220 // JSON del servidor de React al componente de cliente.
221 return <HydrationBoundary state={dehydrate(queryClient)}>
222 <ClientPage />
223 </HydrationBoundary>;
224}
225```
226```tsx filename="src/app/user/[username]/ClientPage.tsx" tint="client"
227"use client";
228export function ClientPage() {
229 const { username } = useParams();
230 const { data: user } = useSuspenseQuery(queryUserInfo(username));
231
232 // ... algunos hooks
233
234 return <main>
235 {/* ... una página web interactiva */}
236 </main>;
237}
238```
239
240Este ejemplo necesita tres archivos por las reglas del empaquetado de los
241componentes de servidor (el componente de cliente necesita `"use client"` y los
242archivos de componentes de servidor no suelen poderse importar en el cliente
243debido a las importaciones exclusivas de servidor). En el enrutador de páginas
244(Pages Router), podría haberlo hecho todo en un solo archivo por el tree-shaking
245que poseen `getStaticProps` y `getServerSideProps`.
246
247[ws]: https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API
248[nextjs-updating-data]: https://nextjs.org/docs/app/getting-started/updating-data
249
250<h3#redundant-fetches>Cada navegación supone una petición</h3>
251
252Como el enrutador de aplicación inicia cada página como componente de servidor,
253con áreas pequeñas de interactividad (idealmente), navegar a una nueva página
254¡*fuerza* una petición al servidor de Next.js, independientemente de los datos
255que el cliente ya tenga disponibles! Incluso con un archivo `loading.tsx`, al
256abrir `/`, navegar a `/other/ y luego volver a `/`, se mostrará el estado de
257carga mientras vuelve a obtener los datos de la página de inicio.
258
259Para lo único para lo que esto funciona bien es para el **contenido
260perfectamente estático**, donde las navegaciones instantáneas y la precarga
261(prefetching) funcionan genial. Pero **las aplicaciones web no son estáticas**,
262tienen mucho contenido dinámico. Haber iniciado sesión afecta a la página de
263inicio, cosa que es irritante porque el cliente literalmente ya tiene todo lo
264que necesita para mostrar la página instantáneamente. Ni siquiera han cambiado
265las cookies.
266
267> **nota**: Probándolo más en un proyecto en blanco, he observado casos en los
268> que el código de frontend de Next precarga rutas **sin ningún contenido
269> real**. En el ejemplo de hello world, era una carga de 1.8kB de RSC que
270> apuntaba a 2 fragmentos de JS diferentes 4 veces distintas. Esto es un desperdicio
271> puro de nuestro ancho de banda y egreso, especialmente considerando que toda
272> esta información se vuelve a obtener cuando de verdad hago clic en el enlace.
273>
274> ```json whitespace="pre-wrap"
275> 1:"$Sreact.fragment"
276> 2:I[39756,["/_next/static/chunks/ff1a16fafef87110.js","/_next/static/chunks/7dd66bdf8a7e5707.js"],"default"]
277> 3:I[37457,["/_next/static/chunks/ff1a16fafef87110.js","/_next/static/chunks/7dd66bdf8a7e5707.js"],"default"]
278> 4:I[97367,["/_next/static/chunks/ff1a16fafef87110.js","/_next/static/chunks/7dd66bdf8a7e5707.js"],"ViewportBoundary"]
279> 6:I[97367,["/_next/static/chunks/ff1a16fafef87110.js","/_next/static/chunks/7dd66bdf8a7e5707.js"],"MetadataBoundary"]
280> 7:"$Sreact.suspense"
281> 0:{"b":"TdwnOXsfOJapNex_HjHGt","f":[["children","other",["other",{"children":["__PAGE__",{}]}],["other",["$","$1","c",{"children":[null,["$","$L2",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L3",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","forbidden":"$undefined","unauthorized":"$undefined"}]]}],{"children":null},[["$","div","l",{"children":"loading..."}],[],[]],false],["$","$1","h",{"children":[null,["$","$1","KCFxAJdIDH3BlYXAHsbcVv",{"children":[["$","$L4",null,{"children":"$L5"}],["$","meta",null,{"name":"next-size-adjust","content":""}]]}],["$","$L6","KCFxAJdIDH3BlYXAHsbcVm",{"children":["$","div",null,{"hidden":true,"children":["$","$7",null,{"fallback":null,"children":"$L8"}]}]}]]}],false]],"S":false}
282> 5:[["$","meta","0",{"charSet":"utf-8"}],["$","meta","1",{"name":"viewport","content":"width=device-width, initial-scale=1"}]]
283> 9:I[27201,["/_next/static/chunks/ff1a16fafef87110.js","/_next/static/chunks/7dd66bdf8a7e5707.js"],"IconMark"]
284> 8:[["$","title","0",{"children":"Create Next App"}],["$","meta","1",{"name":"description","content":"Generated by create next app"}],["$","link","2",{"rel":"icon","href":"/favicon.ico?favicon.0b3bf435.ico","sizes":"256x256","type":"image/x-icon"}],["$","$L9","3",{}]]
285> ```
286>
287> Revisándolo, me di cuenta de que en realidad hay algo de contenido: el estado
288> de carga. ¿Lo ves?
289>
290> ```json
291> ["$","div","l",{"children":"loading..."}]
292> ```
293>
294> Sigue siendo un desperdicio grande, ya que todos estos datos se vuelven a
295> emitir en el RSC de la propia página.
296
297Parece ser que la solución a esto es [`staleTime`][nextjs-stale], pero está
298marcado como experimental y "no recomendado para producción". Es una vergüenza
299el hecho de que esto sea una opción que no está por defecto y que parece haber
300sido una idea de último momento. Incluso usándola, no es posible hacer que
301varias páginas que hacen referencia a los mismos datos los compartan.
302
303Un ejemplo de un estado de carga irrepresentable con el enrutador de aplicación
304es el de ciertas páginas, como una página de propuestas (issues) en un proyecto
305de git, al hacer clic en un nombre de usuario para ir a su página de perfil. Con
306`loading.tsx`, la página entera es un esqueleto, pero al modelar estas consultas
307con TanStack Query es posible mostrar el nombre de usuario y avatar
308instantáneamente mientras cargan la biografía del usuario y sus repositorios.
309Los componentes de servidor son incompatibles con esta tipo de navegación porque
310los datos sólo están disponibles en componentes renderizados, con lo cual deben
311ser obtenidos una vez más.
312
313[nextjs-stale]: https://nextjs.org/docs/app/api-reference/config/next-config-js/staleTimes
314[nextjs-stale-bug]: https://github.com/vercel/next.js/issues/70661
315
316En nuestro sitio Next.js, tenemos esta línea de código en nuestros obtenedores
317de datos en los componentes de servidor para hacer que las navegaciones suaves
318vayan más rápido, saltándose la fase de obtención de datos por completo.
319
320```tsx filename="src/util/tanstack-query-helpers.server.ts" tint="server"
321export function serverSidePrefetchQueries(queries) {
322 if ((await headers()).get("next-url")) {
323 // Se trata de una navegación suave. Nos SALTAMOS la precarga para
324 // aumentar la velocidad. Puede que el cliente ya tenga estos datos y,
325 // de lo contrario, tienen el estado de carga. Idealmente, no existiría
326 // esta petición -- el lado de cliente ya tiene casi TODO el código ya
327 // que la aplicación está escrita principalmente usando componentes de
328 // cliente. Un fallo de diseño de parte del enrutador de aplicación, la
329 // verdad.
330 return;
331 }
332 // ... lógica de precarga de datos ...
333}
334```
335
336Aparte, `loading.tsx` debería contener las llamadas a `useQuery` para que,
337mientras se realiza la petición para el RSC vacío, se obtengan los datos si de
338verdad se necesitan. De hecho, el estado `loading.tsx` puede ser directamente el
339componente de cliente y se mostrará la página de cliente.
340
341```tsx filename="src/app/user/[username]/loading.tsx" tint="client"
342"use client";
343export default function PageLoadingSkeleton() {
344 return <ClientPage />;
345}
346```
347
348> En el trabajo, simplemente hacemos que nuestros archivos `loading.tsx`
349> contengan las llamadas a `useQuery` y muestren un esqueleto. Esto se debe a
350> que cuando Next.js carga el componente de servidor en sí, remonta la página
351> entera sí o sí. No se da ningún tipo de VDOM diffing, así que todos los hooks
352> (`useState`) se reiniciarán un poco después de que la petición se complete.
353> Intenté reproducir un caso simple en el que estaba *suplicándole* a Next.js
354> que tan solo *actualizara el DOM existente* y preservara el estado, pero
355> directamente no lo hace. Por suerte, el tiempo que tarda la llamada RSC en
356> blanco es suficientemente corto.
357
358<h3#layout-restrictions>Los layouts tienen restricciones artificiales</h3>
359
360Los layouts pueden obtener datos, pero no pueden observar o alterar la petición
361de ninguna manera. Está hecho así para que Next.js pueda obtener y cachear los
362layouts cada vez que quiera. En cualquier otro framework, los layouts son
363componentes normales sin diferencia de otros componentes en la página.
364
365Obtener los layouts por sí mismos es una idea chula, pero acaba siendo estúpida,
366pues toda obtención de datos debe rehacerse para cada layout. No puedes
367compartir un `QueryClient`, en su lugar debes usar su
368[`fetch` _monkey-patched_][nextjs-fetch] para cachear la misma petición `GET`
369tal y como prometen.
370
371Cuando me pregunta un compañero de trabajo por qué Next.js rechaza código, ya ni
372intento explicar los entresijos, directamente digo *"Es un problema de habilidad
373de Next.js, no te preocupes, que le voy a dar fuego pronto"*. Estas reglas son
374demasiado difíciles para que un desarrollador promedio las entienda.
375
376[nextjs-fetch]: https://nextjs.org/docs/app/api-reference/functions/fetch
377
378<h3#rsc-payload>Te llevas todo el contenido dos veces de todas formas</h3>
379
380A diferencia de la ["arquitectura de islas"][islands], los componentes de
381servidor deben ser hidratados en el frontend para poder emplear `Suspense` y
382mantener el estado de los componentes de cliente. Al hacer navegaciones suaves,
383se obtiene mediante `fetch` la "carga de RSC" (que no es HTML en absoluto).
384Al hacer una recarga fresca de la página, se necesita HTML para
385[pintar por primera vez][first paint], pero la información sobre los componentes
386de cliente y `Suspense` no está en dicho HTML. La solución de React es **enviar
387una segunda copia del _markup_ de la página entera**. Un ejemplo de lo que
388enviaría un servidor de producción de Next.js en un renderizado dinámico de una
389página sería algo así:
390
391[first paint]: https://web.dev/articles/fcp
392
393```html filename="GET /user/clover"
394<!DOCTYPE html>
395<html>
396<head>
397 {etiquetas link y meta}
398</head>
399<body>
400 {renderizado del lado del servidor}
401 <script>
402 // un script de inicialización que inicia `__next_f`
403 // como un arreglo. en cuanto carga React, la función
404 // `.push` se modifica para escribir nuevos fragmentos
405 // al decodificador de RSC directamente. tiene algunos
406 // auxiliares de DOM también
407 (self.__next_f=self.__next_f||[]).push([0])
408 </script>
409 <script>
410 // la carga de RSC para el shell de la aplicación.
411 self.__next_f.push([1,"1:\"$Sreact.fragment\"\n2:I[658993,[\"/_next/st{...}"])
412 </script>
413
414 <!--
415 NO está escrita todavía la etiqueta </body>, ya que hay una
416 frontera de Suspense sin resolver. con el paso del tiempo,
417 se escriben más datos.
418 -->
419 <div class="user-post-list">
420 {renderizado del lado del servidor de una frontera de Suspense}
421 </div>
422 <script>
423 // la carga de RSC para la frontera de Suspense
424 self.__next_f.push([2,"14:[\"$\",\"div\",null,{\"children\":[[\"$\",\"h4\"{...}"])
425 </script>
426
427 <!-- se repiten etiquetas HTML y script hasta que la página acaba -->
428</body>
429</html>
430```
431
432Esta solución **duplica el tamaño de la carga inicial de HTML**. Aún peor, la
433carga de RSC incluye JSON en cadenas de texto de JS, un formato mucho menos
434eficiente que HTML. Aunque parece comprimirse bien con brotli y renderizarse
435rápido en el navegador, esto es un desperdicio. Con el patrón de hidratación,
436los datos se podrían reutilizar localmente, por lo menos, para la interactividad
437y otras páginas.
438
439Incluso en páginas con ínfima interactividad, pagas el precio. Como ejemplo, la
440documentación de Next.js, al cargar su [página de
441inicio](https://nextjs.org/docs), carga una página de alrededor de 750kB (250kB
442de HTML y 500kB de etiquetas script), y el contenido aparece dos veces.
443
444Puedes comprobarlo pulsando <kbd>Cmd</kbd> + <kbd>Opt</kbd> + <kbd>u</kbd> en
445Mac o <kbd>Ctrl</kbd> + <kbd>u</kbd> en otras plataformas y después
446<kbd>Cmd</kbd> / <kbd>Ctrl</kbd> + <kbd>f</kbd> para ubicar cualquier cadena
447en el blog, como "construyendo aplicaciones web full-stack". Aparece dos veces.
448Y **es inevitable**, ya que es una parte fundamental de los componentes de
449servidor de React.
450
451El formato de RSC definitivamente tiene más fallas, pero no tengo ganas de
452ponerme a investigar por qué la cadena `/_next/static/chunks/6192a3719cda7dcc.js`
453aparece 27 veces. ¿Qué diablos? ¿¿¿Dais el ancho de banda por gratis???
454
455[islands]: https://www.patterns.dev/vanilla/islands-architecture/
456
457<h3#turbopack>Turbopack da asco</h3>
458
459Esta sección no es constructiva.
460
461- Turbopack no es ágil
462- Turbopack emite código difícil de depurar (en modo de desarrollo)
463- Turbopack produce mensajes de error malos en muchos casos
464
465Normalmente no le habría dado una sección en el blog a este punto, pero quiero
466mostrar tres ejemplos de verdad, directamente extraídos del proyecto.
467
468El primero es un lugar donde, al refactorizar código para satisfacer los modelos
469de componente de servidor/cliente, hice asíncrono un componente de cliente. Era
470irritante porque no indicaba dónde estaba el error, sólo contenía el stack trace
471del <b class='server'>servidor</b>.
472
473![Next.js error](/file/2025/blog-everyone-hates-nextjs/asyncerror.png)
474
475Otro ejemplo de un error horrible:
476
477![Next.js error](/file/2025/blog-everyone-hates-nextjs/nexterror.png)
478
479> Tras arreglar el problema detrás de este segundo error (que ni siquiera recuerdo),
480> el servidor de desarrollo se quedó colgado y tuve que reiniciarlo para que se recuperara.
481
482El último caso es el millón de veces que he puesto un _breakpoint_ (punto de
483interrupción) en el depurador y la variable `hola` se convierte en
484`__TURBOPACK__imported__module__$5b$project$5d2f$client$2f$src$2f$utils$2f$filename$2e$ts__$5b$app$2d$client$5d$__$28$ecmascript$29$__["hola"]`
485y más mierda.
486
487Vale. Todo esto da asco. ¿Qué podemos hacer?
488
489<h2#ditching-nextjs>Dejando atrás Next.js y Vercel en el trabajo</h2>
490
491Hay dos tipos de proyectos web:
492
493- Una página web con contenido mayoritariamente estático.
494- Una aplicación web con componentes mayormente dinámicos e interactivos.
495
496Y Next.js es la herramienta equivocada para ambos. Si quieres una página web
497estática, usa [Astro] o [Fresh]. Para quienes necesiten la potencia de React,
498esta sección trata cómo pasé de estar atado a Next a [TanStack Start], de forma
499incremental y sin baches.
500
501[Astro]: https://astro.build/
502[Fresh]: https://fresh.deno.dev/
503[TanStack Start]: https://tanstack.com/start/latest
504
505Todo empezó con esta configuración de Vite.
506
507```ts filename="vite.config.ts"
508const config = defineConfig(({ mode }) => {
509 const env = loadEnv(mode, process.cwd(), "NEXT_PUBLIC_");
510 return {
511 // Usa el puerto predeterminado de Next.js, 3000
512 server: { port: 3000 },
513 // Usa el prefijo de variables de entorno predeterminado de Next.js, "NEXT_PUBLIC_"
514 define: Object.fromEntries(Object.entries(env).map(
515 ([k, v]) => [`process.env.${k}`, JSON.stringify(v)])),
516 plugins: [
517 viteTsConfigPaths({ projects: ["./tsconfig.json"] }),
518 tailwindcss(),
519 // Para que mis compañeros de trabajo lo entendieran bien, empecé
520 // a portar las rutas en `src/tanstack-routes`; en cuanto acabara,
521 // volvería a cambiarlo a `src/routes`, como estaba por defecto.
522 tanstackStart({
523 router: { routesDirectory: "src/tanstack-routes" },
524 }),
525 viteReact(),
526 ],
527 resolve: {
528 // La clave para la migración incremental: redirigir `next` a otro lugar
529 alias: { next: path.resolve("./src/tanstack-next/") },
530 conditions: ["tanstack"],
531 extensions: [
532 // Permitir que un archivo como `utils/session.tanstack.ts`
533 // sobreescriba a `utils/session.ts` al ser importado.
534 ".tanstack.tsx", ".tanstack.ts",
535 // Extensiones de importación predeterminadas
536 ".mjs", ".js", ".mts", ".ts",
537 ".jsx", ".tsx", ".json",
538 ],
539 },
540 };
541});
542```
543
544Después, me puse a buscar cada uso de una API de Next.js, para o eliminarlo o
545crear un talón (_stub_) para TanStack. Por ejemplo, `src/tanstack-next/link.tsx`
546implementa `next/link`:
547
548```tsx filename="src/tanstack-next/link.tsx"
549import { Link } from "@tanstack/react-router";
550import type { LinkProps } from "next/link";
551
552export default function LinkAdapter({ href, ...rest }: LinkProps) {
553 return <Link {...rest} to={href as unknown as any} />;
554}
555```
556
557> Algunos de estos talones pueden ser extremadamente simples. Al principio, mi
558> implementación de `useRouter` era `return {}`. Más tarde, ya tuve que añadir
559> algunos métodos al objeto. El código no tiene por qué ser limpio, ya que es
560> temporal.
561
562De ahí, el sitio nuevo puede importar casi cada componente de cliente o creando
563talones para las APIs de Next.js que necesite o usando la extensión `.tanstack.ts`
564para reimplementar la lógica archivo por archivo. Poco después, conseguí que
565la página principal del sitio funcionara en TanStack Start e hicimos _merge_.
566
567![Mi PR (solicitud de incorporación de cambios) "nextgate"](/file/2025/blog-everyone-hates-nextjs/pr.png)
568
569> En esta primera PR (solicitud de incorporación de cambios) sólo funcionaba una
570> de nuestras páginas, y conseguí que funcionara con unas mil líneas de código
571> añadido y 40 líneas eliminadas. Algunos parches previos eliminaban los pocos
572> usos de `next/image` y `next/font`.
573
574Lo que quedaba era portar el resto de rutas. Lo único que perdemos al migrar de
575Next.js a cualquier otro framework es el poder usar `await` con funciones de
576obtención de datos en la interfaz de usuario. En la práctica, mover cada ruta
577a una función `loader` esclareció qué era lo que pasaba al renderizar cada
578página desde el servidor (SSR). Para páginas con varias llamadas de obtención de
579datos, estas podían combinarse en una llamada API especial que devolviera todos
580los datos relevantes a ellas.
581
582Para reiterar, en negrita: <strong style='color:var(--secondary)'>El camino de
583migración de componentes de servidor es simplificar tu código &mdash; RSC
584intrínsecamente te lleva por un camino caótico repleto de cosas
585innecesarias</strong>. Casi todas las partes complejas de nuestro sitio se
586volvieron más fáciles de entender para todos nuestros ingenieros. La única
587excepción fue acostumbrar a todos a las nuevas convenciones para el enrutado
588del sistema de archivos. Con suficientes ejemplos, todos lo acabamos pillando.
589
590Con la migración incremental ya operativa, no se rompía el _deployment_ existente
591al añadir código nuevo. TanStack fue apoderándose del código y, con el tiempo,
592fuimos eliminando todos los talones de Next.js y ganamos todas las maravillosas
593[características de seguridad de tipos][type-safety features] que ofrece el
594enrutador de TanStack (TanStack Router). Al final, el sitio rendía más rápido en
595todos los aspectos: el modo de desarrollo, tiempos de carga en producción,
596navegaciones suaves, y todo a un menor coste que nuestro _deployment_ de Next con
597Vercel.
598
599[type-safety features]: https://tanstack.com/router/v1/docs/framework/react/guide/type-safety
600
601No somos los únicos sintiendo el cambio. Aunque intento evitar las redes
602sociales, alguien me envió [los resultados del trabajo de Brian Anglin en
603Superwall][superwall-twitter], mostrando reducciones de CPU increíbles usando
604TanStack Start. También recuerdo el cambio de ChatGPT de Next.js a Remix hace un
605año (conversaciones relevantes: [[1][chatgpt-1]] [[2][chatgpt-2]] [[3][chatgpt-3]]).
606
607[superwall-twitter]: https://twitter.com/BriansAngles/status/1978834116079436242#m
608[chatgpt-1]: https://xcancel.com/ryanflorence/status/1831379475654947233
609[chatgpt-2]: https://old.reddit.com/r/reactjs/comments/1f97zgr/chatgpt_migrates_from_nextjs_to_remix
610[chatgpt-3]: https://old.reddit.com/r/nextjs/comments/1f92jdv/chatgptcom_switched_from_nextjs_to_remix
611
612<h3#next-metadata><code>next/metadata</code> es maravilloso</h3>
613
614Bajo mi punto de vista, esta es una de las pocas APIs buenas que tiene Next.js,
615y fue el único lugar en nuestro código en el que el cambio a TanStack resultó en
616mayor dificultad. En vez de empeorar el código, porté su API de metadatos a una
617función normal y corriente, para que todos la pudieran usar. Solía tener un
618puerto 1:1 en NPM, pero a comienzos del año simplifiqué su API a un archivo
619pequeño y comprensible. En el momento de escribir esta publicación, he añadido
620una API `meta.toTags` compatible con TanStack que puede instalarse desde
621[JSR][lib-jsr] o [NPM][lib-npm], o simplemente la puedes copiar a tu proyecto.
622
623> **aviso**: Por limitaciones de tiempo para escribir este artículo, la librería
624> todavía no está actualizada. Probablemente lo haré ~~para el final de esta
625> semana (24 de octubre)~~ pronto... Por ahora, puedo compartir la versión que
626> uso en el trabajo:
627> [`meta.tanstack.ts`](https://paperclover.net/file/2025/blog-everyone-hates-nextjs/meta.tanstack.ts).
628
629```tsx
630// una vez en tu proyecto
631import * as meta from "@clo/lib/meta.ts";
632
633export const defineHead = meta.toTags.bind(null, {
634 // opciones para todo el sitio
635 base: new URL("https://paperclover.net"),
636 titleTemplate: (title) => [title, "paper clover"]
637 .filter(Boolean).join(' | '),
638 // ...
639});
640
641// para cada página...
642export const Route = createFileRoute("/blog")({
643 head: () =>
644 defineHead({
645 title: "blog de clover", // usando la plantilla `titleTemplate`
646 description: "una gatita maúlla sobre sus opiniones tecnológicas",
647 canonical: "/blog", // yuxtapuesto a `base`
648
649 // Cuando se especifica, configura el embed (embebido)
650 // de Open Graph y Twitter, usando el título y la
651 // descripción de la página por defecto.
652 // La configuración predeterminada está bien,
653 // pero se pueden especificar más opciones.
654 embed: {
655 image: "/img/blog.webp",
656 },
657
658 // Todas las etiquetas meta exóticas se hacen con un fragmento
659 // JSX. No se renderiza React, sólo se itera sobre las etiquetas.
660 // Mi objetivo era cubrir el 99% de los casos comunes.
661 extra: <>
662 <meta name="site-verification" content="waffles" />,
663 </>,
664 }),
665
666 component: Page,
667});
668
669function Page() {
670 ...
671}
672```
673
674Mi versión no se preocupaba con cubrir todas las posibilidades del objeto de
675metadatos de Next.js; usa JSX en línea (_inline_) para llenar ese hueco.
676
677[lib-jsr]: https://jsr.io/@clo/lib
678[lib-npm]: https://npmjs.com/@paperclover/lib
679
680<h3#vercel-og><code>next/og</code> está bien también</h3>
681
682No tengo una opinión fuerte al respecto. Sólo quiero recordar a todos que existe
683el paquete `@vercel/og`.
684
685<h2#experience-feels-like-the-usual>Mi experiencia parece ser la típica</h2>
686
687En la Next.js Conf 2024, todos hablaban maravillas de los componentes de
688servidor. No recuerdo exactamente con quién fue que hablé, pero todos los
689grandes nombres estaban completamente a favor de ellos. Habiendo implementado el
690empaquetador de RSC, yo vi algunos de los problemas en el formato. Ahora, viendo
691que Next 15 "estabilizó" el enrutador de aplicaciones el año pasado, muchas
692compañías están construyendo productos con él, dándose cuenta de estos
693inconvenientes de primera mano.
694
695Llegué tarde al mundo de Next.js, pues empecé en junio con la versión 15. Pero
696todos con los que he hablado están de acuerdo con mis observaciones. Todos con
697los que hablé en la 1.3 Party de Bun estaban de acuerdo conmigo. Incluso gente
698de Vercel me ha dicho que no les gusta cómo es usar Next.js.
699
700Espero que, al estabilizarse TanStack Start, se vuelva el nuevo sustituto de
701Next.js que todos quieran.
702
703<h2#prefer-respectful-tools>Opta por herramientas que te respeten</h2>
704
705El ecosistema de JavaScript es un desastre y ese desastre es por el que la gente
706se burla del desarrollo web. Muchas veces he pensado que el desastre de trabajar
707en la web no tenía solución, pero el desastre eran en verdad las librerías
708comúnmente usadas de las que me rodeaba. Al quitar esa capa, las tecnologías de
709desarrollo web modernas son maravillosas.
710
711Llevo desarrollando esta página web desde cero, sin framework, desde finales de
7122024, implementando sistemas como mi propio [widget de progreso para
713aplicaciones de terminal][progress], [proxy para archivos
714estáticos][file-cache], sistema de compilación incremental, y muchos más
715componentes. Trabajar en este código ha llevado a las mejores sesiones de código
716(en términos de felicidad) en años. Los visitantes de *[paper clover]* se llevan
717una página web de mejor calidad; las minilibrerías que he creado las [extraigo
718para uso público][lib], todos salen ganando.
719
720Este nivel de "desde cero" es demasiado para la mayoría, especialmente en el
721trabajo. Diría que, como mínimo, deberíamos dedicar atención y dinero sólo a
722herramientas de alta calidad que nos respeten. Y Next.js y la empresa detrás de
723ello, Vercel, no caen dentro de ese criterio.
724
725Si usas Next.js y sientes que tu experiencia tampoco te recuerda al respeto,
726reflexiona si tus compañeros de trabajo y tú queréis seguir apoyando su [imperio
727serverless][serverless empire]. El ecosistema de Vite parece bastante decente
728para desarrollar ahora mismo, pero no tengo mucha experiencia todavía en usar
729sus herramientas a gran escala en producción. El [lanzamiento de Vite+ de
730Void0][vite-plus] parece interesante, pero sólo el tiempo dirá si estas
731herramientas, financiadas por capital, nos respetarán (a los usuarios y a los
732desarrolladores) a largo plazo.
733
734La Next.js Conf 2025, en el momento de escribir, es [mañana][next-conf]. En vez
735de comprarme una entrada de $800, decidí destinar ese dinero al [equipo de
736TanStack][tanstack-donate] por [respetar y mejorar el ecosistema de desarrollo
737web][tanstack-ethos].
738
739[paper clover]: https://paperclover.net/
740[lib]: https://git.paperclover.net/clo/sitegen/src/branch/master/lib#readme
741[progress]: https://git.paperclover.net/clo/sitegen/src/branch/master/lib/progress.ts
742[file-cache]: https://git.paperclover.net/clo/sitegen/src/branch/master/src/file-viewer/cache.ts
743
744[next-conf]: https://nextjs.org/conf
745[vite-plus]: https://viteplus.dev/
746[tanstack-ethos]: https://tanstack.com/ethos
747[tanstack-donate]: https://github.com/sponsors/tannerlinsley
748[serverless empire]: https://youtu.be/SCIfWhAheVw
749
750## Lo que aguarda el futuro
751
752Paulatinamente, he estado reemplazando mucho software que me falta al respeto
753con mejores alternativas. Algunos ejemplos son GitHub, Visual Studio Code,
754DaVinci Resolve, Discord, Google Drive/Workspace, entre muchos otros. Pienso
755escribir más en este blog sobre mis proyectos técnicos (la librería de progreso,
756el propósito de mi generador de sitio propio, aprendizajes de mi trabajo
757actual), incluyendo algunos de mis proyectos pasados en Bun (detalles sobre HMR,
758el sistema de reporte de errores y la locura de sistema para empaquetar módulos
759integrados). Si te interesa, por favor suscríbete a la lista de correos:
760
761<a href="mailto:subscribe@paperclover.net?subject=paper%20clover%20mailing%20list&body=I%20would%20like%20to%20be%20subscribed%20to%20the%20following%20mailing%20lists%3A%0A%0A-%20Technical%20Blog%20Posts%20-%20YES%0A-%20Art%20(Original%20Music%2FVideo)%20-%20YES%0A%0A(feel%20free%20to%20write%20whatever%20else%20you%20want)">haz clic aquí para enviar un correo a <code>subscribe@paperclover.net</code>, pidiendo ser añadido a la lista de correos.</a> (llevo esta lista de correos a mano)
762
763[volver al inicio](#top) &mdash; [pregúntame algo sobre este artículo](/q+a)
764
765<br />
766<br />
767<br />
768<br />
769<br />
770<br />
771<footer>
7722025 (c) paper clover
773</footer>
src/blog/pages/webdev/one-year-next-app-router/es.mdx deleted-868
......@@ -1,868 +0,0 @@
1import Heading from "@/blog/tags/heading.tsx";
2import TableOfContents from "@/blog/tags/table-of-contents.tsx";
3import { Layout } from "@/blog/tags/layout.tsx";
4export { theme } from "@/blog/tags/layout.tsx";
5
6export const meta = {
7 title: "Un año con el enrutador de aplicación (App Router) de Next.js — por qué decidimos cambiar",
8 description: "Una crítica de los componentes de servidor de React y Next.js 15.",
9 keywords: ["webdev", "technical analysis", "opinion"],
10 authors: ["clover caruso"],
11 embed: {
12 thumbnail: "/open-graph/next-js.es.png"
13 },
14 twitter: {
15 image: "https://paperclover.net/open-graph/next-js.png"
16 },
17 canonical: "/blog/webdev/one-year-next-app-router.es"
18};
19
20<Layout
21 meta={meta}
22 date={'Oct 21st, 2025'}
23 slug="webdev/one-year-next-app-router"
24 tags={meta.keywords}
25 translation={{
26 lang: "es",
27 author: "@trubiso",
28 href: null,
29 date: "Jan 13th, 2026"
30 }}
31>
32
33He estado usando [Next.js] profesionalmente para el desarrollo de aplicaciones
34web en mi trabajo y el diseño de su enrutador de aplicación (App Router) y los
35[componentes de servidor (React Server Components/RSC)][rsc] me parece
36extremadamente frustrante a nivel fundamental. No es por pequeños bugs o porque
37la API me parezca confusa, sino por grandes discrepancias en las decisiones de
38diseño fundamentales que tomaron Vercel y el equipo de React en su creación.
39
40Cuanto más asisto a eventos de desarrollo web, más veo a gente a quien no le
41gusta Next.js, pero que aún así debe usarlo. Al final del artículo, compartiré
42cómo escapamos de este infierno, migrando todo nuestro frontend a [TanStack
43Start] sin problemas.
44
45[Next.js]: https://nextjs.org
46[rsc]: https://react.dev/reference/rsc/server-components
47
48<TableOfContents>
49
50- [Un repaso técnico: ¿qué son los componentes de servidor?][§1]
51- [Los inconvenientes del enrutador de aplicaciones][§2]
52 - [Las actualizaciones optimistas son imposibles][§2.1]
53 - [Cada navegación supone una petición][§2.2]
54 - [Los layouts tienen restricciones artificiales][§2.3]
55 - [Te llevas todo el contenido dos veces de todas formas][§2.4]
56 - [Turbopack da asco][§2.5]
57- [Dejando atrás Next.js y Vercel en el trabajo][§3]
58 - [`next/metadata` es maravilloso][§3.1]
59 - [`next/og` está bien también][§3.2]
60- [Mi experiencia parece ser la típica][§4]
61- [Opta por herramientas que te respeten][§5]
62
63</TableOfContents>
64
65[§1]: #technical-review
66
67<Heading
68 level='h2'
69 slug='technical-review'
70>Un repaso técnico: ¿qué son los componentes de servidor?</Heading>
71
72El punto clave de RSC es que los componentes se dividen en dos categorías,
73componentes de <b class='server'>"servidor"</b> y componentes de <b class='client'>"cliente"</b>. Los componentes de servidor pueden usar ni
74`useState` ni `useEffect`, pero pueden hacer uso de funciones `async` y emplear
75herramientas de backend, permitiendo por ejemplo llamar directamente a una base
76de datos. Los componentes de cliente constituyen el modelo tradicional, en el
77que hay código en el backend para generar HTML y en el frontend para administrar
78el DOM usando `window.document.*`.
79
80> Ahí va el primer desastre: ¡la nomenclatura! React usa las palabras <b class='server'>"servidor"</b> y <b class='client'>"cliente"</b> para referirse a cosas muy específicas, ignorando sus definiciones existentes. No habría ningún problema, si no fuera por que ¡los componentes de <b class='client'>cliente</b> también pueden correr en el backend! En este artículo, utilizaré los términos <b>"backend"</b> y <b>"frontend"</b> para describir los ámbitos de ejecución en los que existen las aplicaciones web: un proceso de Node.js y un navegador web, respectivamente.
81
82Este modelo de componentes de <b class='server'>servidor</b> y de <b class='client'>cliente</b> es curioso. Dado que las funciones integradas, como
83`<Suspense />`, se serializan a través de la red, la obtención de datos se puede
84modelar de manera trivial con <b class='server'>componentes de servidor</b>
85asíncronos, y la interfaz de usuario alternativa funciona como si fuera del lado
86del cliente.
87
88```tsx filename="src/app/[username]/page.tsx" tint="server"
89// En este artículo, los componentes de servidor están resaltados en rojo.
90export default async function Page({ params }) {
91 // Los parámetros de la página se pasan como una Promise resuelta.
92 const { username } = await params;
93
94 // Los componentes `UserInfo` y `UserPostList` serán corridos a la vez. En
95 // cuanto `UserInfo` esté listo, el visitante verá la página con un
96 // `PostListSkeleton` si la lista de publicaciones no está lista.
97 return <main>
98 <UserInfo username={username} />
99
100 <Suspense fallback={<PostListSkeleton />}>
101 <UserPostList username={username} />
102 </Suspense>
103 </main>
104}
105
106// Evitamos waterfalls teniendo varios componentes evaluados a la vez.
107
108async function UserInfo({ username }) {
109 const user = await fetchUserInfo(username);
110 return <>
111 <h1>{user.displayName}</h1>
112 {user.bio ? <Markdown content={user.bio} /> : ""}
113 </>
114}
115
116async function UserPostList({ username }) {
117 const posts = await fetchUserPostList(username);
118 return /* interfaz de usuario de la lista de publicaciones omitida por brevedad */;
119}
120```
121
122El ejemplo anterior no usa nada de JavaScript (sin contar el tamaño del empaquetado
123_gzipped_ de 40kB del propio React) para la interfaz de usuario ni para la
124obtención de datos &mdash; ¡sólo envía el HTML! Por ejemplo, el _parser_ (analizador)
125imaginario de Markdown dentro del componente `<Markdown />` se queda en el
126backend. En aquellos casos en los que se necesita un frontend interactivo,
127simplemente se puede crear un <b class='client'>componente de cliente</b>
128poniéndolo en un archivo empezando con `"use client"`.
129
130```tsx filename="src/components/CopyButton.tsx" tint="client"
131"use client"; // Este comentario marca el archivo para el empaquetado del lado del cliente.
132
133export function CopyButton({ url }) {
134 return <>
135 <span>{url}</span>
136 <button onClick={() => {
137 const full = new URL(url, location.href);
138 navigator.clipboard.writeText(full.href);
139 // omitiendo todo el manejo de errores, la interfaz de éxito, los estilos...
140 }}>copy</button>
141 </>
142}
143```
144```tsx filename="src/app/q+a/Card.tsx" tint="server"
145export function Card() {
146 return <article>
147 <header>
148 {/* Hace que el navegador importe el botón de copiar */}
149 <CopyButton url="/q+a/2506010139" />
150 </header>
151 <p>
152 {/* Procesa el Markdown en el backend */}
153 <Markdown content=".........." />
154 </p>
155 </article>
156}
157```
158
159[§2]: #real-world-pitfalls
160
161<Heading
162 level='h2'
163 slug='real-world-pitfalls'
164>Los inconvenientes del enrutador de aplicación</Heading>
165
166Tras dejar [Bun] como ingeniero de runtime (donde implementé el [empaquetado de
167componentes de servidor][Server Components bundling] y [una plantilla de
168RSC][bun-rsc]), me uní a una pequeña compañía, trabajando en la línea de fuego:
169una aplicación de Next.js con un backend de Hono. Lo siguiente es una versión
170simplificada de los problemas con los que me enfrenté tratando de mantener y
171desarrollar características nuevas. Como resultado de todos ellos, perdimos
172todos el tiempo intentando eludir fallos de diseño o explicándonos por qué algo
173que no debería ser un problema para empezar se ha convertido en un obstáculo
174inamovible.
175
176[Bun]: https://bun.com
177[Server Components bundling]: https://github.com/oven-sh/bun/blob/67f0c3e016aa479738469adac2b79a1862b88122/src/bake/bake.d.ts
178[bun-rsc]: https://github.com/oven-sh/bun/tree/e7790894d92b730758ecadf971cb935063508dfb/src/bake/bun-framework-react
179
180[§2.1]: #optimistic-updates
181
182<Heading
183 level='h3'
184 slug='optimistic-updates'
185>Las actualizaciones optimistas son imposibles</Heading>
186
187La documentación de Next.js respecto a la mutación [no menciona las
188actualizaciones optimistas][nextjs-updating-data], parece ser que no pensaron en
189este caso. Los componentes renderizados por el <b class='server'>servidor de
190React</b> no pueden ser modificados tras ser montados por diseño. Los elementos
191que puedan tener cambios deben estar dentro de un componente de cliente, pero no
192puede estos componentes no pueden dar lugar a obtención de datos, incluso
193durante el renderizado del lado del servidor (SSR) en el backend. Esto deriva en
194componentes de servidor incómodamente diminutos que sólo se encargan de obtener
195ciertos datos y tienen un componente homólogo de cliente que contiene una
196versión prácticamente estática de la página.
197
198```tsx filename="src/app/user/[username]/page.tsx" tint="server"
199
200export default async function Page() {
201 const user = await fetchUserInfo(username);
202 return <ProfileLayout>
203 <UserProfile user={user} />
204 </ProfileLayout>;
205}
206```
207```tsx filename="src/app/user/[username]/UserProfile.tsx" tint="client"
208
209"use client"; // Toca mover el código de cliente a otro archivo!
210
211export function UserProfile({ user: initialUser }) {
212 // Existen muchas librerías de gestión de estado excelentes;
213 // para simplificar, usaremos una celda de estado.
214 const [user, optimisticUpdateUser] = useState(initialUser);
215
216 async function onEdit(newUser) {
217 optimisticUpdateUser(newUser);
218 const resp = await fetch("...", {
219 method: 'POST',
220 body: JSON.stringify(newUser),
221 ... // (encabezados, credenciales, trazado y más)
222 })
223 if (!resp.ok) /* ¡recuerda siempre comprobar si se ha dado algún error! */
224 }
225
226 return <main>{/* interfaz de usuario con campos editables... */}</main>:
227}
228```
229
230A medida que más partes de la página necesitan interactividad, se vuelve más
231lioso mantener las partes estáticas puramente del lado del servidor. En la
232aplicación de trabajo, casi cada componente de la interfaz de usuario muestra
233datos dinámicos. Un [`WebSocket`][ws] sincroniza los datos en tiempo real a
234medida que se actualizan (por ejemplo, el estado en línea y el perfil básico de
235una tarjeta de usuario). Como estas configuraciones de componentes son más
236difíciles de entender y mantener para los ingenieros, casi todas nuestras
237páginas están marcadas como `"use client"` con un `page.tsx` que define la
238obtención de datos necesaria.
239
240Un ejemplo más concreto de cómo queda esto en la práctica con la librería de
241obtención de datos que usamos en el trabajo, [TanStack Query].
242
243[TanStack Query]: https://github.com/tanstack/query#readme
244
245```ts filename="src/queries/users.ts"
246// Tenemos una función auxiliar `defineQuery` para la seguridad de tipos en el trabajo.
247// Los obtenedores de datos son triviales y pueden correr tanto en el backend como en el frontend.
248export const queryUserInfo = (username) => ({
249 queryKey: ['user', username],
250 queryFn: async ({ ... }) => /* obtener datos */
251});
252```
253```tsx filename="src/app/user/[username]/page.tsx" tint="server"
254export default async function Page({ params }) {
255 const { username } = await params;
256
257 // No hay estado global en el servidor de React. Como los layouts
258 // se ejecutan en paralelo, toca reconstruir el `QueryClient` de
259 // TanStack varias veces por ruta.
260 const queryClient = new QueryClient();
261 await queryClient.ensureQueryData(queryUserInfo(username));
262
263 // HydrationBoundary es un componente de cliente que pasa datos
264 // JSON del servidor de React al componente de cliente.
265 return <HydrationBoundary state={dehydrate(queryClient)}>
266 <ClientPage />
267 </HydrationBoundary>;
268}
269```
270```tsx filename="src/app/user/[username]/ClientPage.tsx" tint="client"
271"use client";
272export function ClientPage() {
273 const { username } = useParams();
274 const { data: user } = useSuspenseQuery(queryUserInfo(username));
275
276 // ... algunos hooks
277
278 return <main>
279 {/* ... una página web interactiva */}
280 </main>;
281}
282```
283
284Este ejemplo necesita tres archivos por las reglas del empaquetado de los
285componentes de servidor (el componente de cliente necesita `"use client"` y los
286archivos de componentes de servidor no suelen poderse importar en el cliente
287debido a las importaciones exclusivas de servidor). En el enrutador de páginas
288(Pages Router), podría haberlo hecho todo en un solo archivo por el tree-shaking
289que poseen `getStaticProps` y `getServerSideProps`.
290
291[ws]: https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API
292[nextjs-updating-data]: https://nextjs.org/docs/app/getting-started/updating-data
293
294
295[§2.2]: #redundant-fetches
296
297<Heading
298 level='h3'
299 slug='redundant-fetches'
300>Cada navegación supone una petición</Heading>
301
302Como el enrutador de aplicación inicia cada página como componente de servidor,
303con áreas pequeñas de interactividad (idealmente), navegar a una nueva página
304¡*fuerza* una petición al servidor de Next.js, independientemente de los datos
305que el cliente ya tenga disponibles! Incluso con un archivo `loading.tsx`, al
306abrir `/`, navegar a `/other/ y luego volver a `/`, se mostrará el estado de
307carga mientras vuelve a obtener los datos de la página de inicio.
308
309Para lo único para lo que esto funciona bien es para el **contenido
310perfectamente estático**, donde las navegaciones instantáneas y la precarga
311(prefetching) funcionan genial. Pero **las aplicaciones web no son estáticas**,
312tienen mucho contenido dinámico. Haber iniciado sesión afecta a la página de
313inicio, cosa que es irritante porque el cliente literalmente ya tiene todo lo
314que necesita para mostrar la página instantáneamente. Ni siquiera han cambiado
315las cookies.
316
317> **nota**: Probándolo más en un proyecto en blanco, he observado casos en los
318> que el código de frontend de Next precarga rutas **sin ningún contenido
319> real**. En el ejemplo de hello world, era una carga de 1.8kB de RSC que
320> apuntaba a 2 fragmentos de JS diferentes 4 veces distintas. Esto es un desperdicio
321> puro de nuestro ancho de banda y egreso, especialmente considerando que toda
322> esta información se vuelve a obtener cuando de verdad hago clic en el enlace.
323>
324> ```json whitespace="pre-wrap"
325> 1:"$Sreact.fragment"
326> 2:I[39756,["/_next/static/chunks/ff1a16fafef87110.js","/_next/static/chunks/7dd66bdf8a7e5707.js"],"default"]
327> 3:I[37457,["/_next/static/chunks/ff1a16fafef87110.js","/_next/static/chunks/7dd66bdf8a7e5707.js"],"default"]
328> 4:I[97367,["/_next/static/chunks/ff1a16fafef87110.js","/_next/static/chunks/7dd66bdf8a7e5707.js"],"ViewportBoundary"]
329> 6:I[97367,["/_next/static/chunks/ff1a16fafef87110.js","/_next/static/chunks/7dd66bdf8a7e5707.js"],"MetadataBoundary"]
330> 7:"$Sreact.suspense"
331> 0:{"b":"TdwnOXsfOJapNex_HjHGt","f":[["children","other",["other",{"children":["__PAGE__",{}]}],["other",["$","$1","c",{"children":[null,["$","$L2",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L3",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","forbidden":"$undefined","unauthorized":"$undefined"}]]}],{"children":null},[["$","div","l",{"children":"loading..."}],[],[]],false],["$","$1","h",{"children":[null,["$","$1","KCFxAJdIDH3BlYXAHsbcVv",{"children":[["$","$L4",null,{"children":"$L5"}],["$","meta",null,{"name":"next-size-adjust","content":""}]]}],["$","$L6","KCFxAJdIDH3BlYXAHsbcVm",{"children":["$","div",null,{"hidden":true,"children":["$","$7",null,{"fallback":null,"children":"$L8"}]}]}]]}],false]],"S":false}
332> 5:[["$","meta","0",{"charSet":"utf-8"}],["$","meta","1",{"name":"viewport","content":"width=device-width, initial-scale=1"}]]
333> 9:I[27201,["/_next/static/chunks/ff1a16fafef87110.js","/_next/static/chunks/7dd66bdf8a7e5707.js"],"IconMark"]
334> 8:[["$","title","0",{"children":"Create Next App"}],["$","meta","1",{"name":"description","content":"Generated by create next app"}],["$","link","2",{"rel":"icon","href":"/favicon.ico?favicon.0b3bf435.ico","sizes":"256x256","type":"image/x-icon"}],["$","$L9","3",{}]]
335> ```
336>
337> Revisándolo, me di cuenta de que en realidad hay algo de contenido: el estado
338> de carga. ¿Lo ves?
339>
340> ```json
341> ["$","div","l",{"children":"loading..."}]
342> ```
343>
344> Sigue siendo un desperdicio grande, ya que todos estos datos se vuelven a
345> emitir en el RSC de la propia página.
346
347Parece ser que la solución a esto es [`staleTime`][nextjs-stale], pero está
348marcado como experimental y "no recomendado para producción". Es una vergüenza
349el hecho de que esto sea una opción que no está por defecto y que parece haber
350sido una idea de último momento. Incluso usándola, no es posible hacer que
351varias páginas que hacen referencia a los mismos datos los compartan.
352
353Un ejemplo de un estado de carga irrepresentable con el enrutador de aplicación
354es el de ciertas páginas, como una página de propuestas (issues) en un proyecto
355de git, al hacer clic en un nombre de usuario para ir a su página de perfil. Con
356`loading.tsx`, la página entera es un esqueleto, pero al modelar estas consultas
357con TanStack Query es posible mostrar el nombre de usuario y avatar
358instantáneamente mientras cargan la biografía del usuario y sus repositorios.
359Los componentes de servidor son incompatibles con esta tipo de navegación porque
360los datos sólo están disponibles en componentes renderizados, con lo cual deben
361ser obtenidos una vez más.
362
363[nextjs-stale]: https://nextjs.org/docs/app/api-reference/config/next-config-js/staleTimes
364[nextjs-stale-bug]: https://github.com/vercel/next.js/issues/70661
365
366En nuestro sitio Next.js, tenemos esta línea de código en nuestros obtenedores
367de datos en los componentes de servidor para hacer que las navegaciones suaves
368vayan más rápido, saltándose la fase de obtención de datos por completo.
369
370```tsx filename="src/util/tanstack-query-helpers.server.ts" tint="server"
371export function serverSidePrefetchQueries(queries) {
372 if ((await headers()).get("next-url")) {
373 // Se trata de una navegación suave. Nos SALTAMOS la precarga para
374 // aumentar la velocidad. Puede que el cliente ya tenga estos datos y,
375 // de lo contrario, tienen el estado de carga. Idealmente, no existiría
376 // esta petición -- el lado de cliente ya tiene casi TODO el código ya
377 // que la aplicación está escrita principalmente usando componentes de
378 // cliente. Un fallo de diseño de parte del enrutador de aplicación, la
379 // verdad.
380 return;
381 }
382 // ... lógica de precarga de datos ...
383}
384```
385
386Aparte, `loading.tsx` debería contener las llamadas a `useQuery` para que,
387mientras se realiza la petición para el RSC vacío, se obtengan los datos si de
388verdad se necesitan. De hecho, el estado `loading.tsx` puede ser directamente el
389componente de cliente y se mostrará la página de cliente.
390
391```tsx filename="src/app/user/[username]/loading.tsx" tint="client"
392"use client";
393export default function PageLoadingSkeleton() {
394 return <ClientPage />;
395}
396```
397
398> En el trabajo, simplemente hacemos que nuestros archivos `loading.tsx`
399> contengan las llamadas a `useQuery` y muestren un esqueleto. Esto se debe a
400> que cuando Next.js carga el componente de servidor en sí, remonta la página
401> entera sí o sí. No se da ningún tipo de VDOM diffing, así que todos los hooks
402> (`useState`) se reiniciarán un poco después de que la petición se complete.
403> Intenté reproducir un caso simple en el que estaba *suplicándole* a Next.js
404> que tan solo *actualizara el DOM existente* y preservara el estado, pero
405> directamente no lo hace. Por suerte, el tiempo que tarda la llamada RSC en
406> blanco es suficientemente corto.
407
408[§2.3]: #layout-restrictions
409
410<Heading
411 level='h3'
412 slug='layout-restrictions'
413>Los layouts tienen restricciones artificiales</Heading>
414
415Los layouts pueden obtener datos, pero no pueden observar o alterar la petición
416de ninguna manera. Está hecho así para que Next.js pueda obtener y cachear los
417layouts cada vez que quiera. En cualquier otro framework, los layouts son
418componentes normales sin diferencia de otros componentes en la página.
419
420Obtener los layouts por sí mismos es una idea chula, pero acaba siendo estúpida,
421pues toda obtención de datos debe rehacerse para cada layout. No puedes
422compartir un `QueryClient`, en su lugar debes usar su
423[`fetch` _monkey-patched_][nextjs-fetch] para cachear la misma petición `GET`
424tal y como prometen.
425
426Cuando me pregunta un compañero de trabajo por qué Next.js rechaza código, ya ni
427intento explicar los entresijos, directamente digo *"Es un problema de habilidad
428de Next.js, no te preocupes, que le voy a dar fuego pronto"*. Estas reglas son
429demasiado difíciles para que un desarrollador promedio las entienda.
430
431[nextjs-fetch]: https://nextjs.org/docs/app/api-reference/functions/fetch
432
433[§2.4]: #rsc-payload
434
435<Heading
436 level='h3'
437 slug='rsc-payload'
438>Te llevas todo el contenido dos veces de todas formas</Heading>
439
440A diferencia de la ["arquitectura de islas"][islands], los componentes de
441servidor deben ser hidratados en el frontend para poder emplear `Suspense` y
442mantener el estado de los componentes de cliente. Al hacer navegaciones suaves,
443se obtiene mediante `fetch` la "carga de RSC" (que no es HTML en absoluto).
444Al hacer una recarga fresca de la página, se necesita HTML para
445[pintar por primera vez][first paint], pero la información sobre los componentes
446de cliente y `Suspense` no está en dicho HTML. La solución de React es **enviar
447una segunda copia del _markup_ de la página entera**. Un ejemplo de lo que
448enviaría un servidor de producción de Next.js en un renderizado dinámico de una
449página sería algo así:
450
451[first paint]: https://web.dev/articles/fcp
452
453```html filename="GET /user/clover"
454<!DOCTYPE html>
455<html>
456<head>
457 {etiquetas link y meta}
458</head>
459<body>
460 {renderizado del lado del servidor}
461 <script>
462 // un script de inicialización que inicia `__next_f`
463 // como un arreglo. en cuanto carga React, la función
464 // `.push` se modifica para escribir nuevos fragmentos
465 // al decodificador de RSC directamente. tiene algunos
466 // auxiliares de DOM también
467 (self.__next_f=self.__next_f||[]).push([0])
468 </script>
469 <script>
470 // la carga de RSC para el shell de la aplicación.
471 self.__next_f.push([1,"1:\"$Sreact.fragment\"\n2:I[658993,[\"/_next/st{...}"])
472 </script>
473
474 <!--
475 NO está escrita todavía la etiqueta </body>, ya que hay una
476 frontera de Suspense sin resolver. con el paso del tiempo,
477 se escriben más datos.
478 -->
479 <div class="user-post-list">
480 {renderizado del lado del servidor de una frontera de Suspense}
481 </div>
482 <script>
483 // la carga de RSC para la frontera de Suspense
484 self.__next_f.push([2,"14:[\"$\",\"div\",null,{\"children\":[[\"$\",\"h4\"{...}"])
485 </script>
486
487 <!-- se repiten etiquetas HTML y script hasta que la página acaba -->
488</body>
489</html>
490```
491
492Esta solución **duplica el tamaño de la carga inicial de HTML**. Aún peor, la
493carga de RSC incluye JSON en cadenas de texto de JS, un formato mucho menos
494eficiente que HTML. Aunque parece comprimirse bien con brotli y renderizarse
495rápido en el navegador, esto es un desperdicio. Con el patrón de hidratación,
496los datos se podrían reutilizar localmente, por lo menos, para la interactividad
497y otras páginas.
498
499Incluso en páginas con ínfima interactividad, pagas el precio. Como ejemplo, la
500documentación de Next.js, al cargar su [página de
501inicio](https://nextjs.org/docs), carga una página de alrededor de 750kB (250kB
502de HTML y 500kB de etiquetas script), y el contenido aparece dos veces.
503
504Puedes comprobarlo pulsando <kbd>Cmd</kbd> + <kbd>Opt</kbd> + <kbd>u</kbd> en
505Mac o <kbd>Ctrl</kbd> + <kbd>u</kbd> en otras plataformas y después
506<kbd>Cmd</kbd> / <kbd>Ctrl</kbd> + <kbd>f</kbd> para ubicar cualquier cadena
507en el blog, como "construyendo aplicaciones web full-stack". Aparece dos veces.
508Y **es inevitable**, ya que es una parte fundamental de los componentes de
509servidor de React.
510
511El formato de RSC definitivamente tiene más fallas, pero no tengo ganas de
512ponerme a investigar por qué la cadena `/_next/static/chunks/6192a3719cda7dcc.js`
513aparece 27 veces. ¿Qué diablos? ¿¿¿Dais el ancho de banda por gratis???
514
515[islands]: https://www.patterns.dev/vanilla/islands-architecture/
516
517[§2.5]: #turbopack
518
519<Heading
520 level='h3'
521 slug='turbopack'
522>Turbopack da asco</Heading>
523
524Esta sección no es constructiva.
525
526- Turbopack no es ágil
527- Turbopack emite código difícil de depurar (en modo de desarrollo)
528- Turbopack produce mensajes de error malos en muchos casos
529
530Normalmente no le habría dado una sección en el blog a este punto, pero quiero
531mostrar tres ejemplos de verdad, directamente extraídos del proyecto.
532
533El primero es un lugar donde, al refactorizar código para satisfacer los modelos
534de componente de servidor/cliente, hice asíncrono un componente de cliente. Era
535irritante porque no indicaba dónde estaba el error, sólo contenía el stack trace
536del <b class='server'>servidor</b>.
537
538![Next.js error](/file/2025/blog-everyone-hates-nextjs/asyncerror.png)
539
540Otro ejemplo de un error horrible:
541
542![Next.js error](/file/2025/blog-everyone-hates-nextjs/nexterror.png)
543
544> Tras arreglar el problema detrás de este segundo error (que ni siquiera recuerdo),
545> el servidor de desarrollo se quedó colgado y tuve que reiniciarlo para que se recuperara.
546
547El último caso es el millón de veces que he puesto un _breakpoint_ (punto de
548interrupción) en el depurador y la variable `hola` se convierte en
549`__TURBOPACK__imported__module__$5b$project$5d2f$client$2f$src$2f$utils$2f$filename$2e$ts__$5b$app$2d$client$5d$__$28$ecmascript$29$__["hola"]`
550y más mierda.
551
552Vale. Todo esto da asco. ¿Qué podemos hacer?
553
554[§3]: #ditching-nextjs
555
556<Heading
557 level='h2'
558 slug='ditching-nextjs'
559>Dejando atrás Next.js y Vercel en el trabajo</Heading>
560
561Hay dos tipos de proyectos web:
562
563- Una página web con contenido mayoritariamente estático.
564- Una aplicación web con componentes mayormente dinámicos e interactivos.
565
566Y Next.js es la herramienta equivocada para ambos. Si quieres una página web
567estática, usa [Astro] o [Fresh]. Para quienes necesiten la potencia de React,
568esta sección trata cómo pasé de estar atado a Next a [TanStack Start], de forma
569incremental y sin baches.
570
571[Astro]: https://astro.build/
572[Fresh]: https://fresh.deno.dev/
573[TanStack Start]: https://tanstack.com/start/latest
574
575Todo empezó con esta configuración de Vite.
576
577```ts filename="vite.config.ts"
578const config = defineConfig(({ mode }) => {
579 const env = loadEnv(mode, process.cwd(), "NEXT_PUBLIC_");
580 return {
581 // Usa el puerto predeterminado de Next.js, 3000
582 server: { port: 3000 },
583 // Usa el prefijo de variables de entorno predeterminado de Next.js, "NEXT_PUBLIC_"
584 define: Object.fromEntries(Object.entries(env).map(
585 ([k, v]) => [`process.env.${k}`, JSON.stringify(v)])),
586 plugins: [
587 viteTsConfigPaths({ projects: ["./tsconfig.json"] }),
588 tailwindcss(),
589 // Para que mis compañeros de trabajo lo entendieran bien, empecé
590 // a portar las rutas en `src/tanstack-routes`; en cuanto acabara,
591 // volvería a cambiarlo a `src/routes`, como estaba por defecto.
592 tanstackStart({
593 router: { routesDirectory: "src/tanstack-routes" },
594 }),
595 viteReact(),
596 ],
597 resolve: {
598 // La clave para la migración incremental: redirigir `next` a otro lugar
599 alias: { next: path.resolve("./src/tanstack-next/") },
600 conditions: ["tanstack"],
601 extensions: [
602 // Permitir que un archivo como `utils/session.tanstack.ts`
603 // sobreescriba a `utils/session.ts` al ser importado.
604 ".tanstack.tsx", ".tanstack.ts",
605 // Extensiones de importación predeterminadas
606 ".mjs", ".js", ".mts", ".ts",
607 ".jsx", ".tsx", ".json",
608 ],
609 },
610 };
611});
612```
613
614Después, me puse a buscar cada uso de una API de Next.js, para o eliminarlo o
615crear un talón (_stub_) para TanStack. Por ejemplo, `src/tanstack-next/link.tsx`
616implementa `next/link`:
617
618```tsx filename="src/tanstack-next/link.tsx"
619import { Link } from "@tanstack/react-router";
620import type { LinkProps } from "next/link";
621
622export default function LinkAdapter({ href, ...rest }: LinkProps) {
623 return <Link {...rest} to={href as unknown as any} />;
624}
625```
626
627> Algunos de estos talones pueden ser extremadamente simples. Al principio, mi
628> implementación de `useRouter` era `return {}`. Más tarde, ya tuve que añadir
629> algunos métodos al objeto. El código no tiene por qué ser limpio, ya que es
630> temporal.
631
632De ahí, el sitio nuevo puede importar casi cada componente de cliente o creando
633talones para las APIs de Next.js que necesite o usando la extensión `.tanstack.ts`
634para reimplementar la lógica archivo por archivo. Poco después, conseguí que
635la página principal del sitio funcionara en TanStack Start e hicimos _merge_.
636
637![Mi PR (solicitud de incorporación de cambios) "nextgate"](/file/2025/blog-everyone-hates-nextjs/pr.png)
638
639> En esta primera PR (solicitud de incorporación de cambios) sólo funcionaba una
640> de nuestras páginas, y conseguí que funcionara con unas mil líneas de código
641> añadido y 40 líneas eliminadas. Algunos parches previos eliminaban los pocos
642> usos de `next/image` y `next/font`.
643
644Lo que quedaba era portar el resto de rutas. Lo único que perdemos al migrar de
645Next.js a cualquier otro framework es el poder usar `await` con funciones de
646obtención de datos en la interfaz de usuario. En la práctica, mover cada ruta
647a una función `loader` esclareció qué era lo que pasaba al renderizar cada
648página desde el servidor (SSR). Para páginas con varias llamadas de obtención de
649datos, estas podían combinarse en una llamada API especial que devolviera todos
650los datos relevantes a ellas.
651
652Para reiterar, en negrita: <strong style='color:var(--secondary)'>El camino de
653migración de componentes de servidor es simplificar tu código &mdash; RSC
654intrínsecamente te lleva por un camino caótico repleto de cosas
655innecesarias</strong>. Casi todas las partes complejas de nuestro sitio se
656volvieron más fáciles de entender para todos nuestros ingenieros. La única
657excepción fue acostumbrar a todos a las nuevas convenciones para el enrutado
658del sistema de archivos. Con suficientes ejemplos, todos lo acabamos pillando.
659
660Con la migración incremental ya operativa, no se rompía el _deployment_ existente
661al añadir código nuevo. TanStack fue apoderándose del código y, con el tiempo,
662fuimos eliminando todos los talones de Next.js y ganamos todas las maravillosas
663[características de seguridad de tipos][type-safety features] que ofrece el
664enrutador de TanStack (TanStack Router). Al final, el sitio rendía más rápido en
665todos los aspectos: el modo de desarrollo, tiempos de carga en producción,
666navegaciones suaves, y todo a un menor coste que nuestro _deployment_ de Next con
667Vercel.
668
669[type-safety features]: https://tanstack.com/router/v1/docs/framework/react/guide/type-safety
670
671No somos los únicos sintiendo el cambio. Aunque intento evitar las redes
672sociales, alguien me envió [los resultados del trabajo de Brian Anglin en
673Superwall][superwall-twitter], mostrando reducciones de CPU increíbles usando
674TanStack Start. También recuerdo el cambio de ChatGPT de Next.js a Remix hace un
675año (conversaciones relevantes: [[1][chatgpt-1]] [[2][chatgpt-2]] [[3][chatgpt-3]]).
676
677[superwall-twitter]: https://twitter.com/BriansAngles/status/1978834116079436242#m
678[chatgpt-1]: https://xcancel.com/ryanflorence/status/1831379475654947233
679[chatgpt-2]: https://old.reddit.com/r/reactjs/comments/1f97zgr/chatgpt_migrates_from_nextjs_to_remix
680[chatgpt-3]: https://old.reddit.com/r/nextjs/comments/1f92jdv/chatgptcom_switched_from_nextjs_to_remix
681
682[§3.1]: #next-metadata
683
684<Heading
685 level='h3'
686 slug='next-metadata'
687><code>next/metadata</code> es maravilloso</Heading>
688
689Bajo mi punto de vista, esta es una de las pocas APIs buenas que tiene Next.js,
690y fue el único lugar en nuestro código en el que el cambio a TanStack resultó en
691mayor dificultad. En vez de empeorar el código, porté su API de metadatos a una
692función normal y corriente, para que todos la pudieran usar. Solía tener un
693puerto 1:1 en NPM, pero a comienzos del año simplifiqué su API a un archivo
694pequeño y comprensible. En el momento de escribir esta publicación, he añadido
695una API `meta.toTags` compatible con TanStack que puede instalarse desde
696[JSR][lib-jsr] o [NPM][lib-npm], o simplemente la puedes copiar a tu proyecto.
697
698> **aviso**: Por limitaciones de tiempo para escribir este artículo, la librería
699> todavía no está actualizada. Probablemente lo haré ~~para el final de esta
700> semana (24 de octubre)~~ pronto... Por ahora, puedo compartir la versión que
701> uso en el trabajo:
702> [`meta.tanstack.ts`](https://paperclover.net/file/2025/blog-everyone-hates-nextjs/meta.tanstack.ts).
703
704```tsx
705// una vez en tu proyecto
706import * as meta from "@clo/lib/meta.ts";
707
708export const defineHead = meta.toTags.bind(null, {
709 // opciones para todo el sitio
710 base: new URL("https://paperclover.net"),
711 titleTemplate: (title) => [title, "paper clover"]
712 .filter(Boolean).join(' | '),
713 // ...
714});
715
716// para cada página...
717export const Route = createFileRoute("/blog")({
718 head: () =>
719 defineHead({
720 title: "blog de clover", // usando la plantilla `titleTemplate`
721 description: "una gatita maúlla sobre sus opiniones tecnológicas",
722 canonical: "/blog", // yuxtapuesto a `base`
723
724 // Cuando se especifica, configura el embed (embebido)
725 // de Open Graph y Twitter, usando el título y la
726 // descripción de la página por defecto.
727 // La configuración predeterminada está bien,
728 // pero se pueden especificar más opciones.
729 embed: {
730 image: "/img/blog.webp",
731 },
732
733 // Todas las etiquetas meta exóticas se hacen con un fragmento
734 // JSX. No se renderiza React, sólo se itera sobre las etiquetas.
735 // Mi objetivo era cubrir el 99% de los casos comunes.
736 extra: <>
737 <meta name="site-verification" content="waffles" />,
738 </>,
739 }),
740
741 component: Page,
742});
743
744function Page() {
745 ...
746}
747```
748
749Mi versión no se preocupaba con cubrir todas las posibilidades del objeto de
750metadatos de Next.js; usa JSX en línea (_inline_) para llenar ese hueco.
751
752[lib-jsr]: https://jsr.io/@clo/lib
753[lib-npm]: https://npmjs.com/@paperclover/lib
754
755[§3.2]: #vercel-og
756
757<Heading
758 level='h3'
759 slug='ditching-nextjs'
760><code>next/og</code> está bien también</Heading>
761
762No tengo una opinión fuerte al respecto. Sólo quiero recordar a todos que existe
763el paquete `@vercel/og`.
764
765[§4]: #experience-feels-like-the-usual
766
767<Heading
768 level='h2'
769 slug='experience-feels-like-the-usual'
770>Mi experiencia parece ser la típica</Heading>
771
772En la Next.js Conf 2024, todos hablaban maravillas de los componentes de
773servidor. No recuerdo exactamente con quién fue que hablé, pero todos los
774grandes nombres estaban completamente a favor de ellos. Habiendo implementado el
775empaquetador de RSC, yo vi algunos de los problemas en el formato. Ahora, viendo
776que Next 15 "estabilizó" el enrutador de aplicaciones el año pasado, muchas
777compañías están construyendo productos con él, dándose cuenta de estos
778inconvenientes de primera mano.
779
780Llegué tarde al mundo de Next.js, pues empecé en junio con la versión 15. Pero
781todos con los que he hablado están de acuerdo con mis observaciones. Todos con
782los que hablé en la 1.3 Party de Bun estaban de acuerdo conmigo. Incluso gente
783de Vercel me ha dicho que no les gusta cómo es usar Next.js.
784
785Espero que, al estabilizarse TanStack Start, se vuelva el nuevo sustituto de
786Next.js que todos quieran.
787
788[§5]: #prefer-respectful-tools
789
790<Heading
791 level='h2'
792 slug='prefer-respectful-tools'
793>Opta por herramientas que te respeten</Heading>
794
795El ecosistema de JavaScript es un desastre y ese desastre es por el que la gente
796se burla del desarrollo web. Muchas veces he pensado que el desastre de trabajar
797en la web no tenía solución, pero el desastre eran en verdad las librerías
798comúnmente usadas de las que me rodeaba. Al quitar esa capa, las tecnologías de
799desarrollo web modernas son maravillosas.
800
801Llevo desarrollando esta página web desde cero, sin framework, desde finales de
8022024, implementando sistemas como mi propio [widget de progreso para
803aplicaciones de terminal][progress], [proxy para archivos
804estáticos][file-cache], sistema de compilación incremental, y muchos más
805componentes. Trabajar en este código ha llevado a las mejores sesiones de código
806(en términos de felicidad) en años. Los visitantes de *[paper clover]* se llevan
807una página web de mejor calidad; las minilibrerías que he creado las [extraigo
808para uso público][lib], todos salen ganando.
809
810Este nivel de "desde cero" es demasiado para la mayoría, especialmente en el
811trabajo. Diría que, como mínimo, deberíamos dedicar atención y dinero sólo a
812herramientas de alta calidad que nos respeten. Y Next.js y la empresa detrás de
813ello, Vercel, no caen dentro de ese criterio.
814
815Si usas Next.js y sientes que tu experiencia tampoco te recuerda al respeto,
816reflexiona si tus compañeros de trabajo y tú queréis seguir apoyando su [imperio
817serverless][serverless empire]. El ecosistema de Vite parece bastante decente
818para desarrollar ahora mismo, pero no tengo mucha experiencia todavía en usar
819sus herramientas a gran escala en producción. El [lanzamiento de Vite+ de
820Void0][vite-plus] parece interesante, pero sólo el tiempo dirá si estas
821herramientas, financiadas por capital, nos respetarán (a los usuarios y a los
822desarrolladores) a largo plazo.
823
824La Next.js Conf 2025, en el momento de escribir, es [mañana][next-conf]. En vez
825de comprarme una entrada de $800, decidí destinar ese dinero al [equipo de
826TanStack][tanstack-donate] por [respetar y mejorar el ecosistema de desarrollo
827web][tanstack-ethos].
828
829[paper clover]: https://paperclover.net/
830[lib]: https://git.paperclover.net/clo/sitegen/src/branch/master/lib#readme
831[progress]: https://git.paperclover.net/clo/sitegen/src/branch/master/lib/progress.ts
832[file-cache]: https://git.paperclover.net/clo/sitegen/src/branch/master/src/file-viewer/cache.ts
833
834[next-conf]: https://nextjs.org/conf
835[vite-plus]: https://viteplus.dev/
836[tanstack-ethos]: https://tanstack.com/ethos
837[tanstack-donate]: https://github.com/sponsors/tannerlinsley
838[serverless empire]: https://youtu.be/SCIfWhAheVw
839
840## Lo que aguarda el futuro
841
842Paulatinamente, he estado reemplazando mucho software que me falta al respeto
843con mejores alternativas. Algunos ejemplos son GitHub, Visual Studio Code,
844DaVinci Resolve, Discord, Google Drive/Workspace, entre muchos otros. Pienso
845escribir más en este blog sobre mis proyectos técnicos (la librería de progreso,
846el propósito de mi generador de sitio propio, aprendizajes de mi trabajo
847actual), incluyendo algunos de mis proyectos pasados en Bun (detalles sobre HMR,
848el sistema de reporte de errores y la locura de sistema para empaquetar módulos
849integrados). Si te interesa, por favor suscríbete a la lista de correos:
850
851<a href="mailto:subscribe@paperclover.net?subject=paper%20clover%20mailing%20list&body=I%20would%20like%20to%20be%20subscribed%20to%20the%20following%20mailing%20lists%3A%0A%0A-%20Technical%20Blog%20Posts%20-%20YES%0A-%20Art%20(Original%20Music%2FVideo)%20-%20YES%0A%0A(feel%20free%20to%20write%20whatever%20else%20you%20want)">haz clic aquí para enviar un correo a <code>subscribe@paperclover.net</code>, pidiendo ser añadido a la lista de correos.</a> (llevo esta lista de correos a mano)
852
853[volver al inicio](#top) &mdash; [pregúntame algo sobre este artículo](/q+a)
854
855<br />
856<br />
857<br />
858<br />
859<br />
860<br />
861<footer>
8622025 (c) paper clover
863</footer>
864
865</Layout>
866
867<br />
868
src/blog/pages/webdev/one-year-next-app-router/ko.mdo created+541
......@@ -0,0 +1,541 @@
1---
2layout: "@/blog/tags/blog-layout.marko"
3meta:
4 title: Next.js 앱 라우터와 함께한 1년 — 우리가 떠나기로 한 이유
5 description: 리액트 서버 컴포넌트와 Next.js 15에 대한 비판
6 keywords: ["webdev", "technical analysis", "opinion"]
7 authors: ["clover caruso", "Chanhee Kim"]
8 embed:
9 thumbnail: "/open-graph/next-js.ko.png"
10 canonical: "/blog/webdev/one-year-next-app-router.ko"
11date: "Oct 21st, 2025"
12translation:
13 lang: "ko"
14 author: "Chanhee Kim"
15 href: "https:/\/substack.com/@chanheekim377573"
16 date: "Dec 8th, 2025"
17---
18
19직장에서 웹 앱 개발에 Next.js를 전문적으로 사용해오면서, 앱 라우터와 [리액트 서버 컴포넌트(React Server Components, RSC)][rsc]의 핵심 설계가 매우 답답하게 느껴졌습니다. 사소한 버그나 API의 혼란스러움이 아니라, Vercel과 리액트 팀이 이를 구축할 때 내린 근본적인 설계 결정에 대한 큰 이견이 있기 때문입니다.
20
21웹 개발 행사에 갈때마다 Next.js를 싫어함에도 계속 사용해야 하는 사람들을 더 많이 보게 됩니다. 이 글의 마지막에는 저와 동료들이 어떻게 이 지옥에서 탈출하여 전체 프론트엔드를 [TanStack Start]로 원활하게 마이그레이션했는지 공유하겠습니다.
22
23[TanStack Start]: https://tanstack.com/start/latest
24[rsc]: https://react.dev/reference/rsc/server-components
25
26<table-of-contents />
27
28<h2#technical-review>기술 리뷰: 서버 컴포넌트란 무엇인가요?</h2>
29
30RSC의 핵심은 컴포넌트를 <b class='server'>"서버"</b> 컴포넌트와 <b class='client'>"클라이언트"</b> 컴포넌트 두 가지 범주로 분류한다는 점입니다. 서버 컴포넌트는 `useState`나 `useEffect`를 사용하지 않지만, `async function`일 수 있으며 데이터베이스에 직접 호출하는 등 백엔드 도구를 참조할 수 있습니다. 클라이언트 컴포넌트는 기존 모델로, 백엔드에서 HTML 텍스트를 생성하는 코드와 `window.document.*`를 사용하여 DOM을 관리하는 프론트엔드 코드가 존재합니다.
31
32> 첫 번째 재앙: 명명법!! 리액트는 이제 기존 정의를 무시하고 <b class='server'>"서버"</b>와 <b class='client'>"클라이언트"</b>라는 단어를 매우 특정한 개념을 가리키는 데 사용하고 있습니다. <b class='client'>클라이언트</b> 컴포넌트도 백엔드에서 실행될 수 있다는 점을 제외하면 괜찮을 텐데요! 이 글에서는 웹 앱이 존재하는 두 가지 실행 환경, 즉 Node.js 프로세스와 웹 브라우저를 각각 설명하기 위해 <b>"백엔드"</b>와 <b>"프론트엔드"</b>라는 용어를 사용할 것입니다.
33
34이 <b class='server'>"서버"</b>/<b class='client'>"클라이언트"</b> 컴포넌트 모델은 흥미롭습니다. `<Suspense />` 같은 내장 컴포넌트가 네트워크를 통해 직렬화되기 때문에, 비동기 <b class='server'>서버 컴포넌트</b>로 데이터 가져오기를 아주 간단하게 모델링할 수 있으며, 폴백 UI는 마치 클라이언트 측에서 작동하는 것처럼 동작합니다.
35
36```tsx filename="src/app/[username]/page.tsx" tint="server"
37// 이 글에서 서버 컴포넌트는 빨간색으로 강조 표시됩니다.
38export default async function Page({ params }) {
39 // Page 매개변수는 해결된 프로미스로 제공됩니다
40 const { username } = await params;
41
42 // `UserInfo` 및 `UserPostList` 컴포넌트는 동시에 실행됩니다.
43 // `UserInfo`가 준비되면, 방문자는 게시물 목록이 아직 준비되지 않은 경우
44 // `PostListSkeleton`이 포함된 페이지를 보게 됩니다.
45 return <main>
46 <UserInfo username={username} />
47
48 <Suspense fallback={<PostListSkeleton />}>
49 <UserPostList username={username} />
50 </Suspense>
51 </main>
52}
53
54// 워터폴은 여러 컴포넌트를 동시에 평가함으로써 방지됩니다.
55
56async function UserInfo({ username }) {
57 const user = await fetchUserInfo(username);
58 return <>
59 <h1>{user.displayName}</h1>
60 {user.bio ? <Markdown content={user.bio} /> : ""}
61 </>
62}
63
64async function UserPostList({ username }) {
65 const posts = await fetchUserPostList(username);
66 return /* post list ui omitted for brevity */;
67}
68```
69
70리액트 자체의 40kB gzip 압축 번들을 제외하면, 위 예시는 UI와 데이터 페칭을 위한 자바스크립트가 전혀 없습니다. 단순히 마크업을 스트리밍할 뿐이죠! 예를 들어, `<Markdown />` 컴포넌트 내부의 가상 마크다운 파서는 백엔드에 그대로 남아 있습니다. 인터랙티브한 프론트엔드가 필요할 때는, "use client"로 시작하는 파일에 컴포넌트를 배치하여 <b class='client'>클라이언트 컴포넌트</b>를 만들 수 있습니다.
71
72```tsx filename="src/components/CopyButton.tsx" tint="client"
73"use client"; // 이 주석은 파일이 클라이언트 사이드로 번들링 되도록 마킹합니다.
74
75export function CopyButton({ url }) {
76 return <>
77 <span>{url}</span>
78 <button onClick={() => {
79 const full = new URL(url, location.href);
80 navigator.clipboard.writeText(full.href);
81 // 에러 처리나 성공시 보여주는 ui는 제외했습니다
82 }}>copy</button>
83 </>
84}
85```
86```tsx filename="src/app/q+a/Card.tsx" tint="server"
87export function Card() {
88 return <article>
89 <header>
90 {/* 브라우저가 CopyButton을 import 하도록 합니다 */}
91 <CopyButton url="/q+a/2506010139" />
92 </header>
93 <p>
94 {/* 마크다운 처리는 백엔드에서 수행됩니다 */}
95 <Markdown content=".........." />
96 </p>
97 </article>
98}
99```
100
101
102<h2#real-world-pitfalls>실제로 발생하는 앱 라우터의 문제점들</h2>
103
104런타임 엔지니어로 근무하던 [Bun]을 그만둔 후([서버 컴포넌트 번들링]과 [RSC 템플릿][bun-rsc]을 구현했습니다), 저는 최전선에서 일하는 소규모 회사에 합류했습니다. Hono 백엔드를 가진 Next.js 애플리케이션이었습니다. 다음 내용들은 실제 현장에서 유지보수 및 신규 기능 개발 시 마주친 문제들을 단순화한 것입니다. 이 모든 것들의 결과로 인해, 모두가 설계상의 결함을 우회하거나, 당연히 해결되어야 할 문제가 왜 해결 불가능한 장애물이 되었는지 서로 설명하는 데 시간을 낭비하게 되었습니다.
105
106[Bun]: https://bun.com
107[서버 컴포넌트 번들링]: https://github.com/oven-sh/bun/blob/67f0c3e016aa479738469adac2b79a1862b88122/src/bake/bake.d.ts
108[bun-rsc]: https://github.com/oven-sh/bun/tree/e7790894d92b730758ecadf971cb935063508dfb/src/bake/bun-framework-react
109
110
111<h3#optimistic-updates>낙관적 업데이트는 불가능합니다</h3>
112
113Next.js 문서에는 변경 수행 시 [낙관적 업데이트에 대한 언급이 없습니다][nextjs-updating-data]. 이 경우를 고려하지 않은 것으로 보입니다. <b class='server'>리액트 서버</b>에서 렌더링되는 컴포넌트는 설계상 마운팅 후 수정할 수 없습니다. 변경될 수 있는 요소는 클라이언트 컴포넌트 내에 있어야 하지만, 백엔드에서 SSR(서버 측 렌더링) 중에도 클라이언트 컴포넌트에서 데이터 가져오기가 불가능합니다. 이로 인해 데이터 가져오기만 수행하는 어색하게 작은 서버 컴포넌트와, 대부분 정적인 버전의 페이지를 포함하는 클라이언트 컴포넌트가 분리되어 있는 구조가 됩니다.
114
115[nextjs-updating-data]: https://nextjs.org/docs/app/getting-started/updating-data
116
117```tsx filename="src/app/user/[username]/page.tsx" tint="server"
118export default async function Page() {
119 const user = await fetchUserInfo(username);
120 return <ProfileLayout>
121 <UserProfile user={user} />
122 </ProfileLayout>;
123}
124```
125```tsx filename="src/app/user/[username]/UserProfile.tsx" tint="client"
126"use client"; // 클라이언트 코드를 반드시 두 번째 파일로 분리해야 합니다!
127
128export function UserProfile({ user: initialUser }) {
129 // 훌륭한 상태 관리 라이브러리들이 많이 존재합니다.
130 // 단순화를 위해 이 예제에서는 하나의 상태 셀을 사용하겠습니다.
131 const [user, optimisticUpdateUser] = useState(initialUser);
132
133 async function onEdit(newUser) {
134 optimisticUpdateUser(newUser);
135 const resp = await fetch("...", {
136 method: 'POST',
137 body: JSON.stringify(newUser),
138 ... // (헤더, 자격 증명, 추적 등)
139 })
140 if (!resp.ok) /* 항상 오류 검사를 잊지 마세요! */
141 }
142
143 return <main>{/* 편집 가능한 필드가 있는 사용자 인터페이스... */}</main>:
144}
145```
146
147페이지의 상호작용 요소가 늘어날수록 정적 부분을 서버 측에서 완전히 처리하기 복잡해집니다. 업무용 앱에서는 거의 모든 UI 요소가 동적 데이터를 표시합니다. [`WebSocket`][ws]은 데이터가 업데이트될 때 실시간으로 동기화합니다(예: 사용자 카드의 온라인 상태와 기본 프로필 정보). 이러한 컴포넌트 설정은 엔지니어가 이해하고 유지하기 어렵기 때문에, 거의 모든 페이지가 데이터 가져오기를 정의하는 `page.tsx`를 통해 완전히 `"use client` 방식으로 구현됩니다.
148
149[ws]: https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API
150
151직장에서 사용하는 데이터 페칭 라이브러리인 [TanStack Query](https://github.com/tanstack/query#readme)를 통해 실제 적용 사례를 보다 구체적으로 살펴보겠습니다.
152
153```ts filename="src/queries/users.ts"
154// 작업 시 타입 안전성을 위해 `defineQuery` 헬퍼 함수가 사용됩니다.
155// 페처는 단순하며 백엔드나 프론트엔드에서 실행될 수 있습니다.
156export const queryUserInfo = (username) => ({
157 queryKey: ['user', username],
158 queryFn: async ({ ... }) => /* fetch data */
159});
160```
161```tsx filename="src/app/user/[username]/page.tsx" tint="server"
162export default async function Page({ params }) {
163 const { username } = await params;
164
165 // 리액트 서버에는 글로벌 상태가 없습니다.
166 // 레이아웃이 병렬로 실행되기 때문에 TanStack `QueryClient`는 경로 마다 여러 번 재구성되어야 합니다.
167 const queryClient = new QueryClient();
168 await queryClient.ensureQueryData(queryUserInfo(username));
169
170 // HydrationBoundary는 리액트 서버에서 클라이언트 컴포넌트로
171 // JSON 데이터를 전달하는 클라이언트 컴포넌트입니다.
172 return <HydrationBoundary state={dehydrate(queryClient)}>
173 <ClientPage />
174 </HydrationBoundary>;
175}
176```
177```tsx filename="src/app/user/[username]/ClientPage.tsx" tint="client"
178"use client";
179export function ClientPage() {
180 const { username } = useParams();
181 const { data: user } = useSuspenseQuery(queryUserInfo(username));
182
183 // ... 이외의 다른 훅들
184
185 return <main>
186 {/* ... 인터랙티브 웹 페이지 */}
187 </main>;
188}
189```
190
191이 예제는 서버 컴포넌트 번들링 규칙 때문에 반드시 세 개의 별도 파일로 구성되어야 합니다. (클라이언트 컴포넌트는 `"use client"`가 필요하며, 서버 전용 임포트 때문에 서버 컴포넌트 파일은 클라이언트에서 종종 임포트할 수 없습니다.) Pages 라우터에서는 `getStaticProps`와 `getServerSideProps`가 트리 셰이킹을 지원하기 때문에 단일 파일로 구현할 수 있었습니다.
192
193
194<h3#redundant-fetches>모든 탐색(navigation)은 또 다른 페치 요청입니다</h3>
195
196앱 라우터는 모든 페이지를 서버 컴포넌트로 시작하며 이상적으로는 상호작용 영역이 작기 때문에, 새 페이지로 이동할 때는 클라이언트가 이미 보유한 데이터와 무관하게 Next.js 서버를 다시 호출해야 합니다! `loading.tsx` 파일이 있더라도, `/`를 열고 `/other`로 이동한 후 다시 `/`로 돌아오면 홈페이지를 재로딩하는 동안 로딩 상태가 표시됩니다.
197
198이 방법이 통하는 유일한 경우는 <b>완벽히 정적인 콘텐츠</b>일 때로, 이때는 즉각적인 탐색과 사전 로딩이 훌륭하게 작동합니다. 하지만 <b>웹 애플리케이션은 정적이지 않고</b> 다량의 동적 콘텐츠를 포함합니다. 클라이언트가 페이지를 즉시 표시하는 데 필요한 모든 것을 이미 가지고 있음에도 불구하고, 로그인 상태가 홈페이지를 변경시키는 것은 정말 짜증나는 일입니다. 쿠키가 변경된 것도 아닌데 말이죠.
199
200> 참고: 빈 프로젝트에서 추가 테스트를 진행한 결과, Next 프론트엔드 코드가 실제 콘텐츠 없이 경로를 미리 가져오는 사례를 관찰했습니다. 'Hello World' 예제에서는 1.8kB 크기의 RSC 페이로드가 2개의 서로 다른 JS 청크를 4번에 걸쳐 가리켰습니다. 이는 순전히 대역폭과 아웃바운드 트래픽을 낭비하는 행위입니다. 특히 링크를 실제로 클릭할 때 이 모든 정보를 다시 가져온다는 점을 고려하면 더욱 그렇습니다.
201>
202> ```json whitespace="pre-wrap"
203> 1:"$Sreact.fragment"
204> 2:I[39756,["/_next/static/chunks/ff1a16fafef87110.js","/_next/static/chunks/7dd66bdf8a7e5707.js"],"default"]
205> 3:I[37457,["/_next/static/chunks/ff1a16fafef87110.js","/_next/static/chunks/7dd66bdf8a7e5707.js"],"default"]
206> 4:I[97367,["/_next/static/chunks/ff1a16fafef87110.js","/_next/static/chunks/7dd66bdf8a7e5707.js"],"ViewportBoundary"]
207> 6:I[97367,["/_next/static/chunks/ff1a16fafef87110.js","/_next/static/chunks/7dd66bdf8a7e5707.js"],"MetadataBoundary"]
208> 7:"$Sreact.suspense"
209> 0:{"b":"TdwnOXsfOJapNex_HjHGt","f":[["children","other",["other",{"children":["__PAGE__",{}]}],["other",["$","$1","c",{"children":[null,["$","$L2",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L3",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","forbidden":"$undefined","unauthorized":"$undefined"}]]}],{"children":null},[["$","div","l",{"children":"loading..."}],[],[]],false],["$","$1","h",{"children":[null,["$","$1","KCFxAJdIDH3BlYXAHsbcVv",{"children":[["$","$L4",null,{"children":"$L5"}],["$","meta",null,{"name":"next-size-adjust","content":""}]]}],["$","$L6","KCFxAJdIDH3BlYXAHsbcVm",{"children":["$","div",null,{"hidden":true,"children":["$","$7",null,{"fallback":null,"children":"$L8"}]}]}]]}],false]],"S":false}
210> 5:[["$","meta","0",{"charSet":"utf-8"}],["$","meta","1",{"name":"viewport","content":"width=device-width, initial-scale=1"}]]
211> 9:I[27201,["/_next/static/chunks/ff1a16fafef87110.js","/_next/static/chunks/7dd66bdf8a7e5707.js"],"IconMark"]
212> 8:[["$","title","0",{"children":"Create Next App"}],["$","meta","1",{"name":"description","content":"Generated by create next app"}],["$","link","2",{"rel":"icon","href":"/favicon.ico?favicon.0b3bf435.ico","sizes":"256x256","type":"image/x-icon"}],["$","$L9","3",{}]]
213> ```
214>
215> 검토해 보니 여기에 실제로 일부 내용이 있더군요. 로딩 상태입니다. 보이시나요?
216>
217> ```json
218> ["$","div","l",{"children":"loading..."}]
219> ```
220>
221> 이 모든 데이터가 실제 페이지 RSC에서 다시 전송되기 때문에 여전히 큰 낭비입니다.
222
223이 문제의 해결책으로 보이는 [`staleTime`][nextjs-stale]은 실험적 기능으로 분류되어 "실제 운영 환경에서는 권장되지 않는다"고 명시되어 있습니다. 이 기능이 비기본 설정의 추가 구성 옵션으로 처리되고 있다는 사실 자체가 당혹스럽습니다. 설령 이를 사용한다고 해도, 동일한 기본 데이터를 참조하는 여러 페이지 간에 데이터를 공유하는 것은 불가능합니다.
224
225[nextjs-stale]: https://nextjs.org/docs/app/api-reference/config/next-config-js/staleTimes
226[nextjs-stale-bug]: https://github.com/vercel/next.js/issues/70661
227
228앱 라우터로는 표현할 수 없는 로딩 상태의 한 형태는, 예를 들어 Git 프로젝트의 이슈 페이지와 같은 페이지에서 사용자 이름을 클릭해 해당 프로필 페이지로 이동하는 경우입니다. `loading.tsx`를 사용하면 전체 페이지가 스켈레톤 형태로 표시되지만, TanStack Query로 이러한 쿼리를 모델링하면 사용자 정보와 저장소를 불러오는 동안 사용자 이름과 아바타를 즉시 표시할 수 있습니다. 서버 컴포넌트는 렌더링된 컴포넌트에서만 데이터가 사용 가능하기 때문에 이 형태의 탐색을 지원하지 않습니다. 따라서 데이터를 다시 가져와야 합니다.
229
230우리 Next.js 사이트의 서버 컴포넌트 데이터 페처에는 데이터 페치 단계를 완전히 건너뛰어 소프트 네비게이션을 더 빠르게 만들기 위한 코드 라인이 있습니다.
231
232```tsx filename="src/util/tanstack-query-helpers.server.ts" tint="server"
233export function serverSidePrefetchQueries(queries) {
234 if ((await headers()).get("next-url")) {
235 // 이건 소프트 네비게이션입니다. 더 빠르게 하려면 프리페칭을 건너뛰세요.
236 // 클라이언트가 이미 이 데이터를 가지고 있을 수 있으며, 그렇지 않더라도 로딩 상태를 가지고 있습니다.
237 // 이상적으로는 이 서버 요청은 없어야 합니다. 앱의 대부분이 클라이언트 컴포넌트로 작성되어
238 // 거의 모든 코드가 클라이언트 측에 있기 때문이죠. 솔직히 말해서 앱 라우터의 설계상의 결함이라고 할 수 있죠.
239 return;
240 }
241 // ... 데이터 프리 페칭 로직 ...
242}
243```
244
245또한 `loading.tsx`에는 `useQuery` 호출이 포함되어야 합니다. 이렇게 하면 빈 RSC에 대한 네트워크 요청이 발생하는 동안 실제로 필요한 경우 데이터를 가져올 수 있습니다. 실제로 `loading.tsx`의 상태는 실제 클라이언트 컴포넌트 자체일 수 있으며, 그러면 클라이언트 페이지가 표시됩니다.
246
247```tsx filename="src/app/user/[username]/loading.tsx" tint="client"
248"use client";
249export default function PageLoadingSkeleton() {
250 return <ClientPage />;
251}
252```
253
254> 업무에서는 단순히 `loading.tsx` 파일에 `useQuery` 호출을 포함하고 스켈레톤만 표시하도록 만듭니다. Next.js가 실제 서버 컴포넌트를 로드할 때면 어쨌든 페이지 전체가 재마운트되기 때문입니다. 여기서는 VDOM 비교가 발생하지 않으므로, 요청 완료 후 모든 훅(useState)이 약간의 지연후에 초기화됩니다. 기존 DOM만 업데이트하고 상태를 유지하도록 Next.js에 간청하는 간단한 사례를 재현해 보려 했지만, 그렇게 되지 않았습니다. 다행히 빈 RSC 호출에 소요되는 시간은 충분히 짧습니다.
255
256
257<h3#layout-restrictions>레이아웃은 인위적으로 제한됩니다</h3>
258
259레이아웃은 데이터를 가져올 수는 있지만, 요청을 어떤 방식으로든 관찰하거나 변경할 수 없습니다. 이는 Next.js가 원하는 때에 레이아웃을 가져오고 캐시할 수 있도록 하기 위함입니다. 다른 모든 프레임워크에서는 레이아웃이 단순히 일반 컴포넌트일 뿐이며 페이지 컴포넌트와 기능상 차이는 없습니다.
260
261레이아웃을 개별적으로 로드하는 아이디어는 매력적이지만 이는 결국 모든 데이터 로드 작업이 레이아웃마다 재실행되어야 한다는 의미라서 어리석은 선택이 됩니다. `QueryClient`를 공유할 수 없습니다. 대신, 그들이 약속한 것처럼 동일한 `GET` 요청을 캐싱하려면 [몽키 패치된 `fetch` 메서드][nextjs-fetch]에 의존해야 합니다.
262
263[nextjs-fetch]: https://nextjs.org/docs/app/api-reference/functions/fetch
264
265동료가 Next.js가 왜 특정 코드를 거부하는지 묻는다면, 기술적 복잡성을 설명하는 건 포기하고 그냥 "Next.js 기술 문제야, 곧 해결할 테니까 걱정 마"라고 말하곤 합니다. 이 규칙들은 일반 개발자들이 이해하기엔 너무 까다롭습니다.
266
267
268<h3#rsc-payload>여전히 모든 콘텐츠를 두 번 다운로드합니다</h3>
269
270["아일랜드 아키텍처"][islands]와 달리 서버 컴포넌트는 `Suspense` 지원 및 클라이언트 컴포넌트 상태 보존을 위해 프론트엔드에서 여전히 하이드레이션되어야 합니다. 소프트 네비게이션 시 "RSC 페이로드"(HTML이 전혀 아님)는 `fetch`로 가져옵니다. 새로 고침 시 첫 번째 페인트에는 HTML이 필요하지만, 클라이언트 컴포넌트와 `Suspense` 관련 정보는 해당 HTML에 포함되어 있지 않습니다. 리액트의 해결책은 <b>전체 페이지 마크업의 두 번째 사본을 전송하는 것</b>입니다. Next.js 프로덕션 서버가 동적 페이지 렌더링 시 전송하는 예시는 다음과 같습니다.
271
272[islands]: https://www.patterns.dev/vanilla/islands-architecture/
273[first paint]: https://web.dev/articles/fcp
274
275```html filename="GET /user/clover"
276<!DOCTYPE html>
277<html>
278<head>
279 {link and meta tags}
280</head>
281<body>
282 {server side render}
283 <script>
284 // 글로벌 `__next_f`를 배열로 설정하는 부트스트랩 스크립트.
285 // 리액트가 로드되면 이 `.push` 함수가 재정의되어
286 // 새로운 청크를 RSC 디코더에 직접 기록합니다.
287 // 이 스크립트에는 DOM 헬퍼도 포함되어 있습니다.
288 (self.__next_f=self.__next_f||[]).push([0])
289 </script>
290 <script>
291 // 애플리케이션 셸용 RSC 페이로드.
292 self.__next_f.push([1,"1:\"$Sreact.fragment\"\n2:I[658993,[\"/_next/st{...}"])
293 </script>
294
295 <!--
296 닫는 태그 </body>는 아직 작성되지 않았습니다.
297 해결되지 않은 서스펜스 경계가 존재하기 때문입니다.
298 시간이 흐른 후에야 추가 데이터가 작성됩니다.
299 -->
300 <div class="user-post-list">
301 {server side render of a Suspense boundary}
302 </div>
303 <script>
304 // 서스펜스 경계에 대한 RSC 페이로드
305 self.__next_f.push([2,"14:[\"$\",\"div\",null,{\"children\":[[\"$\",\"h4\"{...}"])
306 </script>
307
308 <!-- HTML 및 스크립트 태그는 페이지 전체가 완료될 때까지 반복됩니다 -->
309</body>
310</html>
311```
312
313이 솔루션은 <b>초기 HTML 페이로드의 크기를 두 배로 늘립니다</b>. 하지만 더 나쁜 점은 RSC 페이로드에 JS 문자열 리터럴로 감싸져 있는 JSON이 포함되어 있다는 것입니다. 이는 HTML보다 훨씬 비효율적인 형식입니다. 브로틀리(brotli)로 잘 압축되고 브라우저에서 빠르게 렌더링되는 것처럼 보이지만, 이는 낭비입니다. 하이드레이션 패턴을 사용하면 최소한 로컬 데이터는 상호작용 및 다른 페이지에서 재사용될 수 있습니다.
314
315상호작용이 거의 없거나 전혀 없는 페이지에서도 비용을 지불하게 됩니다. [Next.js 문서](https://nextjs.org/docs)를 예로 들면, 홈페이지 로딩 시 약 750kB(HTML 250kB와 스크립트 태그 500kB)의 페이지가 로드되며, 콘텐츠가 두 번 포함됩니다.
316
317Mac에서는 <kbd>Cmd</kbd> + <kbd>Opt</kbd> + <kbd>u</kbd>를, 다른 플랫폼에서는 <kbd>Ctrl</kbd> + <kbd>u</kbd>를 눌러 확인할 수 있습니다. 그런 다음 <kbd>Cmd</kbd> / <kbd>Ctrl</kbd> + <kbd>f</kbd>를 눌러 블로그의 특정 문자열(예: "풀스택 웹 애플리케이션 구축")을 찾아보세요. 두 번 등장합니다. 이는 리액트 서버 컴포넌트의 핵심 요소이므로 <b>피할 수 없습니다</b>.
318
319이런 RSC 형식은 분명히 더 많은 낭비를 낳습니다. 하지만 정말로 `/_next/static/chunks/6192a3719cda7dcc.js`라는 문자열이 27번이나 따로따로 나타나는 이유를 캐내고 싶진 않네요. 뭐야, 너희들. 대역폭이 공짜냐???
320
321
322<h3#turbopack>터보팩은 구립니다</h3>
323
324이 섹션은 건설적이지 않습니다.
325
326- 터보팩은 빠르지 않습니다
327- 터보팩은 디버거에서 디버깅하기 어려운 코드를 생성합니다(개발 모드에서)
328- 터보팩은 많은 경우에 불명확한 오류 메시지를 출력합니다
329
330평소라면 이 점을 블로그에 별도 섹션으로 다루지 않았겠지만, 프로젝트에서 직접 가져온 세 가지 실제 사례를 지적하고자 합니다.
331
332첫 번째는 서버/클라이언트 컴포넌트 모델을 충족시키기 위한 리팩토링 과정에서 실수로 클라이언트 컴포넌트를 `async`로 만든 경우입니다. 이 문제는 문제가 발생한 위치를 전혀 알려주지 않고 <b class='server'>서버</b> 스택 트레이스만 포함하고 있어서 상당히 성가셨습니다.
333
334![Next.js error](/file/2025/blog-everyone-hates-nextjs/asyncerror.png)
335
336끔찍한 오류 메시지의 또 다른 사례입니다.
337
338![Next.js error](/file/2025/blog-everyone-hates-nextjs/nexterror.png)
339
340> 이 두 번째 오류의 근본적인 문제(기억나지 않음)를 수정한 후, 개발 서버가 멈춰서 복구하기 위해 재시작해야 했습니다.
341
342마지막으로, 디버거 중단점을 수십 번 설정할 때마다 변수 이름 `hello`가 `__TURBOPACK__imported__module__$5b$project$5d2f$client$2f$src$2f$utils$2f$filename$2e$ts__$5b$app$2d$client$5d$__$28$ecmascript$29$__["hello"]` 같은 거지같은 이름으로 바뀌는 경우입니다.
343
344네. 이 모든게 구립니다. 어쩌면 좋을까요?
345
346
347<h2#ditching-nextjs>업무에서 Next.js와 Vercel을 매끄럽게 대체하기</h2>
348
349웹 프로젝트에는 두 가지 유형이 있습니다.
350
351- 주로 정적 콘텐츠로 구성된 웹사이트
352- 주로 동적이고 상호작용적인 컴포넌트를 가진 웹 애플리케이션
353
354Next.js는 이 두 가지 작업 모두에 적합하지 않은 도구입니다. 정적 웹사이트를 만드는 첫 번째 유형에 해당한다면 [Astro]나 [Fresh]를 선택하세요. 리액트의 모든 기능을 필요로 하는 분들을 위해, 이 섹션에서는 벤더에 종속된 Next를 [TanStack Start]로 점진적으로 매끄럽게 교체한 방법을 설명합니다.
355
356[Astro]: https://astro.build/
357[Fresh]: https://fresh.deno.dev/
358
359이 Vite 설정부터 시작했습니다.
360
361```ts filename="vite.config.ts"
362const config = defineConfig(({ mode }) => {
363 const env = loadEnv(mode, process.cwd(), "NEXT_PUBLIC_");
364 return {
365 // Next.js 기본 포트 3000을 사용하세요
366 server: { port: 3000 },
367 // Next.js의 기본 환경 접두사 "NEXT_PUBLIC_"을 사용하십시오.
368 define: Object.fromEntries(Object.entries(env).map(
369 ([k, v]) => [`process.env.${k}`, JSON.stringify(v)])),
370 plugins: [
371 viteTsConfigPaths({ projects: ["./tsconfig.json"] }),
372 tailwindcss(),
373 // 동료들이 이해하기 쉽도록 `src/tanstack-routes`에 있는 라우트를
374 // 포팅하기 시작했습니다. 마이그레이션이 완료되면
375 // 기본 `src/routes`로 되돌아갈 예정입니다.
376 tanstackStart({
377 router: { routesDirectory: "src/tanstack-routes" },
378 }),
379 viteReact(),
380 ],
381 resolve: {
382 // 증분 마이그레이션의 핵심: `next`를 다른 곳으로 리디렉션
383 alias: { next: path.resolve("./src/tanstack-next/") },
384 conditions: ["tanstack"],
385 extensions: [
386 // `utils/session.tanstack.ts`와 같은 이름의 파일이
387 // `utils/session.ts`를 가져올 때 덮어쓸 수 있도록 허용합니다.
388 ".tanstack.tsx", ".tanstack.ts",
389 // 기본 임포트 확장자
390 ".mjs", ".js", ".mts", ".ts",
391 ".jsx", ".tsx", ".json",
392 ],
393 },
394 };
395});
396```
397
398그런 다음 Next.js API의 모든 사용처를 찾아 제거하거나 TanStack용 스텁을 만들었습니다. 예를 들어, `src/tanstack-next/link.tsx`는 `next/link`를 구현합니다.
399
400```tsx filename="src/tanstack-next/link.tsx"
401import { Link } from "@tanstack/react-router";
402import type { LinkProps } from "next/link";
403
404export default function LinkAdapter({ href, ...rest }: LinkProps) {
405 return <Link {...rest} to={href as unknown as any} />;
406}
407```
408
409> 이러한 스텁 중 일부는 매우 간단할 수 있습니다. 처음 시작했을 때, 제가 구현한 `useRouter`는 단순히 `return {}`였지만, 나중에 객체에 몇 가지 메서드를 추가해야 했습니다. 여기 코드 자체는 임시적인 것이므로 깔끔할 필요가 없습니다.
410
411이제 새 사이트는 필요한 Next.js API를 스터빙하거나 `.tanstack.ts` 확장자를 사용해 파일별로 로직을 재구현함으로써 거의 모든 클라이언트 컴포넌트를 가져올 수 있습니다. 그리고 얼마 지나지 않아 TanStack Start에서 사이트 홈페이지를 작동시키는 데 성공했고, 해당 브랜치를 병합했습니다.
412
413![My "nextgate" PR](/file/2025/blog-everyone-hates-nextjs/pr.png)
414
415> 이 첫 번째 PR은 우리 페이지 중 하나만을 지원했으며, 추가된 코드 천 줄과 삭제된 코드 40줄로 이를 수행할 수 있었습니다. 저는 이전 패치를 통해 `next/image` 및 `next/font`의 몇 가지 사용 사례를 제거한 적이 있습니다.
416
417남은 작업은 다른 모든 경로를 포팅하는 것이었습니다. Next.js에서 다른 프레임워크로 마이그레이션할 때 잃게 되는 한 가지는 UI에서 데이터 페칭 함수를 `await`할 수 있는 기능입니다. 실제로 모든 경로를 `loader` 함수로 이동시키면 페이지가 서버 측 렌더링(SSR)될 때 어떤 일이 발생하는지 훨씬 명확해졌습니다. 여러 번의 데이터 가져오기가 필요한 페이지의 경우, 해당 페이지에 필요한 모든 관련 데이터를 반환하는 단일 특수 API 호출로 통합할 수 있었습니다.
418
419다시 한번 강조하자면, <strong style='color:var(--secondary)'>서버 컴포넌트에서의 마이그레이션 경로는 단순히 코드를 단순화하는 것입니다. RSC는 본질적으로 필요 없는 것들로 가득한 혼란스러운 길로 이끌어갑니다.</strong> 우리 사이트의 거의 모든 복잡한 부분이 모든 엔지니어에게 이해하기 쉬워졌습니다. 유일한 예외는 모두가 새로운 파일 시스템 라우팅 규칙에 익숙해져야 했던 점입니다. 충분한 예시를 통해 우리 모두 그 요령을 터득했습니다.
420
421증분 마이그레이션을 통해 신규 코드가 기존 배포를 방해하지 않았습니다. TanStack이 점진적으로 코드베이스를 인수하면서, 결국 모든 Next.js 스텁을 삭제하고 TanStack 라우터가 제공하는 모든 우수한 [타입 안전성 기능]을 확보했습니다. 최종적으로 사이트는 모든 측면에서 더 빠른 성능을 보였습니다: 개발 모드, 프로덕션 페이지 로딩 시간, 소프트 네비게이션, 그리고 Vercel을 사용한 Next 배포보다 저렴한 비용으로 구현되었습니다.
422
423[타입 안전성 기능]: https://tanstack.com/router/v1/docs/framework/react/guide/type-safety
424
425변화를 목격하는 건 우리만이 아닙니다. 저는 소셜 미디어를 멀리하려 노력하지만, 누군가 [브라이언 앵글린(Brian Anglin)이 Superwall에서 진행한 작업 결과][superwall-twitter]를 보내왔는데, TanStack Start에서 CPU 사용량이 엄청나게 감소한 걸 보여주더군요. 또한 1년 전 ChatGPT가 Next.js에서 Remix로 전환한 일도 기억납니다(관련 온라인 대화: [[1][chatgpt-1]] [[2][chatgpt-2]] [[3][chatgpt-3]]).
426
427[superwall-twitter]: https://twitter.com/BriansAngles/status/1978834116079436242#m
428[chatgpt-1]: https://xcancel.com/ryanflorence/status/1831379475654947233
429[chatgpt-2]: https://old.reddit.com/r/reactjs/comments/1f97zgr/chatgpt_migrates_from_nextjs_to_remix
430[chatgpt-3]: https://old.reddit.com/r/nextjs/comments/1f92jdv/chatgptcom_switched_from_nextjs_to_remix
431
432
433<h3#next-metadata><code>next/metadata</code>는 훌륭합니다</h3>
434
435제 생각에 이건 Next.js가 가진 몇 안 되는 좋은 API 중 하나이며, TanStack으로 전환하면서 코드에서 유일하게 작업이 더 어려워진 부분이었습니다. 코드를 악화시키기보다는, 그냥 그들의 메타데이터 API를 일반 함수로 포팅해서 누구나 사용할 수 있게 했습니다. 원래는 NPM에 1:1 포팅 버전을 올렸지만, 올해 초에 API를 간결하고 이해하기 쉬운 하나의 파일로 단순화했습니다. 이 블로그 글 작성 시점 기준으로, TanStack 호환 `meta.toTags` API를 추가했으며, [JSR][lib-jsr]이나 [NPM][lib-npm]에서 설치하거나 단순히 프로젝트에 복사해서 사용할 수 있습니다.
436
437> 공지: 본 글 작성에 시간이 부족하여 라이브러리는 아직 업데이트되지 않았습니다. 아마도 ~~이번 주 말(10월 24일)~~ 가까운 시일 내에 처리할 예정입니다... 임시로 업무용으로 사용 중인 버전을 제 웹사이트에 공유합니다. [`meta.tanstack.ts`](https://paperclover.net/file/2025/blog-everyone-hates-nextjs/meta.tanstack.ts)
438
439```tsx
440// 프로젝트에 한 번만
441import * as meta from "@clo/lib/meta.ts";
442
443export const defineHead = meta.toTags.bind(null, {
444 // 사이트 전체 옵션
445 base: new URL("https://paperclover.net"),
446 titleTemplate: (title) => [title, "paper clover"]
447 .filter(Boolean).join(' | '),
448 // ...
449});
450
451// 각 페이지마다...
452export const Route = createFileRoute("/blog")({
453 head: () =>
454 defineHead({
455 title: "clover's blog", // `titleTemplate`으로 템플릿 처리됨
456 description: "a catgirl meows about her technology viewpoints",
457 canonical: "/blog", // `base`와 결합됨
458
459 // 지정 시, 페이지 제목과 설명을 기본값으로 사용하여
460 // Open Graph 및 Twitter 임베드를 구성합니다.
461 // 기본값도 괜찮지만, 더 많은 옵션을 지원합니다.
462 embed: {
463 image: "/img/blog.webp",
464 },
465
466 // 모든 특수 메타 태그는 JSX 프래그먼트로 처리됩니다.
467 // 이는 리액트 렌더링하지 않고 단순히 태그를 순회합니다.
468 // 제 목표는 가장 흔한 99%의 사용 사례를 커버하는 것이었습니다.
469 extra: <>
470 <meta name="site-verification" content="waffles" />,
471 </>,
472 }),
473
474 component: Page,
475});
476
477function Page() {
478 ...
479}
480```
481
482제 버전은 Next.js 메타데이터 객체의 전체 영역을 커버하는 데 초점을 두지 않았으며, 대신 인라인 JSX를 사용하여 그 공백을 메웠습니다.
483
484[lib-jsr]: https://jsr.io/@clo/lib
485[lib-npm]: https://npmjs.com/@paperclover/lib
486
487<h3#vercel-og><code>next/og</code> 또한 좋습니다</h3>
488
489특별한 의견은 없습니다. 그냥 `@vercel/og` 패키지가 있다는 점을 모두에게 상기시키고 싶을 뿐입니다.
490
491<h2#experience-feels-like-the-usual>제 경험은 일반적인 것 같아요</h2>
492
493Next.js Conf 2024에서 참석자 모두가 서버 컴포넌트(Server Components)에 열광했습니다. 정확히 누구와 이야기했는지는 기억나지 않지만, 주요 인사들은 모두 이 기술에 주목하고 있었습니다. RSC의 번들러 부분을 구현한 저는 이 포맷의 몇 가지 문제점을 목격했습니다. Next 15가 지난해 App Router를 "안정화"하면서 많은 기업들이 이를 기반으로 제품을 구축하고 있으며, 이러한 문제점들을 직접 경험하고 있습니다.
494
495저는 Next.js를 늦게 접하게 되어 6월에야 버전 15로 시작했습니다. 하지만 행사에서 만난 모든 분들이 제 의견에 공감해 주셨습니다. Bun의 1.3 파티에서 이 주제로 이야기한 분들도 모두 동의하셨죠. 심지어 Vercel의 몇몇 분들조차 Next.js의 실제 사용 방식이 마음에 들지 않는다고 말씀하셨습니다.
496
497TanStack Start가 안정화되면서 모두가 원하는 Next.js 대체 솔루션이 되길 바랍니다.
498
499
500<h2#prefer-respectful-tools>사용자를 존중하는 도구를 선택하세요</h2>
501
502자바스크립트 생태계는 상당 부분 엉망이고, 그 때문인지 웹 개발은 종종 조롱의 대상이 됩니다.. 저는 웹 작업을 회복 불가능한 난장판이라고 생각했던 적이 수없이 많았지만, 그 혼란은 사실 제가 둘러싸고 있던 흔히 쓰이는 라이브러리들 때문이었습니다. 그 껍질을 벗겨내면 현대 웹 개발 기술은 정말 대단합니다.
503
5042024년 말부터 프레임워크 없이 이 웹사이트를 처음부터 직접 제작해 왔습니다. 자체 개발한 [TUI 진행률 위젯][progress], [정적 파일 프록시][file-cache], 증분 빌드 시스템 등 다양한 컴포넌트를 직접 구현하며 작업했습니다. 지난 수년간 가장 행복한 코딩 경험이었습니다. *[페이퍼 클로버]* 방문자들은 더 나은 품질의 웹사이트를 이용하게 되고, 제가 만든 미니 라이브러리는 [공개적으로 활용될 수 있게 추출됩니다][lib]. 덕분에 모두가 이득을 보게 되었죠.
505
506이 정도로 완전히 처음부터 시작하는 방식은 대부분의 사람들에게, 특히 직장에서는 부담스럽습니다. 최소한 우리를 존중하는 고품질 도구에만 우리의 관심과 돈을 쏟아야 한다고 생각합니다. 그리고 Next.js와 그 배후 기업인 Vercel은 그렇지 않습니다.
507
508Next.js를 사용하면서 개발자로서 존중받는다는 느낌이 들지 않는다면, 과연 여러분과 동료들이 그들의 [서버리스 제국]을 계속 지지하고 싶은지 고민해 보세요. 현재 Vite 생태계는 구축하기에 꽤 괜찮아 보이지만, 아직 대규모 프로덕션 환경에서 그들의 도구를 사용해 본 경험은 거의 없습니다. [Void0의 Vite+ 출시 소식][vite-plus]은 흥미롭지만, 이러한 벤처 수준의 지원 도구가 장기적으로 우리(최종 사용자와 개발자)를 존중할지는 시간이 지나야 알 수 있을 것입니다.
509
510Next.js Conf 2025는 글을 쓰는 시점 기준으로 [내일][next-conf]입니다. 800달러짜리 티켓을 구매하는 대신, 웹 개발 생태계를 [존중하고 개선하는][tanstack-ethos] [TanStack 팀에 그 돈을 기부하기로 결정했습니다][tanstack-donate].
511
512[페이퍼 클로버]: https://paperclover.net/
513[lib]: https://git.paperclover.net/clo/sitegen/src/branch/master/lib#readme
514[progress]: https://git.paperclover.net/clo/sitegen/src/branch/master/lib/progress.ts
515[file-cache]: https://git.paperclover.net/clo/sitegen/src/branch/master/src/file-viewer/cache.ts
516
517[next-conf]: https://nextjs.org/conf
518[vite-plus]: https://viteplus.dev/
519[tanstack-ethos]: https://tanstack.com/ethos
520[tanstack-donate]: https://github.com/sponsors/tannerlinsley
521[serverless empire]: https://youtu.be/SCIfWhAheVw
522
523## 앞으로의 계획
524
525점진적으로, 저는 저를 존중하지 않는 많은 소프트웨어들을 더 나은 대안으로 교체해 왔습니다. 그 예로는 GitHub, Visual Studio Code, DaVinci Resolve, Discord, Google Drive/Workspace 등이 있으며, 그 외에도 많습니다. 이 블로그에서는 제가 진행 중인 기술적 작업들(프로그레스 라이브러리, 자체 사이트 생성기의 목적, 현재 직장에서의 배움)과 Bun에서의 과거 프로젝트들(HMR, 크래시 리포터, 내장 모듈 번들링을 위한 독특한 시스템에 대한 세부 사항)에 대해 더 많이 다루려 합니다. 관심이 있으시다면 이메일 리스트에 가입해 주세요.
526
527<hr />
528
529<a href="mailto:subscribe@paperclover.net?subject=paper%20clover%20mailing%20list&body=I%20would%20like%20to%20be%20subscribed%20to%20the%20following%20mailing%20lists%3A%0A%0A-%20Technical%20Blog%20Posts%20-%20YES%0A-%20Art%20(Original%20Music%2FVideo)%20-%20YES%0A%0A(feel%20free%20to%20write%20whatever%20else%20you%20want)">click here to send an email to <code>subscribe@paperclover.net</code>, requesting that you would like to be added to the mailing list.</a> (i manage this mailing list manually)
530
531[↑](#top) &mdash; [ask a question about this article (English)](/q+a)
532
533<br />
534<br />
535<br />
536<br />
537<br />
538<br />
539<footer>
5402025 (c) paper clover
541</footer>
src/blog/pages/webdev/one-year-next-app-router/ko.mdx deleted-624
......@@ -1,624 +0,0 @@
1import Heading from "@/blog/tags/heading.tsx";
2import TableOfContents from "@/blog/tags/table-of-contents.tsx";
3import { Layout } from "@/blog/tags/layout.tsx";
4export { theme } from "@/blog/tags/layout.tsx";
5
6export const meta = {
7 title: "Next.js 앱 라우터와 함께한 1년 — 우리가 떠나기로 한 이유",
8 description: "리액트 서버 컴포넌트와 Next.js 15에 대한 비판",
9 keywords: ["webdev", "technical analysis", "opinion"],
10 authors: ["clover caruso", "Chanhee Kim"],
11 embed: {
12 thumbnail: "/open-graph/next-js.ko.png"
13 },
14 twitter: {
15 image: "https://paperclover.net/open-graph/next-js.ko.png"
16 },
17 canonical: "/blog/webdev/one-year-next-app-router.ko"
18};
19
20<Layout
21 meta={meta}
22 date={'Oct 21st, 2025'}
23 slug="webdev/one-year-next-app-router"
24 translation={{
25 lang: "ko",
26 author: "Chanhee Kim",
27 href: "https://substack.com/@chanheekim377573",
28 date: "Dec 8th, 2025"
29 }}
30>
31
32직장에서 웹 앱 개발에 Next.js를 전문적으로 사용해오면서, 앱 라우터와 [리액트 서버 컴포넌트(React Server Components, RSC)][rsc]의 핵심 설계가 매우 답답하게 느껴졌습니다. 사소한 버그나 API의 혼란스러움이 아니라, Vercel과 리액트 팀이 이를 구축할 때 내린 근본적인 설계 결정에 대한 큰 이견이 있기 때문입니다.
33
34웹 개발 행사에 갈때마다 Next.js를 싫어함에도 계속 사용해야 하는 사람들을 더 많이 보게 됩니다. 이 글의 마지막에는 저와 동료들이 어떻게 이 지옥에서 탈출하여 전체 프론트엔드를 [TanStack Start]로 원활하게 마이그레이션했는지 공유하겠습니다.
35
36[TanStack Start]: https://tanstack.com/start/latest
37[rsc]: https://react.dev/reference/rsc/server-components
38
39<TableOfContents>
40
41- [기술 리뷰: 서버 컴포넌트란 무엇인가요?][§1]
42- [실제로 발생하는 앱 라우터의 문제점들][§2]
43 - [낙관적 업데이트는 불가능합니다][§2.1]
44 - [모든 탐색은 또 다른 페치 요청입니다][§2.2]
45 - [레이아웃은 인위적으로 제한됩니다][§2.3]
46 - [여전히 모든 콘텐츠를 두 번 다운로드합니다][§2.4]
47 - [터보팩은 구립니다][§2.5]
48- [업무에서 Next.js와 Vercel을 매끄럽게 대체하기][§3]
49 - [`next/metadata`는 훌륭합니다][§3.1]
50 - [`next/og` 또한 좋습니다][§3.2]
51- [제 경험은 일반적인 것 같아요][§4]
52- [사용자를 존중하는 도구를 선택하세요][§5]
53
54</TableOfContents>
55
56[§1]: #technical-review
57
58<Heading
59 level='h2'
60 slug='technical-review'
61>기술 리뷰: 서버 컴포넌트란 무엇인가요?</Heading>
62
63RSC의 핵심은 컴포넌트를 <b class='server'>"서버"</b> 컴포넌트와 <b class='client'>"클라이언트"</b> 컴포넌트 두 가지 범주로 분류한다는 점입니다. 서버 컴포넌트는 `useState`나 `useEffect`를 사용하지 않지만, `async function`일 수 있으며 데이터베이스에 직접 호출하는 등 백엔드 도구를 참조할 수 있습니다. 클라이언트 컴포넌트는 기존 모델로, 백엔드에서 HTML 텍스트를 생성하는 코드와 `window.document.*`를 사용하여 DOM을 관리하는 프론트엔드 코드가 존재합니다.
64
65> 첫 번째 재앙: 명명법!! 리액트는 이제 기존 정의를 무시하고 <b class='server'>"서버"</b>와 <b class='client'>"클라이언트"</b>라는 단어를 매우 특정한 개념을 가리키는 데 사용하고 있습니다. <b class='client'>클라이언트</b> 컴포넌트도 백엔드에서 실행될 수 있다는 점을 제외하면 괜찮을 텐데요! 이 글에서는 웹 앱이 존재하는 두 가지 실행 환경, 즉 Node.js 프로세스와 웹 브라우저를 각각 설명하기 위해 <b>"백엔드"</b>와 <b>"프론트엔드"</b>라는 용어를 사용할 것입니다.
66
67이 <b class='server'>"서버"</b>/<b class='client'>"클라이언트"</b> 컴포넌트 모델은 흥미롭습니다. `<Suspense />` 같은 내장 컴포넌트가 네트워크를 통해 직렬화되기 때문에, 비동기 <b class='server'>서버 컴포넌트</b>로 데이터 가져오기를 아주 간단하게 모델링할 수 있으며, 폴백 UI는 마치 클라이언트 측에서 작동하는 것처럼 동작합니다.
68
69```tsx filename="src/app/[username]/page.tsx" tint="server"
70// 이 글에서 서버 컴포넌트는 빨간색으로 강조 표시됩니다.
71export default async function Page({ params }) {
72 // Page 매개변수는 해결된 프로미스로 제공됩니다
73 const { username } = await params;
74
75 // `UserInfo` 및 `UserPostList` 컴포넌트는 동시에 실행됩니다.
76 // `UserInfo`가 준비되면, 방문자는 게시물 목록이 아직 준비되지 않은 경우
77 // `PostListSkeleton`이 포함된 페이지를 보게 됩니다.
78 return <main>
79 <UserInfo username={username} />
80
81 <Suspense fallback={<PostListSkeleton />}>
82 <UserPostList username={username} />
83 </Suspense>
84 </main>
85}
86
87// 워터폴은 여러 컴포넌트를 동시에 평가함으로써 방지됩니다.
88
89async function UserInfo({ username }) {
90 const user = await fetchUserInfo(username);
91 return <>
92 <h1>{user.displayName}</h1>
93 {user.bio ? <Markdown content={user.bio} /> : ""}
94 </>
95}
96
97async function UserPostList({ username }) {
98 const posts = await fetchUserPostList(username);
99 return /* post list ui omitted for brevity */;
100}
101```
102
103리액트 자체의 40kB gzip 압축 번들을 제외하면, 위 예시는 UI와 데이터 페칭을 위한 자바스크립트가 전혀 없습니다. 단순히 마크업을 스트리밍할 뿐이죠! 예를 들어, `<Markdown />` 컴포넌트 내부의 가상 마크다운 파서는 백엔드에 그대로 남아 있습니다. 인터랙티브한 프론트엔드가 필요할 때는, "use client"로 시작하는 파일에 컴포넌트를 배치하여 <b class='client'>클라이언트 컴포넌트</b>를 만들 수 있습니다.
104
105```tsx filename="src/components/CopyButton.tsx" tint="client"
106"use client"; // 이 주석은 파일이 클라이언트 사이드로 번들링 되도록 마킹합니다.
107
108export function CopyButton({ url }) {
109 return <>
110 <span>{url}</span>
111 <button onClick={() => {
112 const full = new URL(url, location.href);
113 navigator.clipboard.writeText(full.href);
114 // 에러 처리나 성공시 보여주는 ui는 제외했습니다
115 }}>copy</button>
116 </>
117}
118```
119```tsx filename="src/app/q+a/Card.tsx" tint="server"
120export function Card() {
121 return <article>
122 <header>
123 {/* 브라우저가 CopyButton을 import 하도록 합니다 */}
124 <CopyButton url="/q+a/2506010139" />
125 </header>
126 <p>
127 {/* 마크다운 처리는 백엔드에서 수행됩니다 */}
128 <Markdown content=".........." />
129 </p>
130 </article>
131}
132```
133
134[§2]: #real-world-pitfalls
135
136<Heading
137 level='h2'
138 slug='real-world-pitfalls'
139>실제로 발생하는 앱 라우터의 문제점들</Heading>
140
141런타임 엔지니어로 근무하던 [Bun]을 그만둔 후([서버 컴포넌트 번들링]과 [RSC 템플릿][bun-rsc]을 구현했습니다), 저는 최전선에서 일하는 소규모 회사에 합류했습니다. Hono 백엔드를 가진 Next.js 애플리케이션이었습니다. 다음 내용들은 실제 현장에서 유지보수 및 신규 기능 개발 시 마주친 문제들을 단순화한 것입니다. 이 모든 것들의 결과로 인해, 모두가 설계상의 결함을 우회하거나, 당연히 해결되어야 할 문제가 왜 해결 불가능한 장애물이 되었는지 서로 설명하는 데 시간을 낭비하게 되었습니다.
142
143[Bun]: https://bun.com
144[서버 컴포넌트 번들링]: https://github.com/oven-sh/bun/blob/67f0c3e016aa479738469adac2b79a1862b88122/src/bake/bake.d.ts
145[bun-rsc]: https://github.com/oven-sh/bun/tree/e7790894d92b730758ecadf971cb935063508dfb/src/bake/bun-framework-react
146
147[§2.1]: #optimistic-updates
148
149<Heading
150 level='h3'
151 slug='optimistic-updates'
152>낙관적 업데이트는 불가능합니다</Heading>
153
154Next.js 문서에는 변경 수행 시 [낙관적 업데이트에 대한 언급이 없습니다][nextjs-updating-data]. 이 경우를 고려하지 않은 것으로 보입니다. <b class='server'>리액트 서버</b>에서 렌더링되는 컴포넌트는 설계상 마운팅 후 수정할 수 없습니다. 변경될 수 있는 요소는 클라이언트 컴포넌트 내에 있어야 하지만, 백엔드에서 SSR(서버 측 렌더링) 중에도 클라이언트 컴포넌트에서 데이터 가져오기가 불가능합니다. 이로 인해 데이터 가져오기만 수행하는 어색하게 작은 서버 컴포넌트와, 대부분 정적인 버전의 페이지를 포함하는 클라이언트 컴포넌트가 분리되어 있는 구조가 됩니다.
155
156[nextjs-updating-data]: https://nextjs.org/docs/app/getting-started/updating-data
157
158```tsx filename="src/app/user/[username]/page.tsx" tint="server"
159export default async function Page() {
160 const user = await fetchUserInfo(username);
161 return <ProfileLayout>
162 <UserProfile user={user} />
163 </ProfileLayout>;
164}
165```
166```tsx filename="src/app/user/[username]/UserProfile.tsx" tint="client"
167"use client"; // 클라이언트 코드를 반드시 두 번째 파일로 분리해야 합니다!
168
169export function UserProfile({ user: initialUser }) {
170 // 훌륭한 상태 관리 라이브러리들이 많이 존재합니다.
171 // 단순화를 위해 이 예제에서는 하나의 상태 셀을 사용하겠습니다.
172 const [user, optimisticUpdateUser] = useState(initialUser);
173
174 async function onEdit(newUser) {
175 optimisticUpdateUser(newUser);
176 const resp = await fetch("...", {
177 method: 'POST',
178 body: JSON.stringify(newUser),
179 ... // (헤더, 자격 증명, 추적 등)
180 })
181 if (!resp.ok) /* 항상 오류 검사를 잊지 마세요! */
182 }
183
184 return <main>{/* 편집 가능한 필드가 있는 사용자 인터페이스... */}</main>:
185}
186```
187
188페이지의 상호작용 요소가 늘어날수록 정적 부분을 서버 측에서 완전히 처리하기 복잡해집니다. 업무용 앱에서는 거의 모든 UI 요소가 동적 데이터를 표시합니다. [`WebSocket`][ws]은 데이터가 업데이트될 때 실시간으로 동기화합니다(예: 사용자 카드의 온라인 상태와 기본 프로필 정보). 이러한 컴포넌트 설정은 엔지니어가 이해하고 유지하기 어렵기 때문에, 거의 모든 페이지가 데이터 가져오기를 정의하는 `page.tsx`를 통해 완전히 `"use client` 방식으로 구현됩니다.
189
190[ws]: https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API
191
192직장에서 사용하는 데이터 페칭 라이브러리인 [TanStack Query](https://github.com/tanstack/query#readme)를 통해 실제 적용 사례를 보다 구체적으로 살펴보겠습니다.
193
194```ts filename="src/queries/users.ts"
195// 작업 시 타입 안전성을 위해 `defineQuery` 헬퍼 함수가 사용됩니다.
196// 페처는 단순하며 백엔드나 프론트엔드에서 실행될 수 있습니다.
197export const queryUserInfo = (username) => ({
198 queryKey: ['user', username],
199 queryFn: async ({ ... }) => /* fetch data */
200});
201```
202```tsx filename="src/app/user/[username]/page.tsx" tint="server"
203export default async function Page({ params }) {
204 const { username } = await params;
205
206 // 리액트 서버에는 글로벌 상태가 없습니다.
207 // 레이아웃이 병렬로 실행되기 때문에 TanStack `QueryClient`는 경로 마다 여러 번 재구성되어야 합니다.
208 const queryClient = new QueryClient();
209 await queryClient.ensureQueryData(queryUserInfo(username));
210
211 // HydrationBoundary는 리액트 서버에서 클라이언트 컴포넌트로
212 // JSON 데이터를 전달하는 클라이언트 컴포넌트입니다.
213 return <HydrationBoundary state={dehydrate(queryClient)}>
214 <ClientPage />
215 </HydrationBoundary>;
216}
217```
218```tsx filename="src/app/user/[username]/ClientPage.tsx" tint="client"
219"use client";
220export function ClientPage() {
221 const { username } = useParams();
222 const { data: user } = useSuspenseQuery(queryUserInfo(username));
223
224 // ... 이외의 다른 훅들
225
226 return <main>
227 {/* ... 인터랙티브 웹 페이지 */}
228 </main>;
229}
230```
231
232이 예제는 서버 컴포넌트 번들링 규칙 때문에 반드시 세 개의 별도 파일로 구성되어야 합니다. (클라이언트 컴포넌트는 `"use client"`가 필요하며, 서버 전용 임포트 때문에 서버 컴포넌트 파일은 클라이언트에서 종종 임포트할 수 없습니다.) Pages 라우터에서는 `getStaticProps`와 `getServerSideProps`가 트리 셰이킹을 지원하기 때문에 단일 파일로 구현할 수 있었습니다.
233
234[§2.2]: #redundant-fetches
235
236<Heading
237 level='h3'
238 slug='redundant-fetches'
239>모든 탐색(navigation)은 또 다른 페치 요청입니다</Heading>
240
241앱 라우터는 모든 페이지를 서버 컴포넌트로 시작하며 이상적으로는 상호작용 영역이 작기 때문에, 새 페이지로 이동할 때는 클라이언트가 이미 보유한 데이터와 무관하게 Next.js 서버를 다시 호출해야 합니다! `loading.tsx` 파일이 있더라도, `/`를 열고 `/other`로 이동한 후 다시 `/`로 돌아오면 홈페이지를 재로딩하는 동안 로딩 상태가 표시됩니다.
242
243이 방법이 통하는 유일한 경우는 <b>완벽히 정적인 콘텐츠</b>일 때로, 이때는 즉각적인 탐색과 사전 로딩이 훌륭하게 작동합니다. 하지만 <b>웹 애플리케이션은 정적이지 않고</b> 다량의 동적 콘텐츠를 포함합니다. 클라이언트가 페이지를 즉시 표시하는 데 필요한 모든 것을 이미 가지고 있음에도 불구하고, 로그인 상태가 홈페이지를 변경시키는 것은 정말 짜증나는 일입니다. 쿠키가 변경된 것도 아닌데 말이죠.
244
245> 참고: 빈 프로젝트에서 추가 테스트를 진행한 결과, Next 프론트엔드 코드가 실제 콘텐츠 없이 경로를 미리 가져오는 사례를 관찰했습니다. 'Hello World' 예제에서는 1.8kB 크기의 RSC 페이로드가 2개의 서로 다른 JS 청크를 4번에 걸쳐 가리켰습니다. 이는 순전히 대역폭과 아웃바운드 트래픽을 낭비하는 행위입니다. 특히 링크를 실제로 클릭할 때 이 모든 정보를 다시 가져온다는 점을 고려하면 더욱 그렇습니다.
246>
247> ```json whitespace="pre-wrap"
248> 1:"$Sreact.fragment"
249> 2:I[39756,["/_next/static/chunks/ff1a16fafef87110.js","/_next/static/chunks/7dd66bdf8a7e5707.js"],"default"]
250> 3:I[37457,["/_next/static/chunks/ff1a16fafef87110.js","/_next/static/chunks/7dd66bdf8a7e5707.js"],"default"]
251> 4:I[97367,["/_next/static/chunks/ff1a16fafef87110.js","/_next/static/chunks/7dd66bdf8a7e5707.js"],"ViewportBoundary"]
252> 6:I[97367,["/_next/static/chunks/ff1a16fafef87110.js","/_next/static/chunks/7dd66bdf8a7e5707.js"],"MetadataBoundary"]
253> 7:"$Sreact.suspense"
254> 0:{"b":"TdwnOXsfOJapNex_HjHGt","f":[["children","other",["other",{"children":["__PAGE__",{}]}],["other",["$","$1","c",{"children":[null,["$","$L2",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L3",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","forbidden":"$undefined","unauthorized":"$undefined"}]]}],{"children":null},[["$","div","l",{"children":"loading..."}],[],[]],false],["$","$1","h",{"children":[null,["$","$1","KCFxAJdIDH3BlYXAHsbcVv",{"children":[["$","$L4",null,{"children":"$L5"}],["$","meta",null,{"name":"next-size-adjust","content":""}]]}],["$","$L6","KCFxAJdIDH3BlYXAHsbcVm",{"children":["$","div",null,{"hidden":true,"children":["$","$7",null,{"fallback":null,"children":"$L8"}]}]}]]}],false]],"S":false}
255> 5:[["$","meta","0",{"charSet":"utf-8"}],["$","meta","1",{"name":"viewport","content":"width=device-width, initial-scale=1"}]]
256> 9:I[27201,["/_next/static/chunks/ff1a16fafef87110.js","/_next/static/chunks/7dd66bdf8a7e5707.js"],"IconMark"]
257> 8:[["$","title","0",{"children":"Create Next App"}],["$","meta","1",{"name":"description","content":"Generated by create next app"}],["$","link","2",{"rel":"icon","href":"/favicon.ico?favicon.0b3bf435.ico","sizes":"256x256","type":"image/x-icon"}],["$","$L9","3",{}]]
258> ```
259>
260> 검토해 보니 여기에 실제로 일부 내용이 있더군요. 로딩 상태입니다. 보이시나요?
261>
262> ```json
263> ["$","div","l",{"children":"loading..."}]
264> ```
265>
266> 이 모든 데이터가 실제 페이지 RSC에서 다시 전송되기 때문에 여전히 큰 낭비입니다.
267
268이 문제의 해결책으로 보이는 [`staleTime`][nextjs-stale]은 실험적 기능으로 분류되어 "실제 운영 환경에서는 권장되지 않는다"고 명시되어 있습니다. 이 기능이 비기본 설정의 추가 구성 옵션으로 처리되고 있다는 사실 자체가 당혹스럽습니다. 설령 이를 사용한다고 해도, 동일한 기본 데이터를 참조하는 여러 페이지 간에 데이터를 공유하는 것은 불가능합니다.
269
270[nextjs-stale]: https://nextjs.org/docs/app/api-reference/config/next-config-js/staleTimes
271[nextjs-stale-bug]: https://github.com/vercel/next.js/issues/70661
272
273앱 라우터로는 표현할 수 없는 로딩 상태의 한 형태는, 예를 들어 Git 프로젝트의 이슈 페이지와 같은 페이지에서 사용자 이름을 클릭해 해당 프로필 페이지로 이동하는 경우입니다. `loading.tsx`를 사용하면 전체 페이지가 스켈레톤 형태로 표시되지만, TanStack Query로 이러한 쿼리를 모델링하면 사용자 정보와 저장소를 불러오는 동안 사용자 이름과 아바타를 즉시 표시할 수 있습니다. 서버 컴포넌트는 렌더링된 컴포넌트에서만 데이터가 사용 가능하기 때문에 이 형태의 탐색을 지원하지 않습니다. 따라서 데이터를 다시 가져와야 합니다.
274
275우리 Next.js 사이트의 서버 컴포넌트 데이터 페처에는 데이터 페치 단계를 완전히 건너뛰어 소프트 네비게이션을 더 빠르게 만들기 위한 코드 라인이 있습니다.
276
277```tsx filename="src/util/tanstack-query-helpers.server.ts" tint="server"
278export function serverSidePrefetchQueries(queries) {
279 if ((await headers()).get("next-url")) {
280 // 이건 소프트 네비게이션입니다. 더 빠르게 하려면 프리페칭을 건너뛰세요.
281 // 클라이언트가 이미 이 데이터를 가지고 있을 수 있으며, 그렇지 않더라도 로딩 상태를 가지고 있습니다.
282 // 이상적으로는 이 서버 요청은 없어야 합니다. 앱의 대부분이 클라이언트 컴포넌트로 작성되어
283 // 거의 모든 코드가 클라이언트 측에 있기 때문이죠. 솔직히 말해서 앱 라우터의 설계상의 결함이라고 할 수 있죠.
284 return;
285 }
286 // ... 데이터 프리 페칭 로직 ...
287}
288```
289
290또한 `loading.tsx`에는 `useQuery` 호출이 포함되어야 합니다. 이렇게 하면 빈 RSC에 대한 네트워크 요청이 발생하는 동안 실제로 필요한 경우 데이터를 가져올 수 있습니다. 실제로 `loading.tsx`의 상태는 실제 클라이언트 컴포넌트 자체일 수 있으며, 그러면 클라이언트 페이지가 표시됩니다.
291
292```tsx filename="src/app/user/[username]/loading.tsx" tint="client"
293"use client";
294export default function PageLoadingSkeleton() {
295 return <ClientPage />;
296}
297```
298
299> 업무에서는 단순히 `loading.tsx` 파일에 `useQuery` 호출을 포함하고 스켈레톤만 표시하도록 만듭니다. Next.js가 실제 서버 컴포넌트를 로드할 때면 어쨌든 페이지 전체가 재마운트되기 때문입니다. 여기서는 VDOM 비교가 발생하지 않으므로, 요청 완료 후 모든 훅(useState)이 약간의 지연후에 초기화됩니다. 기존 DOM만 업데이트하고 상태를 유지하도록 Next.js에 간청하는 간단한 사례를 재현해 보려 했지만, 그렇게 되지 않았습니다. 다행히 빈 RSC 호출에 소요되는 시간은 충분히 짧습니다.
300
301[§2.3]: #layout-restrictions
302
303<Heading
304 level='h3'
305 slug='layout-restrictions'
306>레이아웃은 인위적으로 제한됩니다</Heading>
307
308레이아웃은 데이터를 가져올 수는 있지만, 요청을 어떤 방식으로든 관찰하거나 변경할 수 없습니다. 이는 Next.js가 원하는 때에 레이아웃을 가져오고 캐시할 수 있도록 하기 위함입니다. 다른 모든 프레임워크에서는 레이아웃이 단순히 일반 컴포넌트일 뿐이며 페이지 컴포넌트와 기능상 차이는 없습니다.
309
310레이아웃을 개별적으로 로드하는 아이디어는 매력적이지만 이는 결국 모든 데이터 로드 작업이 레이아웃마다 재실행되어야 한다는 의미라서 어리석은 선택이 됩니다. `QueryClient`를 공유할 수 없습니다. 대신, 그들이 약속한 것처럼 동일한 `GET` 요청을 캐싱하려면 [몽키 패치된 `fetch` 메서드][nextjs-fetch]에 의존해야 합니다.
311
312[nextjs-fetch]: https://nextjs.org/docs/app/api-reference/functions/fetch
313
314동료가 Next.js가 왜 특정 코드를 거부하는지 묻는다면, 기술적 복잡성을 설명하는 건 포기하고 그냥 "Next.js 기술 문제야, 곧 해결할 테니까 걱정 마"라고 말하곤 합니다. 이 규칙들은 일반 개발자들이 이해하기엔 너무 까다롭습니다.
315
316[§2.4]: #rsc-payload
317
318<Heading
319 level='h3'
320 slug='rsc-payload'
321>여전히 모든 콘텐츠를 두 번 다운로드합니다</Heading>
322
323["아일랜드 아키텍처"][islands]와 달리 서버 컴포넌트는 `Suspense` 지원 및 클라이언트 컴포넌트 상태 보존을 위해 프론트엔드에서 여전히 하이드레이션되어야 합니다. 소프트 네비게이션 시 "RSC 페이로드"(HTML이 전혀 아님)는 `fetch`로 가져옵니다. 새로 고침 시 첫 번째 페인트에는 HTML이 필요하지만, 클라이언트 컴포넌트와 `Suspense` 관련 정보는 해당 HTML에 포함되어 있지 않습니다. 리액트의 해결책은 <b>전체 페이지 마크업의 두 번째 사본을 전송하는 것</b>입니다. Next.js 프로덕션 서버가 동적 페이지 렌더링 시 전송하는 예시는 다음과 같습니다.
324
325[islands]: https://www.patterns.dev/vanilla/islands-architecture/
326[first paint]: https://web.dev/articles/fcp
327
328```html filename="GET /user/clover"
329<!DOCTYPE html>
330<html>
331<head>
332 {link and meta tags}
333</head>
334<body>
335 {server side render}
336 <script>
337 // 글로벌 `__next_f`를 배열로 설정하는 부트스트랩 스크립트.
338 // 리액트가 로드되면 이 `.push` 함수가 재정의되어
339 // 새로운 청크를 RSC 디코더에 직접 기록합니다.
340 // 이 스크립트에는 DOM 헬퍼도 포함되어 있습니다.
341 (self.__next_f=self.__next_f||[]).push([0])
342 </script>
343 <script>
344 // 애플리케이션 셸용 RSC 페이로드.
345 self.__next_f.push([1,"1:\"$Sreact.fragment\"\n2:I[658993,[\"/_next/st{...}"])
346 </script>
347
348 <!--
349 닫는 태그 </body>는 아직 작성되지 않았습니다.
350 해결되지 않은 서스펜스 경계가 존재하기 때문입니다.
351 시간이 흐른 후에야 추가 데이터가 작성됩니다.
352 -->
353 <div class="user-post-list">
354 {server side render of a Suspense boundary}
355 </div>
356 <script>
357 // 서스펜스 경계에 대한 RSC 페이로드
358 self.__next_f.push([2,"14:[\"$\",\"div\",null,{\"children\":[[\"$\",\"h4\"{...}"])
359 </script>
360
361 <!-- HTML 및 스크립트 태그는 페이지 전체가 완료될 때까지 반복됩니다 -->
362</body>
363</html>
364```
365
366이 솔루션은 <b>초기 HTML 페이로드의 크기를 두 배로 늘립니다</b>. 하지만 더 나쁜 점은 RSC 페이로드에 JS 문자열 리터럴로 감싸져 있는 JSON이 포함되어 있다는 것입니다. 이는 HTML보다 훨씬 비효율적인 형식입니다. 브로틀리(brotli)로 잘 압축되고 브라우저에서 빠르게 렌더링되는 것처럼 보이지만, 이는 낭비입니다. 하이드레이션 패턴을 사용하면 최소한 로컬 데이터는 상호작용 및 다른 페이지에서 재사용될 수 있습니다.
367
368상호작용이 거의 없거나 전혀 없는 페이지에서도 비용을 지불하게 됩니다. [Next.js 문서](https://nextjs.org/docs)를 예로 들면, 홈페이지 로딩 시 약 750kB(HTML 250kB와 스크립트 태그 500kB)의 페이지가 로드되며, 콘텐츠가 두 번 포함됩니다.
369
370Mac에서는 <kbd>Cmd</kbd> + <kbd>Opt</kbd> + <kbd>u</kbd>를, 다른 플랫폼에서는 <kbd>Ctrl</kbd> + <kbd>u</kbd>를 눌러 확인할 수 있습니다. 그런 다음 <kbd>Cmd</kbd> / <kbd>Ctrl</kbd> + <kbd>f</kbd>를 눌러 블로그의 특정 문자열(예: "풀스택 웹 애플리케이션 구축")을 찾아보세요. 두 번 등장합니다. 이는 리액트 서버 컴포넌트의 핵심 요소이므로 <b>피할 수 없습니다</b>.
371
372이런 RSC 형식은 분명히 더 많은 낭비를 낳습니다. 하지만 정말로 `/_next/static/chunks/6192a3719cda7dcc.js`라는 문자열이 27번이나 따로따로 나타나는 이유를 캐내고 싶진 않네요. 뭐야, 너희들. 대역폭이 공짜냐???
373
374[§2.5]: #turbopack
375
376<Heading
377 level='h3'
378 slug='turbopack'
379>터보팩은 구립니다</Heading>
380
381이 섹션은 건설적이지 않습니다.
382
383- 터보팩은 빠르지 않습니다
384- 터보팩은 디버거에서 디버깅하기 어려운 코드를 생성합니다(개발 모드에서)
385- 터보팩은 많은 경우에 불명확한 오류 메시지를 출력합니다
386
387평소라면 이 점을 블로그에 별도 섹션으로 다루지 않았겠지만, 프로젝트에서 직접 가져온 세 가지 실제 사례를 지적하고자 합니다.
388
389첫 번째는 서버/클라이언트 컴포넌트 모델을 충족시키기 위한 리팩토링 과정에서 실수로 클라이언트 컴포넌트를 `async`로 만든 경우입니다. 이 문제는 문제가 발생한 위치를 전혀 알려주지 않고 <b class='server'>서버</b> 스택 트레이스만 포함하고 있어서 상당히 성가셨습니다.
390
391![Next.js error](/file/2025/blog-everyone-hates-nextjs/asyncerror.png)
392
393끔찍한 오류 메시지의 또 다른 사례입니다.
394
395![Next.js error](/file/2025/blog-everyone-hates-nextjs/nexterror.png)
396
397> 이 두 번째 오류의 근본적인 문제(기억나지 않음)를 수정한 후, 개발 서버가 멈춰서 복구하기 위해 재시작해야 했습니다.
398
399마지막으로, 디버거 중단점을 수십 번 설정할 때마다 변수 이름 `hello`가 `__TURBOPACK__imported__module__$5b$project$5d2f$client$2f$src$2f$utils$2f$filename$2e$ts__$5b$app$2d$client$5d$__$28$ecmascript$29$__["hello"]` 같은 거지같은 이름으로 바뀌는 경우입니다.
400
401네. 이 모든게 구립니다. 어쩌면 좋을까요?
402
403[§3]: #ditching-nextjs
404
405<Heading
406 level='h2'
407 slug='ditching-nextjs'
408>업무에서 Next.js와 Vercel을 매끄럽게 대체하기</Heading>
409
410웹 프로젝트에는 두 가지 유형이 있습니다.
411
412- 주로 정적 콘텐츠로 구성된 웹사이트
413- 주로 동적이고 상호작용적인 컴포넌트를 가진 웹 애플리케이션
414
415Next.js는 이 두 가지 작업 모두에 적합하지 않은 도구입니다. 정적 웹사이트를 만드는 첫 번째 유형에 해당한다면 [Astro]나 [Fresh]를 선택하세요. 리액트의 모든 기능을 필요로 하는 분들을 위해, 이 섹션에서는 벤더에 종속된 Next를 [TanStack Start]로 점진적으로 매끄럽게 교체한 방법을 설명합니다.
416
417[Astro]: https://astro.build/
418[Fresh]: https://fresh.deno.dev/
419
420이 Vite 설정부터 시작했습니다.
421
422```ts filename="vite.config.ts"
423const config = defineConfig(({ mode }) => {
424 const env = loadEnv(mode, process.cwd(), "NEXT_PUBLIC_");
425 return {
426 // Next.js 기본 포트 3000을 사용하세요
427 server: { port: 3000 },
428 // Next.js의 기본 환경 접두사 "NEXT_PUBLIC_"을 사용하십시오.
429 define: Object.fromEntries(Object.entries(env).map(
430 ([k, v]) => [`process.env.${k}`, JSON.stringify(v)])),
431 plugins: [
432 viteTsConfigPaths({ projects: ["./tsconfig.json"] }),
433 tailwindcss(),
434 // 동료들이 이해하기 쉽도록 `src/tanstack-routes`에 있는 라우트를
435 // 포팅하기 시작했습니다. 마이그레이션이 완료되면
436 // 기본 `src/routes`로 되돌아갈 예정입니다.
437 tanstackStart({
438 router: { routesDirectory: "src/tanstack-routes" },
439 }),
440 viteReact(),
441 ],
442 resolve: {
443 // 증분 마이그레이션의 핵심: `next`를 다른 곳으로 리디렉션
444 alias: { next: path.resolve("./src/tanstack-next/") },
445 conditions: ["tanstack"],
446 extensions: [
447 // `utils/session.tanstack.ts`와 같은 이름의 파일이
448 // `utils/session.ts`를 가져올 때 덮어쓸 수 있도록 허용합니다.
449 ".tanstack.tsx", ".tanstack.ts",
450 // 기본 임포트 확장자
451 ".mjs", ".js", ".mts", ".ts",
452 ".jsx", ".tsx", ".json",
453 ],
454 },
455 };
456});
457```
458
459그런 다음 Next.js API의 모든 사용처를 찾아 제거하거나 TanStack용 스텁을 만들었습니다. 예를 들어, `src/tanstack-next/link.tsx`는 `next/link`를 구현합니다.
460
461```tsx filename="src/tanstack-next/link.tsx"
462import { Link } from "@tanstack/react-router";
463import type { LinkProps } from "next/link";
464
465export default function LinkAdapter({ href, ...rest }: LinkProps) {
466 return <Link {...rest} to={href as unknown as any} />;
467}
468```
469
470> 이러한 스텁 중 일부는 매우 간단할 수 있습니다. 처음 시작했을 때, 제가 구현한 `useRouter`는 단순히 `return {}`였지만, 나중에 객체에 몇 가지 메서드를 추가해야 했습니다. 여기 코드 자체는 임시적인 것이므로 깔끔할 필요가 없습니다.
471
472이제 새 사이트는 필요한 Next.js API를 스터빙하거나 `.tanstack.ts` 확장자를 사용해 파일별로 로직을 재구현함으로써 거의 모든 클라이언트 컴포넌트를 가져올 수 있습니다. 그리고 얼마 지나지 않아 TanStack Start에서 사이트 홈페이지를 작동시키는 데 성공했고, 해당 브랜치를 병합했습니다.
473
474![My "nextgate" PR](/file/2025/blog-everyone-hates-nextjs/pr.png)
475
476> 이 첫 번째 PR은 우리 페이지 중 하나만을 지원했으며, 추가된 코드 천 줄과 삭제된 코드 40줄로 이를 수행할 수 있었습니다. 저는 이전 패치를 통해 `next/image` 및 `next/font`의 몇 가지 사용 사례를 제거한 적이 있습니다.
477
478남은 작업은 다른 모든 경로를 포팅하는 것이었습니다. Next.js에서 다른 프레임워크로 마이그레이션할 때 잃게 되는 한 가지는 UI에서 데이터 페칭 함수를 `await`할 수 있는 기능입니다. 실제로 모든 경로를 `loader` 함수로 이동시키면 페이지가 서버 측 렌더링(SSR)될 때 어떤 일이 발생하는지 훨씬 명확해졌습니다. 여러 번의 데이터 가져오기가 필요한 페이지의 경우, 해당 페이지에 필요한 모든 관련 데이터를 반환하는 단일 특수 API 호출로 통합할 수 있었습니다.
479
480다시 한번 강조하자면, <strong style='color:var(--secondary)'>서버 컴포넌트에서의 마이그레이션 경로는 단순히 코드를 단순화하는 것입니다. RSC는 본질적으로 필요 없는 것들로 가득한 혼란스러운 길로 이끌어갑니다.</strong> 우리 사이트의 거의 모든 복잡한 부분이 모든 엔지니어에게 이해하기 쉬워졌습니다. 유일한 예외는 모두가 새로운 파일 시스템 라우팅 규칙에 익숙해져야 했던 점입니다. 충분한 예시를 통해 우리 모두 그 요령을 터득했습니다.
481
482증분 마이그레이션을 통해 신규 코드가 기존 배포를 방해하지 않았습니다. TanStack이 점진적으로 코드베이스를 인수하면서, 결국 모든 Next.js 스텁을 삭제하고 TanStack 라우터가 제공하는 모든 우수한 [타입 안전성 기능]을 확보했습니다. 최종적으로 사이트는 모든 측면에서 더 빠른 성능을 보였습니다: 개발 모드, 프로덕션 페이지 로딩 시간, 소프트 네비게이션, 그리고 Vercel을 사용한 Next 배포보다 저렴한 비용으로 구현되었습니다.
483
484[타입 안전성 기능]: https://tanstack.com/router/v1/docs/framework/react/guide/type-safety
485
486변화를 목격하는 건 우리만이 아닙니다. 저는 소셜 미디어를 멀리하려 노력하지만, 누군가 [브라이언 앵글린(Brian Anglin)이 Superwall에서 진행한 작업 결과][superwall-twitter]를 보내왔는데, TanStack Start에서 CPU 사용량이 엄청나게 감소한 걸 보여주더군요. 또한 1년 전 ChatGPT가 Next.js에서 Remix로 전환한 일도 기억납니다(관련 온라인 대화: [[1][chatgpt-1]] [[2][chatgpt-2]] [[3][chatgpt-3]]).
487
488[superwall-twitter]: https://twitter.com/BriansAngles/status/1978834116079436242#m
489[chatgpt-1]: https://xcancel.com/ryanflorence/status/1831379475654947233
490[chatgpt-2]: https://old.reddit.com/r/reactjs/comments/1f97zgr/chatgpt_migrates_from_nextjs_to_remix
491[chatgpt-3]: https://old.reddit.com/r/nextjs/comments/1f92jdv/chatgptcom_switched_from_nextjs_to_remix
492
493[§3.1]: #next-metadata
494
495<Heading
496 level='h3'
497 slug='next-metadata'
498><code>next/metadata</code>는 훌륭합니다</Heading>
499
500제 생각에 이건 Next.js가 가진 몇 안 되는 좋은 API 중 하나이며, TanStack으로 전환하면서 코드에서 유일하게 작업이 더 어려워진 부분이었습니다. 코드를 악화시키기보다는, 그냥 그들의 메타데이터 API를 일반 함수로 포팅해서 누구나 사용할 수 있게 했습니다. 원래는 NPM에 1:1 포팅 버전을 올렸지만, 올해 초에 API를 간결하고 이해하기 쉬운 하나의 파일로 단순화했습니다. 이 블로그 글 작성 시점 기준으로, TanStack 호환 `meta.toTags` API를 추가했으며, [JSR][lib-jsr]이나 [NPM][lib-npm]에서 설치하거나 단순히 프로젝트에 복사해서 사용할 수 있습니다.
501
502> 공지: 본 글 작성에 시간이 부족하여 라이브러리는 아직 업데이트되지 않았습니다. 아마도 ~~이번 주 말(10월 24일)~~ 가까운 시일 내에 처리할 예정입니다... 임시로 업무용으로 사용 중인 버전을 제 웹사이트에 공유합니다. [`meta.tanstack.ts`](https://paperclover.net/file/2025/blog-everyone-hates-nextjs/meta.tanstack.ts)
503
504```tsx
505// 프로젝트에 한 번만
506import * as meta from "@clo/lib/meta.ts";
507
508export const defineHead = meta.toTags.bind(null, {
509 // 사이트 전체 옵션
510 base: new URL("https://paperclover.net"),
511 titleTemplate: (title) => [title, "paper clover"]
512 .filter(Boolean).join(' | '),
513 // ...
514});
515
516// 각 페이지마다...
517export const Route = createFileRoute("/blog")({
518 head: () =>
519 defineHead({
520 title: "clover's blog", // `titleTemplate`으로 템플릿 처리됨
521 description: "a catgirl meows about her technology viewpoints",
522 canonical: "/blog", // `base`와 결합됨
523
524 // 지정 시, 페이지 제목과 설명을 기본값으로 사용하여
525 // Open Graph 및 Twitter 임베드를 구성합니다.
526 // 기본값도 괜찮지만, 더 많은 옵션을 지원합니다.
527 embed: {
528 image: "/img/blog.webp",
529 },
530
531 // 모든 특수 메타 태그는 JSX 프래그먼트로 처리됩니다.
532 // 이는 리액트 렌더링하지 않고 단순히 태그를 순회합니다.
533 // 제 목표는 가장 흔한 99%의 사용 사례를 커버하는 것이었습니다.
534 extra: <>
535 <meta name="site-verification" content="waffles" />,
536 </>,
537 }),
538
539 component: Page,
540});
541
542function Page() {
543 ...
544}
545```
546
547제 버전은 Next.js 메타데이터 객체의 전체 영역을 커버하는 데 초점을 두지 않았으며, 대신 인라인 JSX를 사용하여 그 공백을 메웠습니다.
548
549[lib-jsr]: https://jsr.io/@clo/lib
550[lib-npm]: https://npmjs.com/@paperclover/lib
551
552[§3.2]: #vercel-og
553
554<Heading
555 level='h3'
556 slug='ditching-nextjs'
557><code>next/og</code> 또한 좋습니다</Heading>
558
559특별한 의견은 없습니다. 그냥 `@vercel/og` 패키지가 있다는 점을 모두에게 상기시키고 싶을 뿐입니다.
560
561[§4]: #experience-feels-like-the-usual
562
563<Heading
564 level='h2'
565 slug='experience-feels-like-the-usual'
566>제 경험은 일반적인 것 같아요</Heading>
567
568Next.js Conf 2024에서 참석자 모두가 서버 컴포넌트(Server Components)에 열광했습니다. 정확히 누구와 이야기했는지는 기억나지 않지만, 주요 인사들은 모두 이 기술에 주목하고 있었습니다. RSC의 번들러 부분을 구현한 저는 이 포맷의 몇 가지 문제점을 목격했습니다. Next 15가 지난해 App Router를 "안정화"하면서 많은 기업들이 이를 기반으로 제품을 구축하고 있으며, 이러한 문제점들을 직접 경험하고 있습니다.
569
570저는 Next.js를 늦게 접하게 되어 6월에야 버전 15로 시작했습니다. 하지만 행사에서 만난 모든 분들이 제 의견에 공감해 주셨습니다. Bun의 1.3 파티에서 이 주제로 이야기한 분들도 모두 동의하셨죠. 심지어 Vercel의 몇몇 분들조차 Next.js의 실제 사용 방식이 마음에 들지 않는다고 말씀하셨습니다.
571
572TanStack Start가 안정화되면서 모두가 원하는 Next.js 대체 솔루션이 되길 바랍니다.
573
574[§5]: #prefer-respectful-tools
575
576<Heading
577 level='h2'
578 slug='prefer-respectful-tools'
579>사용자를 존중하는 도구를 선택하세요</Heading>
580
581자바스크립트 생태계는 상당 부분 엉망이고, 그 때문인지 웹 개발은 종종 조롱의 대상이 됩니다.. 저는 웹 작업을 회복 불가능한 난장판이라고 생각했던 적이 수없이 많았지만, 그 혼란은 사실 제가 둘러싸고 있던 흔히 쓰이는 라이브러리들 때문이었습니다. 그 껍질을 벗겨내면 현대 웹 개발 기술은 정말 대단합니다.
582
5832024년 말부터 프레임워크 없이 이 웹사이트를 처음부터 직접 제작해 왔습니다. 자체 개발한 [TUI 진행률 위젯][progress], [정적 파일 프록시][file-cache], 증분 빌드 시스템 등 다양한 컴포넌트를 직접 구현하며 작업했습니다. 지난 수년간 가장 행복한 코딩 경험이었습니다. *[페이퍼 클로버]* 방문자들은 더 나은 품질의 웹사이트를 이용하게 되고, 제가 만든 미니 라이브러리는 [공개적으로 활용될 수 있게 추출됩니다][lib]. 덕분에 모두가 이득을 보게 되었죠.
584
585이 정도로 완전히 처음부터 시작하는 방식은 대부분의 사람들에게, 특히 직장에서는 부담스럽습니다. 최소한 우리를 존중하는 고품질 도구에만 우리의 관심과 돈을 쏟아야 한다고 생각합니다. 그리고 Next.js와 그 배후 기업인 Vercel은 그렇지 않습니다.
586
587Next.js를 사용하면서 개발자로서 존중받는다는 느낌이 들지 않는다면, 과연 여러분과 동료들이 그들의 [서버리스 제국]을 계속 지지하고 싶은지 고민해 보세요. 현재 Vite 생태계는 구축하기에 꽤 괜찮아 보이지만, 아직 대규모 프로덕션 환경에서 그들의 도구를 사용해 본 경험은 거의 없습니다. [Void0의 Vite+ 출시 소식][vite-plus]은 흥미롭지만, 이러한 벤처 수준의 지원 도구가 장기적으로 우리(최종 사용자와 개발자)를 존중할지는 시간이 지나야 알 수 있을 것입니다.
588
589Next.js Conf 2025는 글을 쓰는 시점 기준으로 [내일][next-conf]입니다. 800달러짜리 티켓을 구매하는 대신, 웹 개발 생태계를 [존중하고 개선하는][tanstack-ethos] [TanStack 팀에 그 돈을 기부하기로 결정했습니다][tanstack-donate].
590
591[페이퍼 클로버]: https://paperclover.net/
592[lib]: https://git.paperclover.net/clo/sitegen/src/branch/master/lib#readme
593[progress]: https://git.paperclover.net/clo/sitegen/src/branch/master/lib/progress.ts
594[file-cache]: https://git.paperclover.net/clo/sitegen/src/branch/master/src/file-viewer/cache.ts
595
596[next-conf]: https://nextjs.org/conf
597[vite-plus]: https://viteplus.dev/
598[tanstack-ethos]: https://tanstack.com/ethos
599[tanstack-donate]: https://github.com/sponsors/tannerlinsley
600[serverless empire]: https://youtu.be/SCIfWhAheVw
601
602## 앞으로의 계획
603
604점진적으로, 저는 저를 존중하지 않는 많은 소프트웨어들을 더 나은 대안으로 교체해 왔습니다. 그 예로는 GitHub, Visual Studio Code, DaVinci Resolve, Discord, Google Drive/Workspace 등이 있으며, 그 외에도 많습니다. 이 블로그에서는 제가 진행 중인 기술적 작업들(프로그레스 라이브러리, 자체 사이트 생성기의 목적, 현재 직장에서의 배움)과 Bun에서의 과거 프로젝트들(HMR, 크래시 리포터, 내장 모듈 번들링을 위한 독특한 시스템에 대한 세부 사항)에 대해 더 많이 다루려 합니다. 관심이 있으시다면 이메일 리스트에 가입해 주세요.
605
606<hr />
607
608<a href="mailto:subscribe@paperclover.net?subject=paper%20clover%20mailing%20list&body=I%20would%20like%20to%20be%20subscribed%20to%20the%20following%20mailing%20lists%3A%0A%0A-%20Technical%20Blog%20Posts%20-%20YES%0A-%20Art%20(Original%20Music%2FVideo)%20-%20YES%0A%0A(feel%20free%20to%20write%20whatever%20else%20you%20want)">click here to send an email to <code>subscribe@paperclover.net</code>, requesting that you would like to be added to the mailing list.</a> (i manage this mailing list manually)
609
610[↑](#top) &mdash; [ask a question about this article (English)](/q+a)
611
612<br />
613<br />
614<br />
615<br />
616<br />
617<br />
618<footer>
6192025 (c) paper clover
620</footer>
621
622</Layout>
623
624<br />
src/blog/tags/blog-layout.marko created+107
......@@ -0,0 +1,107 @@
1import "../blog.css";
2export const theme = {
3 bg: "#271a30",
4 fg: "#ffffff",
5 primary: "#91ffc6",
6};
7export const components = {
8 heading: Heading,
9 codeBlock: CodeBlock
10};
11
12export interface Module {
13 meta: {
14 title: string;
15 description: string;
16 keywords: string[];
17 canonical: string;
18 };
19 date: string;
20 content: render.Node;
21 translation?: {
22 lang: string;
23 author: string;
24 href: string;
25 date: string;
26 };
27}
28export interface Input {
29 module: Module;
30}
31
32export interface Context {
33 outline: markodown.Heading[];
34}
35
36export const markoOnly = true;
37
38<const/module: Module = input.module />
39<const/{ date, translation } = module />
40<const/{ title, description, keywords, canonical } = module.meta />
41<const/slug = canonical.replace(/^\/blog\/|\.[a-zA-Z_-]+$/g, '') />
42
43<const/translations=
44 Path.resolve(import.meta.dirname, "../pages/" + slug).readDirSync()
45 .filter((x) => x.ext === ".mdo" && x.baseWithoutExt !== "en")
46 .map((x) => x.baseWithoutExt) />
47<const/t = UNWRAP(localization.languages[translation?.lang ?? "en"]) />
48
49<const/_=(() => {
50 $global.cloverOutline = input.outline;
51})() />
52
53<main>
54<header>
55<a href="/">${t.return}</a>
56
57<h1 style={ 'max-width': "30ch" }>${title}</>
58<p class="description"><em>${description}</em></p>
59
60<div.meta>
61 <a class="meta-author" href="/">${t.writtenByClover}</a>
62 <span>•</span>
63 <if=translation>
64 <a.meta-translate href=translation.href>
65 ${t.translatedBy(translation)}
66 </a>
67 <span>•</span>
68 </>
69 <span.meta-date>${date}</span>
70</div>
71<div.tag-list>
72 <if=translations.length>
73 <a
74 class=["custom tag lang lang-original", { active: !translation }]
75 href=`/blog/${slug}`
76 >
77 ${localization.languages.en.title}
78 </a>
79 <for|code| of=translations>
80 <a
81 class=["custom tag lang", { active: code === translation?.lang }]
82 href=`/blog/${slug}.${code}`
83 >
84 ${UNWRAP(localization.languages[code]).title}
85 </a>
86 </>
87 <a.custom.tag.square href="/blog/community-translations">${"+"}</a>
88 <span.bar />
89 </>
90 <for|tag| of=keywords>
91 <div.tag>${tag}</div>
92 </>
93</div>
94
95<hr />
96</header>
97
98<${input.content} />
99
100</main>
101
102import * as localization from "../localization.tsx";
103import * as markodown from "@clo/markodown";
104import Heading from "./heading.marko";
105import CodeBlock from "./code-block.marko";
106import { Path } from "#sitegen/path";
107import { UNWRAP } from "lib/assert.ts";
src/blog/tags/code-block.marko created+15
......@@ -0,0 +1,15 @@
1<const/{ filename, language, tint, whitespace, content } = input />
2
3<const/highlightedPromise = ["tsx", "jsx", "json", "html", "ts", "js"].includes(language)
4 ? highlight.highlightCode(content.trim(), language)
5 : content.trim()
6/>
7
8<figure class=["code", tint]>
9<if=filename><figcaption>${filename}</figcaption></>
10<await|highlighted|=highlightedPromise>
11 <pre style=whitespace ? { "white-space": whitespace } : {}><code>$!{highlighted}</code></pre>
12</>
13</figure>
14
15import * as highlight from "@/file-viewer/highlight.ts";
src/blog/tags/heading.marko created+5
......@@ -0,0 +1,5 @@
1<const/{ level, content, ...attrs } = input />
2<${'h' + level} ...attrs>
3 <a.return href="#toc">§</a>
4 <${content} />
5</>
src/blog/tags/heading.tsx deleted-10
......@@ -1,10 +0,0 @@
1export default function ({ level: Level, slug, content }) {
2 return (
3 <>
4 <Level id={slug}>
5 <a class="return" href="#toc">§</a>
6 {content}
7 </Level>
8 </>
9 );
10}
src/blog/tags/layout.tsx deleted-106
......@@ -1,106 +0,0 @@
1import "../blog.css";
2import { Path } from "#sitegen/path";
3
4export const theme = {
5 bg: "#271a30",
6 fg: "#ffffff",
7 primary: "#91ffc6",
8};
9
10interface LayoutProps {
11 meta: { title: string; description: string; keywords: string[] };
12 date: string;
13 slug: string;
14 content: render.Node;
15 translation?: {
16 lang: string;
17
18 author: string;
19 href: string;
20
21 date: string;
22 };
23}
24
25export async function Layout({
26 meta: { title, description, keywords },
27 date,
28 slug,
29 content,
30 translation,
31}: LayoutProps) {
32 const translations =
33 (await Path.resolve(import.meta.dirname, "../pages/" + slug).readDir())
34 .filter((x) => x.ext === ".mdx" && x.baseWithoutExt !== "en")
35 .map((x) => x.baseWithoutExt);
36 const t = UNWRAP(localization.languages[translation?.lang ?? "en"]);
37 return (
38 <>
39 <main>
40 <header>
41 <a href="/">{t.return}</a>
42
43 <h1 style="max-width: 30ch">{title}</h1>
44 <p class="description">
45 <em>{description}</em>
46 </p>
47 <div class="meta">
48 <a class="meta-author" href="/">{t.writtenByClover}</a>
49 <span>•</span>
50 {translation
51 ? (
52 <>
53 <a href={translation.href} class="meta-translate">
54 {t.translatedBy(translation)}
55 </a>
56 <span>•</span>
57 </>
58 )
59 : ""}
60 <span class="meta-date">{date}</span>
61 </div>
62 <div class="tag-list">
63 {translations.length > 0
64 ? (
65 <>
66 <a
67 href={`/blog/${slug}`}
68 class={["custom", "tag", "lang", "lang-original", {
69 active: !translation,
70 }]}
71 >
72 {localization.languages.en.title}
73 </a>
74 {translations.map((code) => (
75 <a
76 class={["custom", "tag", "lang", {
77 active: code === translation?.lang,
78 }]}
79 href={`/blog/${slug}.${code}`}
80 >
81 {UNWRAP(localization.languages[code]).title}
82 </a>
83 ))}
84 <a
85 href="/blog/community-translations"
86 class="custom tag square"
87 >
88 +
89 </a>
90 <span class="bar"></span>
91 </>
92 )
93 : ""}
94 {keywords.map((tag) => <div class="tag">{tag}</div>)}
95 </div>
96 <hr />
97 </header>
98 {content}
99 </main>
100 </>
101 );
102}
103
104import * as localization from "../localization.tsx";
105import { UNWRAP } from "lib/assert.ts";
106import * as render from "lib/render.ts";
src/blog/tags/table-of-contents.marko created+29
......@@ -0,0 +1,29 @@
1import "./table-of-contents.css";
2
3export interface Node {
4 heading: markodown.Heading;
5 children: Node[];
6}
7
8<const/outline = $global.cloverOutline />
9<const/groups = markodown.outlineToTree(outline) />
10
11<define/Recurse|input: markodown.HeadingTree|>
12 <a href=`#${input.id}`><${input.content} /></a>
13 <if=input.children.length>
14 <ul>
15 <for|item| of=input.children>
16 <li><Recurse ...item /></li>
17 </>
18 </ul>
19 </>
20</>
21
22<div#toc>
23 <h2>Contents:</h2>
24 <ul><for|item| of=groups>
25 <li><Recurse ...item /></li>
26 </></ul>
27</div>
28
29import * as markodown from "@clo/markodown";
src/blog/tags/table-of-contents.tsx deleted-10
......@@ -1,10 +0,0 @@
1import "./table-of-contents.css";
2
3export default function ({ content }) {
4 return (
5 <div id="toc">
6 <h2>Contents:</h2>
7 {content}
8 </div>
9 );
10}
src/file-viewer/highlight-grammar/mdx.plist deleted-9527
......@@ -1,9527 +0,0 @@
1<?xml version="1.0" encoding="UTF-8"?>
2<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3<plist version="1.0">
4 <dict>
5 <key>fileTypes</key>
6 <array>
7 <string>mdx</string>
8 </array>
9 <key>name</key>
10 <string>MDX</string>
11 <key>patterns</key>
12 <array>
13 <dict>
14 <key>include</key>
15 <string>#markdown-frontmatter</string>
16 </dict>
17 <dict>
18 <key>include</key>
19 <string>#markdown-sections</string>
20 </dict>
21 </array>
22 <key>repository</key>
23 <dict>
24 <key>markdown-frontmatter</key>
25 <dict>
26 <key>patterns</key>
27 <array>
28 <dict>
29 <key>include</key>
30 <string>#extension-toml</string>
31 </dict>
32 <dict>
33 <key>include</key>
34 <string>#extension-yaml</string>
35 </dict>
36 </array>
37 </dict>
38 <key>markdown-sections</key>
39 <dict>
40 <key>patterns</key>
41 <array>
42 <dict>
43 <key>include</key>
44 <string>#commonmark-block-quote</string>
45 </dict>
46 <dict>
47 <key>include</key>
48 <string>#commonmark-code-fenced</string>
49 </dict>
50 <dict>
51 <key>include</key>
52 <string>#extension-gfm-footnote-definition</string>
53 </dict>
54 <dict>
55 <key>include</key>
56 <string>#commonmark-definition</string>
57 </dict>
58 <dict>
59 <key>include</key>
60 <string>#commonmark-heading-atx</string>
61 </dict>
62 <dict>
63 <key>include</key>
64 <string>#commonmark-thematic-break</string>
65 </dict>
66 <dict>
67 <key>include</key>
68 <string>#commonmark-heading-setext</string>
69 </dict>
70 <dict>
71 <key>include</key>
72 <string>#commonmark-list-item</string>
73 </dict>
74 <dict>
75 <key>include</key>
76 <string>#extension-gfm-table</string>
77 </dict>
78 <dict>
79 <key>include</key>
80 <string>#extension-math-flow</string>
81 </dict>
82 <dict>
83 <key>include</key>
84 <string>#extension-mdx-esm</string>
85 </dict>
86 <dict>
87 <key>include</key>
88 <string>#extension-mdx-expression-flow</string>
89 </dict>
90 <dict>
91 <key>include</key>
92 <string>#extension-mdx-jsx-flow</string>
93 </dict>
94 <dict>
95 <key>include</key>
96 <string>#commonmark-paragraph</string>
97 </dict>
98 </array>
99 </dict>
100 <key>markdown-string</key>
101 <dict>
102 <key>patterns</key>
103 <array>
104 <dict>
105 <key>include</key>
106 <string>#commonmark-character-escape</string>
107 </dict>
108 <dict>
109 <key>include</key>
110 <string>#commonmark-character-reference</string>
111 </dict>
112 </array>
113 </dict>
114 <key>markdown-text</key>
115 <dict>
116 <key>patterns</key>
117 <array>
118 <dict>
119 <key>include</key>
120 <string>#commonmark-attention</string>
121 </dict>
122 <dict>
123 <key>include</key>
124 <string>#commonmark-character-escape</string>
125 </dict>
126 <dict>
127 <key>include</key>
128 <string>#commonmark-character-reference</string>
129 </dict>
130 <dict>
131 <key>include</key>
132 <string>#commonmark-code-text</string>
133 </dict>
134 <dict>
135 <key>include</key>
136 <string>#commonmark-hard-break-trailing</string>
137 </dict>
138 <dict>
139 <key>include</key>
140 <string>#commonmark-hard-break-escape</string>
141 </dict>
142 <dict>
143 <key>include</key>
144 <string>#commonmark-label-end</string>
145 </dict>
146 <dict>
147 <key>include</key>
148 <string>#extension-gfm-footnote-call</string>
149 </dict>
150 <dict>
151 <key>include</key>
152 <string>#commonmark-label-start</string>
153 </dict>
154 <dict>
155 <key>include</key>
156 <string>#extension-gfm-autolink-literal</string>
157 </dict>
158 <dict>
159 <key>include</key>
160 <string>#extension-gfm-strikethrough</string>
161 </dict>
162 <dict>
163 <key>include</key>
164 <string>#extension-github-gemoji</string>
165 </dict>
166 <dict>
167 <key>include</key>
168 <string>#extension-github-mention</string>
169 </dict>
170 <dict>
171 <key>include</key>
172 <string>#extension-github-reference</string>
173 </dict>
174 <dict>
175 <key>include</key>
176 <string>#extension-math-text</string>
177 </dict>
178 <dict>
179 <key>include</key>
180 <string>#extension-mdx-expression-text</string>
181 </dict>
182 <dict>
183 <key>include</key>
184 <string>#extension-mdx-jsx-text</string>
185 </dict>
186 </array>
187 </dict>
188 <key>commonmark-attention</key>
189 <dict>
190 <key>patterns</key>
191 <array>
192 <dict>
193 <key>match</key>
194 <string>(?&lt;=\S)\*{3,}|\*{3,}(?=\S)</string>
195 <key>name</key>
196 <string>string.other.strong.emphasis.asterisk.mdx</string>
197 </dict>
198 <dict>
199 <key>match</key>
200 <string>(?&lt;=[\p{L}\p{N}])_{3,}(?![\p{L}\p{N}])|(?&lt;=\p{P})_{3,}|(?&lt;![\p{L}\p{N}]|\p{P})_{3,}(?!\s)</string>
201 <key>name</key>
202 <string>string.other.strong.emphasis.underscore.mdx</string>
203 </dict>
204 <dict>
205 <key>match</key>
206 <string>(?&lt;=\S)\*{2}|\*{2}(?=\S)</string>
207 <key>name</key>
208 <string>string.other.strong.asterisk.mdx</string>
209 </dict>
210 <dict>
211 <key>match</key>
212 <string>(?&lt;=[\p{L}\p{N}])_{2}(?![\p{L}\p{N}])|(?&lt;=\p{P})_{2}|(?&lt;![\p{L}\p{N}]|\p{P})_{2}(?!\s)</string>
213 <key>name</key>
214 <string>string.other.strong.underscore.mdx</string>
215 </dict>
216 <dict>
217 <key>match</key>
218 <string>(?&lt;=\S)\*|\*(?=\S)</string>
219 <key>name</key>
220 <string>string.other.emphasis.asterisk.mdx</string>
221 </dict>
222 <dict>
223 <key>match</key>
224 <string>(?&lt;=[\p{L}\p{N}])_(?![\p{L}\p{N}])|(?&lt;=\p{P})_|(?&lt;![\p{L}\p{N}]|\p{P})_(?!\s)</string>
225 <key>name</key>
226 <string>string.other.emphasis.underscore.mdx</string>
227 </dict>
228 </array>
229 </dict>
230 <key>commonmark-block-quote</key>
231 <dict>
232 <key>begin</key>
233 <string>(?:^|\G)[\t ]*(&gt;)[ ]?</string>
234 <key>beginCaptures</key>
235 <dict>
236 <key>0</key>
237 <dict>
238 <key>name</key>
239 <string>markup.quote.mdx</string>
240 </dict>
241 <key>1</key>
242 <dict>
243 <key>name</key>
244 <string>punctuation.definition.quote.begin.mdx</string>
245 </dict>
246 </dict>
247 <key>patterns</key>
248 <array>
249 <dict>
250 <key>include</key>
251 <string>#markdown-sections</string>
252 </dict>
253 </array>
254 <key>name</key>
255 <string>markup.quote.mdx</string>
256 <key>while</key>
257 <string>(&gt;)[ ]?</string>
258 <key>whileCaptures</key>
259 <dict>
260 <key>0</key>
261 <dict>
262 <key>name</key>
263 <string>markup.quote.mdx</string>
264 </dict>
265 <key>1</key>
266 <dict>
267 <key>name</key>
268 <string>punctuation.definition.quote.begin.mdx</string>
269 </dict>
270 </dict>
271 </dict>
272 <key>commonmark-character-escape</key>
273 <dict>
274 <key>match</key>
275 <string>\\(?:[!"#$%&amp;'()*+,\-.\/:;&lt;=&gt;?@\[\\\]^_`{|}~])</string>
276 <key>name</key>
277 <string>constant.language.character-escape.mdx</string>
278 </dict>
279 <key>commonmark-character-reference</key>
280 <dict>
281 <key>patterns</key>
282 <array>
283 <dict>
284 <key>include</key>
285 <string>#whatwg-html-data-character-reference-named-terminated</string>
286 </dict>
287 <dict>
288 <key>match</key>
289 <string>(&amp;)(#)([Xx])([0-9A-Fa-f]{1,6})(;)</string>
290 <key>name</key>
291 <string>constant.language.character-reference.numeric.hexadecimal.html</string>
292 <key>captures</key>
293 <dict>
294 <key>1</key>
295 <dict>
296 <key>name</key>
297 <string>punctuation.definition.character-reference.begin.html</string>
298 </dict>
299 <key>2</key>
300 <dict>
301 <key>name</key>
302 <string>punctuation.definition.character-reference.numeric.html</string>
303 </dict>
304 <key>3</key>
305 <dict>
306 <key>name</key>
307 <string>punctuation.definition.character-reference.numeric.hexadecimal.html</string>
308 </dict>
309 <key>4</key>
310 <dict>
311 <key>name</key>
312 <string>constant.numeric.integer.hexadecimal.html</string>
313 </dict>
314 <key>5</key>
315 <dict>
316 <key>name</key>
317 <string>punctuation.definition.character-reference.end.html</string>
318 </dict>
319 </dict>
320 </dict>
321 <dict>
322 <key>match</key>
323 <string>(&amp;)(#)([0-9]{1,7})(;)</string>
324 <key>name</key>
325 <string>constant.language.character-reference.numeric.decimal.html</string>
326 <key>captures</key>
327 <dict>
328 <key>1</key>
329 <dict>
330 <key>name</key>
331 <string>punctuation.definition.character-reference.begin.html</string>
332 </dict>
333 <key>2</key>
334 <dict>
335 <key>name</key>
336 <string>punctuation.definition.character-reference.numeric.html</string>
337 </dict>
338 <key>3</key>
339 <dict>
340 <key>name</key>
341 <string>constant.numeric.integer.decimal.html</string>
342 </dict>
343 <key>4</key>
344 <dict>
345 <key>name</key>
346 <string>punctuation.definition.character-reference.end.html</string>
347 </dict>
348 </dict>
349 </dict>
350 </array>
351 </dict>
352 <key>commonmark-code-fenced</key>
353 <dict>
354 <key>patterns</key>
355 <array>
356 <dict>
357 <key>include</key>
358 <string>#commonmark-code-fenced-apib</string>
359 </dict>
360 <dict>
361 <key>include</key>
362 <string>#commonmark-code-fenced-asciidoc</string>
363 </dict>
364 <dict>
365 <key>include</key>
366 <string>#commonmark-code-fenced-c</string>
367 </dict>
368 <dict>
369 <key>include</key>
370 <string>#commonmark-code-fenced-clojure</string>
371 </dict>
372 <dict>
373 <key>include</key>
374 <string>#commonmark-code-fenced-coffee</string>
375 </dict>
376 <dict>
377 <key>include</key>
378 <string>#commonmark-code-fenced-console</string>
379 </dict>
380 <dict>
381 <key>include</key>
382 <string>#commonmark-code-fenced-cpp</string>
383 </dict>
384 <dict>
385 <key>include</key>
386 <string>#commonmark-code-fenced-cs</string>
387 </dict>
388 <dict>
389 <key>include</key>
390 <string>#commonmark-code-fenced-css</string>
391 </dict>
392 <dict>
393 <key>include</key>
394 <string>#commonmark-code-fenced-diff</string>
395 </dict>
396 <dict>
397 <key>include</key>
398 <string>#commonmark-code-fenced-dockerfile</string>
399 </dict>
400 <dict>
401 <key>include</key>
402 <string>#commonmark-code-fenced-elixir</string>
403 </dict>
404 <dict>
405 <key>include</key>
406 <string>#commonmark-code-fenced-elm</string>
407 </dict>
408 <dict>
409 <key>include</key>
410 <string>#commonmark-code-fenced-erlang</string>
411 </dict>
412 <dict>
413 <key>include</key>
414 <string>#commonmark-code-fenced-gitconfig</string>
415 </dict>
416 <dict>
417 <key>include</key>
418 <string>#commonmark-code-fenced-go</string>
419 </dict>
420 <dict>
421 <key>include</key>
422 <string>#commonmark-code-fenced-graphql</string>
423 </dict>
424 <dict>
425 <key>include</key>
426 <string>#commonmark-code-fenced-haskell</string>
427 </dict>
428 <dict>
429 <key>include</key>
430 <string>#commonmark-code-fenced-html</string>
431 </dict>
432 <dict>
433 <key>include</key>
434 <string>#commonmark-code-fenced-ini</string>
435 </dict>
436 <dict>
437 <key>include</key>
438 <string>#commonmark-code-fenced-java</string>
439 </dict>
440 <dict>
441 <key>include</key>
442 <string>#commonmark-code-fenced-js</string>
443 </dict>
444 <dict>
445 <key>include</key>
446 <string>#commonmark-code-fenced-json</string>
447 </dict>
448 <dict>
449 <key>include</key>
450 <string>#commonmark-code-fenced-julia</string>
451 </dict>
452 <dict>
453 <key>include</key>
454 <string>#commonmark-code-fenced-kotlin</string>
455 </dict>
456 <dict>
457 <key>include</key>
458 <string>#commonmark-code-fenced-less</string>
459 </dict>
460 <dict>
461 <key>include</key>
462 <string>#commonmark-code-fenced-less</string>
463 </dict>
464 <dict>
465 <key>include</key>
466 <string>#commonmark-code-fenced-lua</string>
467 </dict>
468 <dict>
469 <key>include</key>
470 <string>#commonmark-code-fenced-makefile</string>
471 </dict>
472 <dict>
473 <key>include</key>
474 <string>#commonmark-code-fenced-md</string>
475 </dict>
476 <dict>
477 <key>include</key>
478 <string>#commonmark-code-fenced-mdx</string>
479 </dict>
480 <dict>
481 <key>include</key>
482 <string>#commonmark-code-fenced-objc</string>
483 </dict>
484 <dict>
485 <key>include</key>
486 <string>#commonmark-code-fenced-perl</string>
487 </dict>
488 <dict>
489 <key>include</key>
490 <string>#commonmark-code-fenced-php</string>
491 </dict>
492 <dict>
493 <key>include</key>
494 <string>#commonmark-code-fenced-php</string>
495 </dict>
496 <dict>
497 <key>include</key>
498 <string>#commonmark-code-fenced-python</string>
499 </dict>
500 <dict>
501 <key>include</key>
502 <string>#commonmark-code-fenced-r</string>
503 </dict>
504 <dict>
505 <key>include</key>
506 <string>#commonmark-code-fenced-raku</string>
507 </dict>
508 <dict>
509 <key>include</key>
510 <string>#commonmark-code-fenced-ruby</string>
511 </dict>
512 <dict>
513 <key>include</key>
514 <string>#commonmark-code-fenced-rust</string>
515 </dict>
516 <dict>
517 <key>include</key>
518 <string>#commonmark-code-fenced-scala</string>
519 </dict>
520 <dict>
521 <key>include</key>
522 <string>#commonmark-code-fenced-scss</string>
523 </dict>
524 <dict>
525 <key>include</key>
526 <string>#commonmark-code-fenced-shell</string>
527 </dict>
528 <dict>
529 <key>include</key>
530 <string>#commonmark-code-fenced-shell-session</string>
531 </dict>
532 <dict>
533 <key>include</key>
534 <string>#commonmark-code-fenced-sql</string>
535 </dict>
536 <dict>
537 <key>include</key>
538 <string>#commonmark-code-fenced-svg</string>
539 </dict>
540 <dict>
541 <key>include</key>
542 <string>#commonmark-code-fenced-swift</string>
543 </dict>
544 <dict>
545 <key>include</key>
546 <string>#commonmark-code-fenced-toml</string>
547 </dict>
548 <dict>
549 <key>include</key>
550 <string>#commonmark-code-fenced-ts</string>
551 </dict>
552 <dict>
553 <key>include</key>
554 <string>#commonmark-code-fenced-tsx</string>
555 </dict>
556 <dict>
557 <key>include</key>
558 <string>#commonmark-code-fenced-vbnet</string>
559 </dict>
560 <dict>
561 <key>include</key>
562 <string>#commonmark-code-fenced-xml</string>
563 </dict>
564 <dict>
565 <key>include</key>
566 <string>#commonmark-code-fenced-yaml</string>
567 </dict>
568 <dict>
569 <key>include</key>
570 <string>#commonmark-code-fenced-unknown</string>
571 </dict>
572 </array>
573 </dict>
574 <key>commonmark-code-fenced-unknown</key>
575 <dict>
576 <key>patterns</key>
577 <array>
578 <dict>
579 <key>begin</key>
580 <string>(?:^|\G)[\t ]*(`{3,})(?:[\t ]*((?:[^\t\n\r` ])+)(?:[\t ]+((?:[^\n\r`])+))?)?(?:[\t ]*$)</string>
581 <key>beginCaptures</key>
582 <dict>
583 <key>1</key>
584 <dict>
585 <key>name</key>
586 <string>string.other.begin.code.fenced.mdx</string>
587 </dict>
588 <key>2</key>
589 <dict>
590 <key>name</key>
591 <string>entity.name.function.mdx</string>
592 <key>patterns</key>
593 <array>
594 <dict>
595 <key>include</key>
596 <string>#markdown-string</string>
597 </dict>
598 </array>
599 </dict>
600 <key>3</key>
601 <dict>
602 <key>patterns</key>
603 <array>
604 <dict>
605 <key>include</key>
606 <string>#markdown-string</string>
607 </dict>
608 </array>
609 </dict>
610 </dict>
611 <key>contentName</key>
612 <string>markup.raw.code.fenced.mdx</string>
613 <key>end</key>
614 <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string>
615 <key>endCaptures</key>
616 <dict>
617 <key>1</key>
618 <dict>
619 <key>name</key>
620 <string>string.other.end.code.fenced.mdx</string>
621 </dict>
622 </dict>
623 <key>name</key>
624 <string>markup.code.other.mdx</string>
625 </dict>
626 <dict>
627 <key>begin</key>
628 <string>(?:^|\G)[\t ]*(~{3,})(?:[\t ]*((?:[^\t\n\r ])+)(?:[\t ]+((?:[^\n\r])+))?)?(?:[\t ]*$)</string>
629 <key>beginCaptures</key>
630 <dict>
631 <key>1</key>
632 <dict>
633 <key>name</key>
634 <string>string.other.begin.code.fenced.mdx</string>
635 </dict>
636 <key>2</key>
637 <dict>
638 <key>name</key>
639 <string>entity.name.function.mdx</string>
640 <key>patterns</key>
641 <array>
642 <dict>
643 <key>include</key>
644 <string>#markdown-string</string>
645 </dict>
646 </array>
647 </dict>
648 <key>3</key>
649 <dict>
650 <key>patterns</key>
651 <array>
652 <dict>
653 <key>include</key>
654 <string>#markdown-string</string>
655 </dict>
656 </array>
657 </dict>
658 </dict>
659 <key>contentName</key>
660 <string>markup.raw.code.fenced.mdx</string>
661 <key>end</key>
662 <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string>
663 <key>endCaptures</key>
664 <dict>
665 <key>1</key>
666 <dict>
667 <key>name</key>
668 <string>string.other.end.code.fenced.mdx</string>
669 </dict>
670 </dict>
671 <key>name</key>
672 <string>markup.code.other.mdx</string>
673 </dict>
674 </array>
675 </dict>
676 <key>commonmark-code-text</key>
677 <dict>
678 <key>match</key>
679 <string>(?&lt;!`)(`+)(?!`)(.+?)(?&lt;!`)(\1)(?!`)</string>
680 <key>name</key>
681 <string>markup.code.other.mdx</string>
682 <key>captures</key>
683 <dict>
684 <key>1</key>
685 <dict>
686 <key>name</key>
687 <string>string.other.begin.code.mdx</string>
688 </dict>
689 <key>2</key>
690 <dict>
691 <key>name</key>
692 <string>markup.raw.code.mdx markup.inline.raw.code.mdx</string>
693 </dict>
694 <key>3</key>
695 <dict>
696 <key>name</key>
697 <string>string.other.end.code.mdx</string>
698 </dict>
699 </dict>
700 </dict>
701 <key>commonmark-definition</key>
702 <dict>
703 <key>match</key>
704 <string>(?:^|\G)[\t ]*(\[)((?:[^\[\\\]]|\\[\[\\\]]?)+?)(\])(:)[ \t]*(?:(&lt;)((?:[^\n&lt;\\&gt;]|\\[&lt;\\&gt;]?)*)(&gt;)|(\g&lt;destination_raw&gt;))(?:[\t ]+(?:(")((?:[^"\\]|\\["\\]?)*)(")|(')((?:[^'\\]|\\['\\]?)*)(')|(\()((?:[^\)\\]|\\[\)\\]?)*)(\))))?$(?&lt;destination_raw&gt;(?!\&lt;)(?:(?:[^\p{Cc}\ \\\(\)]|\\[\(\)\\]?)|\(\g&lt;destination_raw&gt;*\))+){0}</string>
705 <key>name</key>
706 <string>meta.link.reference.def.mdx</string>
707 <key>captures</key>
708 <dict>
709 <key>1</key>
710 <dict>
711 <key>name</key>
712 <string>string.other.begin.mdx</string>
713 </dict>
714 <key>2</key>
715 <dict>
716 <key>name</key>
717 <string>entity.name.identifier.mdx</string>
718 <key>patterns</key>
719 <array>
720 <dict>
721 <key>include</key>
722 <string>#markdown-string</string>
723 </dict>
724 </array>
725 </dict>
726 <key>3</key>
727 <dict>
728 <key>name</key>
729 <string>string.other.end.mdx</string>
730 </dict>
731 <key>4</key>
732 <dict>
733 <key>name</key>
734 <string>punctuation.separator.key-value.mdx</string>
735 </dict>
736 <key>5</key>
737 <dict>
738 <key>name</key>
739 <string>string.other.begin.destination.mdx</string>
740 </dict>
741 <key>6</key>
742 <dict>
743 <key>name</key>
744 <string>string.other.link.destination.mdx</string>
745 <key>patterns</key>
746 <array>
747 <dict>
748 <key>include</key>
749 <string>#markdown-string</string>
750 </dict>
751 </array>
752 </dict>
753 <key>7</key>
754 <dict>
755 <key>name</key>
756 <string>string.other.end.destination.mdx</string>
757 </dict>
758 <key>8</key>
759 <dict>
760 <key>name</key>
761 <string>string.other.link.destination.mdx</string>
762 <key>patterns</key>
763 <array>
764 <dict>
765 <key>include</key>
766 <string>#markdown-string</string>
767 </dict>
768 </array>
769 </dict>
770 <key>9</key>
771 <dict>
772 <key>name</key>
773 <string>string.other.begin.mdx</string>
774 </dict>
775 <key>10</key>
776 <dict>
777 <key>name</key>
778 <string>string.quoted.double.mdx</string>
779 <key>patterns</key>
780 <array>
781 <dict>
782 <key>include</key>
783 <string>#markdown-string</string>
784 </dict>
785 </array>
786 </dict>
787 <key>11</key>
788 <dict>
789 <key>name</key>
790 <string>string.other.end.mdx</string>
791 </dict>
792 <key>12</key>
793 <dict>
794 <key>name</key>
795 <string>string.other.begin.mdx</string>
796 </dict>
797 <key>13</key>
798 <dict>
799 <key>name</key>
800 <string>string.quoted.single.mdx</string>
801 <key>patterns</key>
802 <array>
803 <dict>
804 <key>include</key>
805 <string>#markdown-string</string>
806 </dict>
807 </array>
808 </dict>
809 <key>14</key>
810 <dict>
811 <key>name</key>
812 <string>string.other.end.mdx</string>
813 </dict>
814 <key>15</key>
815 <dict>
816 <key>name</key>
817 <string>string.other.begin.mdx</string>
818 </dict>
819 <key>16</key>
820 <dict>
821 <key>name</key>
822 <string>string.quoted.paren.mdx</string>
823 <key>patterns</key>
824 <array>
825 <dict>
826 <key>include</key>
827 <string>#markdown-string</string>
828 </dict>
829 </array>
830 </dict>
831 <key>17</key>
832 <dict>
833 <key>name</key>
834 <string>string.other.end.mdx</string>
835 </dict>
836 </dict>
837 </dict>
838 <key>commonmark-hard-break-escape</key>
839 <dict>
840 <key>match</key>
841 <string>\\$</string>
842 <key>name</key>
843 <string>constant.language.character-escape.line-ending.mdx</string>
844 </dict>
845 <key>commonmark-hard-break-trailing</key>
846 <dict>
847 <key>match</key>
848 <string>( ){2,}$</string>
849 <key>name</key>
850 <string>carriage-return constant.language.character-escape.line-ending.mdx</string>
851 </dict>
852 <key>commonmark-heading-atx</key>
853 <dict>
854 <key>patterns</key>
855 <array>
856 <dict>
857 <key>match</key>
858 <string>(?:^|\G)[\t ]*(#{1}(?!#))(?:[ \t]+([^\r\n]+?)(?:[ \t]+(#+?))?)?[ \t]*$</string>
859 <key>name</key>
860 <string>markup.heading.atx.1.mdx</string>
861 <key>captures</key>
862 <dict>
863 <key>1</key>
864 <dict>
865 <key>name</key>
866 <string>punctuation.definition.heading.mdx</string>
867 </dict>
868 <key>2</key>
869 <dict>
870 <key>name</key>
871 <string>entity.name.section.mdx</string>
872 <key>patterns</key>
873 <array>
874 <dict>
875 <key>include</key>
876 <string>#markdown-text</string>
877 </dict>
878 </array>
879 </dict>
880 <key>3</key>
881 <dict>
882 <key>name</key>
883 <string>punctuation.definition.heading.mdx</string>
884 </dict>
885 </dict>
886 </dict>
887 <dict>
888 <key>match</key>
889 <string>(?:^|\G)[\t ]*(#{2}(?!#))(?:[ \t]+([^\r\n]+?)(?:[ \t]+(#+?))?)?[ \t]*$</string>
890 <key>name</key>
891 <string>markup.heading.atx.2.mdx</string>
892 <key>captures</key>
893 <dict>
894 <key>1</key>
895 <dict>
896 <key>name</key>
897 <string>punctuation.definition.heading.mdx</string>
898 </dict>
899 <key>2</key>
900 <dict>
901 <key>name</key>
902 <string>entity.name.section.mdx</string>
903 <key>patterns</key>
904 <array>
905 <dict>
906 <key>include</key>
907 <string>#markdown-text</string>
908 </dict>
909 </array>
910 </dict>
911 <key>3</key>
912 <dict>
913 <key>name</key>
914 <string>punctuation.definition.heading.mdx</string>
915 </dict>
916 </dict>
917 </dict>
918 <dict>
919 <key>match</key>
920 <string>(?:^|\G)[\t ]*(#{3}(?!#))(?:[ \t]+([^\r\n]+?)(?:[ \t]+(#+?))?)?[ \t]*$</string>
921 <key>name</key>
922 <string>markup.heading.atx.3.mdx</string>
923 <key>captures</key>
924 <dict>
925 <key>1</key>
926 <dict>
927 <key>name</key>
928 <string>punctuation.definition.heading.mdx</string>
929 </dict>
930 <key>2</key>
931 <dict>
932 <key>name</key>
933 <string>entity.name.section.mdx</string>
934 <key>patterns</key>
935 <array>
936 <dict>
937 <key>include</key>
938 <string>#markdown-text</string>
939 </dict>
940 </array>
941 </dict>
942 <key>3</key>
943 <dict>
944 <key>name</key>
945 <string>punctuation.definition.heading.mdx</string>
946 </dict>
947 </dict>
948 </dict>
949 <dict>
950 <key>match</key>
951 <string>(?:^|\G)[\t ]*(#{4}(?!#))(?:[ \t]+([^\r\n]+?)(?:[ \t]+(#+?))?)?[ \t]*$</string>
952 <key>name</key>
953 <string>markup.heading.atx.4.mdx</string>
954 <key>captures</key>
955 <dict>
956 <key>1</key>
957 <dict>
958 <key>name</key>
959 <string>punctuation.definition.heading.mdx</string>
960 </dict>
961 <key>2</key>
962 <dict>
963 <key>name</key>
964 <string>entity.name.section.mdx</string>
965 <key>patterns</key>
966 <array>
967 <dict>
968 <key>include</key>
969 <string>#markdown-text</string>
970 </dict>
971 </array>
972 </dict>
973 <key>3</key>
974 <dict>
975 <key>name</key>
976 <string>punctuation.definition.heading.mdx</string>
977 </dict>
978 </dict>
979 </dict>
980 <dict>
981 <key>match</key>
982 <string>(?:^|\G)[\t ]*(#{5}(?!#))(?:[ \t]+([^\r\n]+?)(?:[ \t]+(#+?))?)?[ \t]*$</string>
983 <key>name</key>
984 <string>markup.heading.atx.5.mdx</string>
985 <key>captures</key>
986 <dict>
987 <key>1</key>
988 <dict>
989 <key>name</key>
990 <string>punctuation.definition.heading.mdx</string>
991 </dict>
992 <key>2</key>
993 <dict>
994 <key>name</key>
995 <string>entity.name.section.mdx</string>
996 <key>patterns</key>
997 <array>
998 <dict>
999 <key>include</key>
1000 <string>#markdown-text</string>
1001 </dict>
1002 </array>
1003 </dict>
1004 <key>3</key>
1005 <dict>
1006 <key>name</key>
1007 <string>punctuation.definition.heading.mdx</string>
1008 </dict>
1009 </dict>
1010 </dict>
1011 <dict>
1012 <key>match</key>
1013 <string>(?:^|\G)[\t ]*(#{6}(?!#))(?:[ \t]+([^\r\n]+?)(?:[ \t]+(#+?))?)?[ \t]*$</string>
1014 <key>name</key>
1015 <string>markup.heading.atx.6.mdx</string>
1016 <key>captures</key>
1017 <dict>
1018 <key>1</key>
1019 <dict>
1020 <key>name</key>
1021 <string>punctuation.definition.heading.mdx</string>
1022 </dict>
1023 <key>2</key>
1024 <dict>
1025 <key>name</key>
1026 <string>entity.name.section.mdx</string>
1027 <key>patterns</key>
1028 <array>
1029 <dict>
1030 <key>include</key>
1031 <string>#markdown-text</string>
1032 </dict>
1033 </array>
1034 </dict>
1035 <key>3</key>
1036 <dict>
1037 <key>name</key>
1038 <string>punctuation.definition.heading.mdx</string>
1039 </dict>
1040 </dict>
1041 </dict>
1042 </array>
1043 </dict>
1044 <key>commonmark-heading-setext</key>
1045 <dict>
1046 <key>patterns</key>
1047 <array>
1048 <dict>
1049 <key>match</key>
1050 <string>(?:^|\G)[\t ]*(={1,})[ \t]*$</string>
1051 <key>name</key>
1052 <string>markup.heading.setext.1.mdx</string>
1053 </dict>
1054 <dict>
1055 <key>match</key>
1056 <string>(?:^|\G)[\t ]*(-{1,})[ \t]*$</string>
1057 <key>name</key>
1058 <string>markup.heading.setext.2.mdx</string>
1059 </dict>
1060 </array>
1061 </dict>
1062 <key>commonmark-label-end</key>
1063 <dict>
1064 <key>patterns</key>
1065 <array>
1066 <dict>
1067 <key>match</key>
1068 <string>(\])(\()[\t ]*(?:(?:(&lt;)((?:[^\n&lt;\\&gt;]|\\[&lt;\\&gt;]?)*)(&gt;)|(\g&lt;destination_raw&gt;))(?:[\t ]+(?:(")((?:[^"\\]|\\["\\]?)*)(")|(')((?:[^'\\]|\\['\\]?)*)(')|(\()((?:[^\)\\]|\\[\)\\]?)*)(\))))?)?[\t ]*(\))(?&lt;destination_raw&gt;(?!\&lt;)(?:(?:[^\p{Cc}\ \\\(\)]|\\[\(\)\\]?)|\(\g&lt;destination_raw&gt;*\))+){0}</string>
1069 <key>captures</key>
1070 <dict>
1071 <key>1</key>
1072 <dict>
1073 <key>name</key>
1074 <string>string.other.end.mdx</string>
1075 </dict>
1076 <key>2</key>
1077 <dict>
1078 <key>name</key>
1079 <string>string.other.begin.mdx</string>
1080 </dict>
1081 <key>3</key>
1082 <dict>
1083 <key>name</key>
1084 <string>string.other.begin.destination.mdx</string>
1085 </dict>
1086 <key>4</key>
1087 <dict>
1088 <key>name</key>
1089 <string>string.other.link.destination.mdx</string>
1090 <key>patterns</key>
1091 <array>
1092 <dict>
1093 <key>include</key>
1094 <string>#markdown-string</string>
1095 </dict>
1096 </array>
1097 </dict>
1098 <key>5</key>
1099 <dict>
1100 <key>name</key>
1101 <string>string.other.end.destination.mdx</string>
1102 </dict>
1103 <key>6</key>
1104 <dict>
1105 <key>name</key>
1106 <string>string.other.link.destination.mdx</string>
1107 <key>patterns</key>
1108 <array>
1109 <dict>
1110 <key>include</key>
1111 <string>#markdown-string</string>
1112 </dict>
1113 </array>
1114 </dict>
1115 <key>7</key>
1116 <dict>
1117 <key>name</key>
1118 <string>string.other.begin.mdx</string>
1119 </dict>
1120 <key>8</key>
1121 <dict>
1122 <key>name</key>
1123 <string>string.quoted.double.mdx</string>
1124 <key>patterns</key>
1125 <array>
1126 <dict>
1127 <key>include</key>
1128 <string>#markdown-string</string>
1129 </dict>
1130 </array>
1131 </dict>
1132 <key>9</key>
1133 <dict>
1134 <key>name</key>
1135 <string>string.other.end.mdx</string>
1136 </dict>
1137 <key>10</key>
1138 <dict>
1139 <key>name</key>
1140 <string>string.other.begin.mdx</string>
1141 </dict>
1142 <key>11</key>
1143 <dict>
1144 <key>name</key>
1145 <string>string.quoted.single.mdx</string>
1146 <key>patterns</key>
1147 <array>
1148 <dict>
1149 <key>include</key>
1150 <string>#markdown-string</string>
1151 </dict>
1152 </array>
1153 </dict>
1154 <key>12</key>
1155 <dict>
1156 <key>name</key>
1157 <string>string.other.end.mdx</string>
1158 </dict>
1159 <key>13</key>
1160 <dict>
1161 <key>name</key>
1162 <string>string.other.begin.mdx</string>
1163 </dict>
1164 <key>14</key>
1165 <dict>
1166 <key>name</key>
1167 <string>string.quoted.paren.mdx</string>
1168 <key>patterns</key>
1169 <array>
1170 <dict>
1171 <key>include</key>
1172 <string>#markdown-string</string>
1173 </dict>
1174 </array>
1175 </dict>
1176 <key>15</key>
1177 <dict>
1178 <key>name</key>
1179 <string>string.other.end.mdx</string>
1180 </dict>
1181 <key>16</key>
1182 <dict>
1183 <key>name</key>
1184 <string>string.other.end.mdx</string>
1185 </dict>
1186 </dict>
1187 </dict>
1188 <dict>
1189 <key>match</key>
1190 <string>(\])(\[)((?:[^\[\\\]]|\\[\[\\\]]?)+?)(\])</string>
1191 <key>captures</key>
1192 <dict>
1193 <key>1</key>
1194 <dict>
1195 <key>name</key>
1196 <string>string.other.end.mdx</string>
1197 </dict>
1198 <key>2</key>
1199 <dict>
1200 <key>name</key>
1201 <string>string.other.begin.mdx</string>
1202 </dict>
1203 <key>3</key>
1204 <dict>
1205 <key>name</key>
1206 <string>entity.name.identifier.mdx</string>
1207 <key>patterns</key>
1208 <array>
1209 <dict>
1210 <key>include</key>
1211 <string>#markdown-string</string>
1212 </dict>
1213 </array>
1214 </dict>
1215 <key>4</key>
1216 <dict>
1217 <key>name</key>
1218 <string>string.other.end.mdx</string>
1219 </dict>
1220 </dict>
1221 </dict>
1222 <dict>
1223 <key>match</key>
1224 <string>(\])</string>
1225 <key>captures</key>
1226 <dict>
1227 <key>1</key>
1228 <dict>
1229 <key>name</key>
1230 <string>string.other.end.mdx</string>
1231 </dict>
1232 </dict>
1233 </dict>
1234 </array>
1235 </dict>
1236 <key>commonmark-label-start</key>
1237 <dict>
1238 <key>patterns</key>
1239 <array>
1240 <dict>
1241 <key>match</key>
1242 <string>\!\[(?!\^)</string>
1243 <key>name</key>
1244 <string>string.other.begin.image.mdx</string>
1245 </dict>
1246 <dict>
1247 <key>match</key>
1248 <string>\[</string>
1249 <key>name</key>
1250 <string>string.other.begin.link.mdx</string>
1251 </dict>
1252 </array>
1253 </dict>
1254 <key>commonmark-list-item</key>
1255 <dict>
1256 <key>patterns</key>
1257 <array>
1258 <dict>
1259 <key>begin</key>
1260 <string>(?:^|\G)[\t ]*((?:[*+-]))(?:[ ]{4}(?![ ])|\t)(\[[\t Xx]\](?=[\t\n\r ]+(?:$|[^\t\n\r ])))?</string>
1261 <key>beginCaptures</key>
1262 <dict>
1263 <key>1</key>
1264 <dict>
1265 <key>name</key>
1266 <string>variable.unordered.list.mdx</string>
1267 </dict>
1268 <key>2</key>
1269 <dict>
1270 <key>name</key>
1271 <string>keyword.other.tasklist.mdx</string>
1272 </dict>
1273 </dict>
1274 <key>patterns</key>
1275 <array>
1276 <dict>
1277 <key>include</key>
1278 <string>#markdown-sections</string>
1279 </dict>
1280 </array>
1281 <key>while</key>
1282 <string>^(?=[\t ]*$)|(?:^|\G)(?:[ ]{4}|\t)[ ]{1}</string>
1283 </dict>
1284 <dict>
1285 <key>begin</key>
1286 <string>(?:^|\G)[\t ]*((?:[*+-]))(?:[ ]{3}(?![ ]))(\[[\t Xx]\](?=[\t\n\r ]+(?:$|[^\t\n\r ])))?</string>
1287 <key>beginCaptures</key>
1288 <dict>
1289 <key>1</key>
1290 <dict>
1291 <key>name</key>
1292 <string>variable.unordered.list.mdx</string>
1293 </dict>
1294 <key>2</key>
1295 <dict>
1296 <key>name</key>
1297 <string>keyword.other.tasklist.mdx</string>
1298 </dict>
1299 </dict>
1300 <key>patterns</key>
1301 <array>
1302 <dict>
1303 <key>include</key>
1304 <string>#markdown-sections</string>
1305 </dict>
1306 </array>
1307 <key>while</key>
1308 <string>^(?=[\t ]*$)|(?:^|\G)(?:[ ]{4}|\t)</string>
1309 </dict>
1310 <dict>
1311 <key>begin</key>
1312 <string>(?:^|\G)[\t ]*((?:[*+-]))(?:[ ]{2}(?![ ]))(\[[\t Xx]\](?=[\t\n\r ]+(?:$|[^\t\n\r ])))?</string>
1313 <key>beginCaptures</key>
1314 <dict>
1315 <key>1</key>
1316 <dict>
1317 <key>name</key>
1318 <string>variable.unordered.list.mdx</string>
1319 </dict>
1320 <key>2</key>
1321 <dict>
1322 <key>name</key>
1323 <string>keyword.other.tasklist.mdx</string>
1324 </dict>
1325 </dict>
1326 <key>patterns</key>
1327 <array>
1328 <dict>
1329 <key>include</key>
1330 <string>#markdown-sections</string>
1331 </dict>
1332 </array>
1333 <key>while</key>
1334 <string>^(?=[\t ]*$)|(?:^|\G)[ ]{3}</string>
1335 </dict>
1336 <dict>
1337 <key>begin</key>
1338 <string>(?:^|\G)[\t ]*((?:[*+-]))(?:[ ]{1}|(?=\n))(\[[\t Xx]\](?=[\t\n\r ]+(?:$|[^\t\n\r ])))?</string>
1339 <key>beginCaptures</key>
1340 <dict>
1341 <key>1</key>
1342 <dict>
1343 <key>name</key>
1344 <string>variable.unordered.list.mdx</string>
1345 </dict>
1346 <key>2</key>
1347 <dict>
1348 <key>name</key>
1349 <string>keyword.other.tasklist.mdx</string>
1350 </dict>
1351 </dict>
1352 <key>patterns</key>
1353 <array>
1354 <dict>
1355 <key>include</key>
1356 <string>#markdown-sections</string>
1357 </dict>
1358 </array>
1359 <key>while</key>
1360 <string>^(?=[\t ]*$)|(?:^|\G)[ ]{2}</string>
1361 </dict>
1362 <dict>
1363 <key>begin</key>
1364 <string>(?:^|\G)[\t ]*([0-9]{9})((?:\.|\)))(?:[ ]{4}(?![ ])|\t(?![\t ]))(\[[\t Xx]\](?=[\t\n\r ]+(?:$|[^\t\n\r ])))?</string>
1365 <key>beginCaptures</key>
1366 <dict>
1367 <key>1</key>
1368 <dict>
1369 <key>name</key>
1370 <string>string.other.number.mdx</string>
1371 </dict>
1372 <key>2</key>
1373 <dict>
1374 <key>name</key>
1375 <string>variable.ordered.list.mdx</string>
1376 </dict>
1377 <key>3</key>
1378 <dict>
1379 <key>name</key>
1380 <string>keyword.other.tasklist.mdx</string>
1381 </dict>
1382 </dict>
1383 <key>patterns</key>
1384 <array>
1385 <dict>
1386 <key>include</key>
1387 <string>#markdown-sections</string>
1388 </dict>
1389 </array>
1390 <key>while</key>
1391 <string>^(?=[\t ]*$)|(?:^|\G)(?:[ ]{4}|\t){3}[ ]{2}</string>
1392 </dict>
1393 <dict>
1394 <key>begin</key>
1395 <string>(?:^|\G)[\t ]*(?:([0-9]{9})((?:\.|\)))(?:[ ]{3}(?![ ]))|([0-9]{8})((?:\.|\)))(?:[ ]{4}(?![ ])))(\[[\t Xx]\](?=[\t\n\r ]+(?:$|[^\t\n\r ])))?</string>
1396 <key>beginCaptures</key>
1397 <dict>
1398 <key>1</key>
1399 <dict>
1400 <key>name</key>
1401 <string>string.other.number.mdx</string>
1402 </dict>
1403 <key>2</key>
1404 <dict>
1405 <key>name</key>
1406 <string>variable.ordered.list.mdx</string>
1407 </dict>
1408 <key>3</key>
1409 <dict>
1410 <key>name</key>
1411 <string>string.other.number.mdx</string>
1412 </dict>
1413 <key>4</key>
1414 <dict>
1415 <key>name</key>
1416 <string>variable.ordered.list.mdx</string>
1417 </dict>
1418 <key>5</key>
1419 <dict>
1420 <key>name</key>
1421 <string>keyword.other.tasklist.mdx</string>
1422 </dict>
1423 </dict>
1424 <key>patterns</key>
1425 <array>
1426 <dict>
1427 <key>include</key>
1428 <string>#markdown-sections</string>
1429 </dict>
1430 </array>
1431 <key>while</key>
1432 <string>^(?=[\t ]*$)|(?:^|\G)(?:[ ]{4}|\t){3}[ ]{1}</string>
1433 </dict>
1434 <dict>
1435 <key>begin</key>
1436 <string>(?:^|\G)[\t ]*(?:([0-9]{9})((?:\.|\)))(?:[ ]{2}(?![ ]))|([0-9]{8})((?:\.|\)))(?:[ ]{3}(?![ ]))|([0-9]{7})((?:\.|\)))(?:[ ]{4}(?![ ])))(\[[\t Xx]\](?=[\t\n\r ]+(?:$|[^\t\n\r ])))?</string>
1437 <key>beginCaptures</key>
1438 <dict>
1439 <key>1</key>
1440 <dict>
1441 <key>name</key>
1442 <string>string.other.number.mdx</string>
1443 </dict>
1444 <key>2</key>
1445 <dict>
1446 <key>name</key>
1447 <string>variable.ordered.list.mdx</string>
1448 </dict>
1449 <key>3</key>
1450 <dict>
1451 <key>name</key>
1452 <string>string.other.number.mdx</string>
1453 </dict>
1454 <key>4</key>
1455 <dict>
1456 <key>name</key>
1457 <string>variable.ordered.list.mdx</string>
1458 </dict>
1459 <key>5</key>
1460 <dict>
1461 <key>name</key>
1462 <string>string.other.number.mdx</string>
1463 </dict>
1464 <key>6</key>
1465 <dict>
1466 <key>name</key>
1467 <string>variable.ordered.list.mdx</string>
1468 </dict>
1469 <key>7</key>
1470 <dict>
1471 <key>name</key>
1472 <string>keyword.other.tasklist.mdx</string>
1473 </dict>
1474 </dict>
1475 <key>patterns</key>
1476 <array>
1477 <dict>
1478 <key>include</key>
1479 <string>#markdown-sections</string>
1480 </dict>
1481 </array>
1482 <key>while</key>
1483 <string>^(?=[\t ]*$)|(?:^|\G)(?:[ ]{4}|\t){3}</string>
1484 </dict>
1485 <dict>
1486 <key>begin</key>
1487 <string>(?:^|\G)[\t ]*(?:([0-9]{9})((?:\.|\)))(?:[ ]{1}|(?=[ \t]*\n))|([0-9]{8})((?:\.|\)))(?:[ ]{2}(?![ ]))|([0-9]{7})((?:\.|\)))(?:[ ]{3}(?![ ]))|([0-9]{6})((?:\.|\)))(?:[ ]{4}(?![ ])))(\[[\t Xx]\](?=[\t\n\r ]+(?:$|[^\t\n\r ])))?</string>
1488 <key>beginCaptures</key>
1489 <dict>
1490 <key>1</key>
1491 <dict>
1492 <key>name</key>
1493 <string>string.other.number.mdx</string>
1494 </dict>
1495 <key>2</key>
1496 <dict>
1497 <key>name</key>
1498 <string>variable.ordered.list.mdx</string>
1499 </dict>
1500 <key>3</key>
1501 <dict>
1502 <key>name</key>
1503 <string>string.other.number.mdx</string>
1504 </dict>
1505 <key>4</key>
1506 <dict>
1507 <key>name</key>
1508 <string>variable.ordered.list.mdx</string>
1509 </dict>
1510 <key>5</key>
1511 <dict>
1512 <key>name</key>
1513 <string>string.other.number.mdx</string>
1514 </dict>
1515 <key>6</key>
1516 <dict>
1517 <key>name</key>
1518 <string>variable.ordered.list.mdx</string>
1519 </dict>
1520 <key>7</key>
1521 <dict>
1522 <key>name</key>
1523 <string>string.other.number.mdx</string>
1524 </dict>
1525 <key>8</key>
1526 <dict>
1527 <key>name</key>
1528 <string>variable.ordered.list.mdx</string>
1529 </dict>
1530 <key>9</key>
1531 <dict>
1532 <key>name</key>
1533 <string>keyword.other.tasklist.mdx</string>
1534 </dict>
1535 </dict>
1536 <key>patterns</key>
1537 <array>
1538 <dict>
1539 <key>include</key>
1540 <string>#markdown-sections</string>
1541 </dict>
1542 </array>
1543 <key>while</key>
1544 <string>^(?=[\t ]*$)|(?:^|\G)(?:[ ]{4}|\t){2}[ ]{3}</string>
1545 </dict>
1546 <dict>
1547 <key>begin</key>
1548 <string>(?:^|\G)[\t ]*(?:([0-9]{8})((?:\.|\)))(?:[ ]{1}|(?=[ \t]*\n))|([0-9]{7})((?:\.|\)))(?:[ ]{2}(?![ ]))|([0-9]{6})((?:\.|\)))(?:[ ]{3}(?![ ]))|([0-9]{5})((?:\.|\)))(?:[ ]{4}(?![ ])))(\[[\t Xx]\](?=[\t\n\r ]+(?:$|[^\t\n\r ])))?</string>
1549 <key>beginCaptures</key>
1550 <dict>
1551 <key>1</key>
1552 <dict>
1553 <key>name</key>
1554 <string>string.other.number.mdx</string>
1555 </dict>
1556 <key>2</key>
1557 <dict>
1558 <key>name</key>
1559 <string>variable.ordered.list.mdx</string>
1560 </dict>
1561 <key>3</key>
1562 <dict>
1563 <key>name</key>
1564 <string>string.other.number.mdx</string>
1565 </dict>
1566 <key>4</key>
1567 <dict>
1568 <key>name</key>
1569 <string>variable.ordered.list.mdx</string>
1570 </dict>
1571 <key>5</key>
1572 <dict>
1573 <key>name</key>
1574 <string>string.other.number.mdx</string>
1575 </dict>
1576 <key>6</key>
1577 <dict>
1578 <key>name</key>
1579 <string>variable.ordered.list.mdx</string>
1580 </dict>
1581 <key>7</key>
1582 <dict>
1583 <key>name</key>
1584 <string>string.other.number.mdx</string>
1585 </dict>
1586 <key>8</key>
1587 <dict>
1588 <key>name</key>
1589 <string>variable.ordered.list.mdx</string>
1590 </dict>
1591 <key>9</key>
1592 <dict>
1593 <key>name</key>
1594 <string>keyword.other.tasklist.mdx</string>
1595 </dict>
1596 </dict>
1597 <key>patterns</key>
1598 <array>
1599 <dict>
1600 <key>include</key>
1601 <string>#markdown-sections</string>
1602 </dict>
1603 </array>
1604 <key>while</key>
1605 <string>^(?=[\t ]*$)|(?:^|\G)(?:[ ]{4}|\t){2}[ ]{2}</string>
1606 </dict>
1607 <dict>
1608 <key>begin</key>
1609 <string>(?:^|\G)[\t ]*(?:([0-9]{7})((?:\.|\)))(?:[ ]{1}|(?=[ \t]*\n))|([0-9]{6})((?:\.|\)))(?:[ ]{2}(?![ ]))|([0-9]{5})((?:\.|\)))(?:[ ]{3}(?![ ]))|([0-9]{4})((?:\.|\)))(?:[ ]{4}(?![ ])))(\[[\t Xx]\](?=[\t\n\r ]+(?:$|[^\t\n\r ])))?</string>
1610 <key>beginCaptures</key>
1611 <dict>
1612 <key>1</key>
1613 <dict>
1614 <key>name</key>
1615 <string>string.other.number.mdx</string>
1616 </dict>
1617 <key>2</key>
1618 <dict>
1619 <key>name</key>
1620 <string>variable.ordered.list.mdx</string>
1621 </dict>
1622 <key>3</key>
1623 <dict>
1624 <key>name</key>
1625 <string>string.other.number.mdx</string>
1626 </dict>
1627 <key>4</key>
1628 <dict>
1629 <key>name</key>
1630 <string>variable.ordered.list.mdx</string>
1631 </dict>
1632 <key>5</key>
1633 <dict>
1634 <key>name</key>
1635 <string>string.other.number.mdx</string>
1636 </dict>
1637 <key>6</key>
1638 <dict>
1639 <key>name</key>
1640 <string>variable.ordered.list.mdx</string>
1641 </dict>
1642 <key>7</key>
1643 <dict>
1644 <key>name</key>
1645 <string>string.other.number.mdx</string>
1646 </dict>
1647 <key>8</key>
1648 <dict>
1649 <key>name</key>
1650 <string>variable.ordered.list.mdx</string>
1651 </dict>
1652 <key>9</key>
1653 <dict>
1654 <key>name</key>
1655 <string>keyword.other.tasklist.mdx</string>
1656 </dict>
1657 </dict>
1658 <key>patterns</key>
1659 <array>
1660 <dict>
1661 <key>include</key>
1662 <string>#markdown-sections</string>
1663 </dict>
1664 </array>
1665 <key>while</key>
1666 <string>^(?=[\t ]*$)|(?:^|\G)(?:[ ]{4}|\t){2}[ ]{1}</string>
1667 </dict>
1668 <dict>
1669 <key>begin</key>
1670 <string>(?:^|\G)[\t ]*(?:([0-9]{6})((?:\.|\)))(?:[ ]{1}|(?=[ \t]*\n))|([0-9]{5})((?:\.|\)))(?:[ ]{2}(?![ ]))|([0-9]{4})((?:\.|\)))(?:[ ]{3}(?![ ]))|([0-9]{3})((?:\.|\)))(?:[ ]{4}(?![ ])))(\[[\t Xx]\](?=[\t\n\r ]+(?:$|[^\t\n\r ])))?</string>
1671 <key>beginCaptures</key>
1672 <dict>
1673 <key>1</key>
1674 <dict>
1675 <key>name</key>
1676 <string>string.other.number.mdx</string>
1677 </dict>
1678 <key>2</key>
1679 <dict>
1680 <key>name</key>
1681 <string>variable.ordered.list.mdx</string>
1682 </dict>
1683 <key>3</key>
1684 <dict>
1685 <key>name</key>
1686 <string>string.other.number.mdx</string>
1687 </dict>
1688 <key>4</key>
1689 <dict>
1690 <key>name</key>
1691 <string>variable.ordered.list.mdx</string>
1692 </dict>
1693 <key>5</key>
1694 <dict>
1695 <key>name</key>
1696 <string>string.other.number.mdx</string>
1697 </dict>
1698 <key>6</key>
1699 <dict>
1700 <key>name</key>
1701 <string>variable.ordered.list.mdx</string>
1702 </dict>
1703 <key>7</key>
1704 <dict>
1705 <key>name</key>
1706 <string>string.other.number.mdx</string>
1707 </dict>
1708 <key>8</key>
1709 <dict>
1710 <key>name</key>
1711 <string>variable.ordered.list.mdx</string>
1712 </dict>
1713 <key>9</key>
1714 <dict>
1715 <key>name</key>
1716 <string>keyword.other.tasklist.mdx</string>
1717 </dict>
1718 </dict>
1719 <key>patterns</key>
1720 <array>
1721 <dict>
1722 <key>include</key>
1723 <string>#markdown-sections</string>
1724 </dict>
1725 </array>
1726 <key>while</key>
1727 <string>^(?=[\t ]*$)|(?:^|\G)(?:[ ]{4}|\t){2}</string>
1728 </dict>
1729 <dict>
1730 <key>begin</key>
1731 <string>(?:^|\G)[\t ]*(?:([0-9]{5})((?:\.|\)))(?:[ ]{1}|(?=[ \t]*\n))|([0-9]{4})((?:\.|\)))(?:[ ]{2}(?![ ]))|([0-9]{3})((?:\.|\)))(?:[ ]{3}(?![ ]))|([0-9]{2})((?:\.|\)))(?:[ ]{4}(?![ ])))(\[[\t Xx]\](?=[\t\n\r ]+(?:$|[^\t\n\r ])))?</string>
1732 <key>beginCaptures</key>
1733 <dict>
1734 <key>1</key>
1735 <dict>
1736 <key>name</key>
1737 <string>string.other.number.mdx</string>
1738 </dict>
1739 <key>2</key>
1740 <dict>
1741 <key>name</key>
1742 <string>variable.ordered.list.mdx</string>
1743 </dict>
1744 <key>3</key>
1745 <dict>
1746 <key>name</key>
1747 <string>string.other.number.mdx</string>
1748 </dict>
1749 <key>4</key>
1750 <dict>
1751 <key>name</key>
1752 <string>variable.ordered.list.mdx</string>
1753 </dict>
1754 <key>5</key>
1755 <dict>
1756 <key>name</key>
1757 <string>string.other.number.mdx</string>
1758 </dict>
1759 <key>6</key>
1760 <dict>
1761 <key>name</key>
1762 <string>variable.ordered.list.mdx</string>
1763 </dict>
1764 <key>7</key>
1765 <dict>
1766 <key>name</key>
1767 <string>string.other.number.mdx</string>
1768 </dict>
1769 <key>8</key>
1770 <dict>
1771 <key>name</key>
1772 <string>variable.ordered.list.mdx</string>
1773 </dict>
1774 <key>9</key>
1775 <dict>
1776 <key>name</key>
1777 <string>keyword.other.tasklist.mdx</string>
1778 </dict>
1779 </dict>
1780 <key>patterns</key>
1781 <array>
1782 <dict>
1783 <key>include</key>
1784 <string>#markdown-sections</string>
1785 </dict>
1786 </array>
1787 <key>while</key>
1788 <string>^(?=[\t ]*$)|(?:^|\G)(?:[ ]{4}|\t)[ ]{3}</string>
1789 </dict>
1790 <dict>
1791 <key>begin</key>
1792 <string>(?:^|\G)[\t ]*(?:([0-9]{4})((?:\.|\)))(?:[ ]{1}|(?=[ \t]*\n))|([0-9]{3})((?:\.|\)))(?:[ ]{2}(?![ ]))|([0-9]{2})((?:\.|\)))(?:[ ]{3}(?![ ]))|([0-9]{1})((?:\.|\)))(?:[ ]{4}(?![ ])))(\[[\t Xx]\](?=[\t\n\r ]+(?:$|[^\t\n\r ])))?</string>
1793 <key>beginCaptures</key>
1794 <dict>
1795 <key>1</key>
1796 <dict>
1797 <key>name</key>
1798 <string>string.other.number.mdx</string>
1799 </dict>
1800 <key>2</key>
1801 <dict>
1802 <key>name</key>
1803 <string>variable.ordered.list.mdx</string>
1804 </dict>
1805 <key>3</key>
1806 <dict>
1807 <key>name</key>
1808 <string>string.other.number.mdx</string>
1809 </dict>
1810 <key>4</key>
1811 <dict>
1812 <key>name</key>
1813 <string>variable.ordered.list.mdx</string>
1814 </dict>
1815 <key>5</key>
1816 <dict>
1817 <key>name</key>
1818 <string>string.other.number.mdx</string>
1819 </dict>
1820 <key>6</key>
1821 <dict>
1822 <key>name</key>
1823 <string>variable.ordered.list.mdx</string>
1824 </dict>
1825 <key>7</key>
1826 <dict>
1827 <key>name</key>
1828 <string>string.other.number.mdx</string>
1829 </dict>
1830 <key>8</key>
1831 <dict>
1832 <key>name</key>
1833 <string>variable.ordered.list.mdx</string>
1834 </dict>
1835 <key>9</key>
1836 <dict>
1837 <key>name</key>
1838 <string>keyword.other.tasklist.mdx</string>
1839 </dict>
1840 </dict>
1841 <key>patterns</key>
1842 <array>
1843 <dict>
1844 <key>include</key>
1845 <string>#markdown-sections</string>
1846 </dict>
1847 </array>
1848 <key>while</key>
1849 <string>^(?=[\t ]*$)|(?:^|\G)(?:[ ]{4}|\t)[ ]{2}</string>
1850 </dict>
1851 <dict>
1852 <key>begin</key>
1853 <string>(?:^|\G)[\t ]*(?:([0-9]{3})((?:\.|\)))(?:[ ]{1}|(?=[ \t]*\n))|([0-9]{2})((?:\.|\)))(?:[ ]{2}(?![ ]))|([0-9]{1})((?:\.|\)))(?:[ ]{3}(?![ ])))(\[[\t Xx]\](?=[\t\n\r ]+(?:$|[^\t\n\r ])))?</string>
1854 <key>beginCaptures</key>
1855 <dict>
1856 <key>1</key>
1857 <dict>
1858 <key>name</key>
1859 <string>string.other.number.mdx</string>
1860 </dict>
1861 <key>2</key>
1862 <dict>
1863 <key>name</key>
1864 <string>variable.ordered.list.mdx</string>
1865 </dict>
1866 <key>3</key>
1867 <dict>
1868 <key>name</key>
1869 <string>string.other.number.mdx</string>
1870 </dict>
1871 <key>4</key>
1872 <dict>
1873 <key>name</key>
1874 <string>variable.ordered.list.mdx</string>
1875 </dict>
1876 <key>5</key>
1877 <dict>
1878 <key>name</key>
1879 <string>string.other.number.mdx</string>
1880 </dict>
1881 <key>6</key>
1882 <dict>
1883 <key>name</key>
1884 <string>variable.ordered.list.mdx</string>
1885 </dict>
1886 <key>7</key>
1887 <dict>
1888 <key>name</key>
1889 <string>keyword.other.tasklist.mdx</string>
1890 </dict>
1891 </dict>
1892 <key>patterns</key>
1893 <array>
1894 <dict>
1895 <key>include</key>
1896 <string>#markdown-sections</string>
1897 </dict>
1898 </array>
1899 <key>while</key>
1900 <string>^(?=[\t ]*$)|(?:^|\G)(?:[ ]{4}|\t)[ ]{1}</string>
1901 </dict>
1902 <dict>
1903 <key>begin</key>
1904 <string>(?:^|\G)[\t ]*(?:([0-9]{2})((?:\.|\)))(?:[ ]{1}|(?=[ \t]*\n))|([0-9])((?:\.|\)))(?:[ ]{2}(?![ ])))(\[[\t Xx]\](?=[\t\n\r ]+(?:$|[^\t\n\r ])))?</string>
1905 <key>beginCaptures</key>
1906 <dict>
1907 <key>1</key>
1908 <dict>
1909 <key>name</key>
1910 <string>string.other.number.mdx</string>
1911 </dict>
1912 <key>2</key>
1913 <dict>
1914 <key>name</key>
1915 <string>variable.ordered.list.mdx</string>
1916 </dict>
1917 <key>3</key>
1918 <dict>
1919 <key>name</key>
1920 <string>string.other.number.mdx</string>
1921 </dict>
1922 <key>4</key>
1923 <dict>
1924 <key>name</key>
1925 <string>variable.ordered.list.mdx</string>
1926 </dict>
1927 <key>5</key>
1928 <dict>
1929 <key>name</key>
1930 <string>keyword.other.tasklist.mdx</string>
1931 </dict>
1932 </dict>
1933 <key>patterns</key>
1934 <array>
1935 <dict>
1936 <key>include</key>
1937 <string>#markdown-sections</string>
1938 </dict>
1939 </array>
1940 <key>while</key>
1941 <string>^(?=[\t ]*$)|(?:^|\G)(?:[ ]{4}|\t)</string>
1942 </dict>
1943 <dict>
1944 <key>begin</key>
1945 <string>(?:^|\G)[\t ]*([0-9])((?:\.|\)))(?:[ ]{1}|(?=[ \t]*\n))(\[[\t Xx]\](?=[\t\n\r ]+(?:$|[^\t\n\r ])))?</string>
1946 <key>beginCaptures</key>
1947 <dict>
1948 <key>1</key>
1949 <dict>
1950 <key>name</key>
1951 <string>string.other.number.mdx</string>
1952 </dict>
1953 <key>2</key>
1954 <dict>
1955 <key>name</key>
1956 <string>variable.ordered.list.mdx</string>
1957 </dict>
1958 <key>3</key>
1959 <dict>
1960 <key>name</key>
1961 <string>keyword.other.tasklist.mdx</string>
1962 </dict>
1963 </dict>
1964 <key>patterns</key>
1965 <array>
1966 <dict>
1967 <key>include</key>
1968 <string>#markdown-sections</string>
1969 </dict>
1970 </array>
1971 <key>while</key>
1972 <string>^(?=[\t ]*$)|(?:^|\G)[ ]{3}</string>
1973 </dict>
1974 </array>
1975 </dict>
1976 <key>commonmark-paragraph</key>
1977 <dict>
1978 <key>begin</key>
1979 <string>(?![\t ]*$)</string>
1980 <key>name</key>
1981 <string>meta.paragraph.mdx</string>
1982 <key>patterns</key>
1983 <array>
1984 <dict>
1985 <key>include</key>
1986 <string>#markdown-text</string>
1987 </dict>
1988 </array>
1989 <key>while</key>
1990 <string>(?:^|\G)(?:[ ]{4}|\t)</string>
1991 </dict>
1992 <key>commonmark-thematic-break</key>
1993 <dict>
1994 <key>match</key>
1995 <string>(?:^|\G)[\t ]*([-*_])[ \t]*(?:\1[ \t]*){2,}$</string>
1996 <key>name</key>
1997 <string>meta.separator.mdx</string>
1998 </dict>
1999 <key>extension-gfm-autolink-literal</key>
2000 <dict>
2001 <key>patterns</key>
2002 <array>
2003 <dict>
2004 <key>match</key>
2005 <string>(?&lt;=^|[\t\n\r \(\*\_\[\]~])(?=(?i:www)\.[^\n\r])(?:(?:[\p{L}\p{N}]|-|[\._](?!(?:[!"'\)\*,\.:;&lt;\?_~]*(?:[\s&lt;]|\][\t\n \(\[]))))+\g&lt;path&gt;?)?(?&lt;path&gt;(?:(?:[^\t\n\r !"&amp;'\(\)\*,\.:;&lt;\?\]_~]|&amp;(?![A-Za-z]*;(?:[!"'\)\*,\.:;&lt;\?_~]*(?:[\s&lt;]|\][\t\n \(\[])))|[!"'\)\*,\.:;\?_~](?!(?:[!"'\)\*,\.:;&lt;\?_~]*(?:[\s&lt;]|\][\t\n \(\[]))))|\(\g&lt;path&gt;*\))+){0}</string>
2006 <key>name</key>
2007 <string>string.other.link.autolink.literal.www.mdx</string>
2008 </dict>
2009 <dict>
2010 <key>match</key>
2011 <string>(?&lt;=^|[^A-Za-z])(?i:https?://)(?=[\p{L}\p{N}])(?:(?:[\p{L}\p{N}]|-|[\._](?!(?:[!"'\)\*,\.:;&lt;\?_~]*(?:[\s&lt;]|\][\t\n \(\[]))))+\g&lt;path&gt;?)?(?&lt;path&gt;(?:(?:[^\t\n\r !"&amp;'\(\)\*,\.:;&lt;\?\]_~]|&amp;(?![A-Za-z]*;(?:[!"'\)\*,\.:;&lt;\?_~]*(?:[\s&lt;]|\][\t\n \(\[])))|[!"'\)\*,\.:;\?_~](?!(?:[!"'\)\*,\.:;&lt;\?_~]*(?:[\s&lt;]|\][\t\n \(\[]))))|\(\g&lt;path&gt;*\))+){0}</string>
2012 <key>name</key>
2013 <string>string.other.link.autolink.literal.http.mdx</string>
2014 </dict>
2015 <dict>
2016 <key>match</key>
2017 <string>(?&lt;=^|[^A-Za-z/])(?i:mailto:|xmpp:)?(?:[0-9A-Za-z+\-\._])+@(?:(?:[0-9A-Za-z]|[-_](?!(?:[!"'\)\*,\.:;&lt;\?_~]*(?:[\s&lt;]|\][\t\n \(\[]))))+(?:\.(?!(?:[!"'\)\*,\.:;&lt;\?_~]*(?:[\s&lt;]|\][\t\n \(\[])))))+(?:[A-Za-z]|[-_](?!(?:[!"'\)\*,\.:;&lt;\?_~]*(?:[\s&lt;]|\][\t\n \(\[]))))+</string>
2018 <key>name</key>
2019 <string>string.other.link.autolink.literal.email.mdx</string>
2020 </dict>
2021 </array>
2022 </dict>
2023 <key>extension-gfm-footnote-call</key>
2024 <dict>
2025 <key>match</key>
2026 <string>(\[)(\^)((?:[^\t\n\r \[\\\]]|\\[\[\\\]]?)+)(\])</string>
2027 <key>captures</key>
2028 <dict>
2029 <key>1</key>
2030 <dict>
2031 <key>name</key>
2032 <string>string.other.begin.link.mdx</string>
2033 </dict>
2034 <key>2</key>
2035 <dict>
2036 <key>name</key>
2037 <string>string.other.begin.footnote.mdx</string>
2038 </dict>
2039 <key>3</key>
2040 <dict>
2041 <key>name</key>
2042 <string>entity.name.identifier.mdx</string>
2043 <key>patterns</key>
2044 <array>
2045 <dict>
2046 <key>include</key>
2047 <string>#markdown-string</string>
2048 </dict>
2049 </array>
2050 </dict>
2051 <key>4</key>
2052 <dict>
2053 <key>name</key>
2054 <string>string.other.end.footnote.mdx</string>
2055 </dict>
2056 </dict>
2057 </dict>
2058 <key>extension-gfm-footnote-definition</key>
2059 <dict>
2060 <key>begin</key>
2061 <string>(?:^|\G)[\t ]*(\[)(\^)((?:[^\t\n\r \[\\\]]|\\[\[\\\]]?)+)(\])(:)[\t ]*</string>
2062 <key>beginCaptures</key>
2063 <dict>
2064 <key>1</key>
2065 <dict>
2066 <key>name</key>
2067 <string>string.other.begin.link.mdx</string>
2068 </dict>
2069 <key>2</key>
2070 <dict>
2071 <key>name</key>
2072 <string>string.other.begin.footnote.mdx</string>
2073 </dict>
2074 <key>3</key>
2075 <dict>
2076 <key>name</key>
2077 <string>entity.name.identifier.mdx</string>
2078 <key>patterns</key>
2079 <array>
2080 <dict>
2081 <key>include</key>
2082 <string>#markdown-string</string>
2083 </dict>
2084 </array>
2085 </dict>
2086 <key>4</key>
2087 <dict>
2088 <key>name</key>
2089 <string>string.other.end.footnote.mdx</string>
2090 </dict>
2091 </dict>
2092 <key>patterns</key>
2093 <array>
2094 <dict>
2095 <key>include</key>
2096 <string>#markdown-sections</string>
2097 </dict>
2098 </array>
2099 <key>while</key>
2100 <string>^(?=[\t ]*$)|(?:^|\G)(?:[ ]{4}|\t)</string>
2101 </dict>
2102 <key>extension-gfm-strikethrough</key>
2103 <dict>
2104 <key>match</key>
2105 <string>(?&lt;=\S)(?&lt;!~)~{1,2}(?!~)|(?&lt;!~)~{1,2}(?=\S)(?!~)</string>
2106 <key>name</key>
2107 <string>string.other.strikethrough.mdx</string>
2108 </dict>
2109 <key>extension-gfm-table</key>
2110 <dict>
2111 <key>begin</key>
2112 <string>(?:^|\G)[\t ]*(?=\|[^\n\r]+\|[ \t]*$)</string>
2113 <key>patterns</key>
2114 <array>
2115 <dict>
2116 <key>match</key>
2117 <string>(?&lt;=\||(?:^|\G))[\t ]*((?:[^\n\r\\\|]|\\[\\\|]?)+?)[\t ]*(?=\||$)</string>
2118 <key>captures</key>
2119 <dict>
2120 <key>1</key>
2121 <dict>
2122 <key>patterns</key>
2123 <array>
2124 <dict>
2125 <key>include</key>
2126 <string>#markdown-text</string>
2127 </dict>
2128 </array>
2129 </dict>
2130 </dict>
2131 </dict>
2132 <dict>
2133 <key>match</key>
2134 <string>(?:\|)</string>
2135 <key>name</key>
2136 <string>markup.list.table-delimiter.mdx</string>
2137 </dict>
2138 </array>
2139 <key>end</key>
2140 <string>^(?=[\t ]*$)|$</string>
2141 </dict>
2142 <key>extension-github-gemoji</key>
2143 <dict>
2144 <key>match</key>
2145 <string>(:)((?:(?:(?:hand_with_index_finger_and_thumb_cros|mailbox_clo|fist_rai|confu)s|r(?:aised_hand_with_fingers_splay|e(?:gister|l(?:iev|ax)))|disappointed_reliev|confound|(?:a(?:ston|ngu)i|flu)sh|unamus|hush)e|(?:chart_with_(?:down|up)wards_tre|large_orange_diamo|small_(?:orang|blu)e_diamo|large_blue_diamo|parasol_on_grou|loud_sou|rewi)n|(?:rightwards_pushing_h|hourglass_flowing_s|leftwards_(?:pushing_)?h|(?:raised_back_of|palm_(?:down|up)|call_me)_h|(?:(?:(?:clippert|ascensi)on|norfolk)_is|christmas_is|desert_is|bouvet_is|new_zea|thai|eng|fin|ire)l|rightwards_h|pinching_h|writing_h|s(?:w(?:itzer|azi)|cot)l|magic_w|ok_h|icel)an|s(?:un_behind_(?:large|small|rain)_clou|hallow_pan_of_foo|tar_of_davi|leeping_be|kateboar|a(?:tisfie|uropo)|hiel|oun|qui)|(?:ear_with_hearing_a|pouring_liqu)i|(?:identification_c|(?:arrow_(?:back|for)|fast_for)w|credit_c|woman_be|biohaz|man_be|l(?:eop|iz))ar|m(?:usical_key|ortar_)boar|(?:drop_of_bl|canned_f)oo|c(?:apital_abc|upi)|person_bal|(?:black_bi|(?:cust|plac)a)r|(?:clip|key)boar|mermai|pea_po|worrie|po(?:la|u)n|threa|dv)d|(?:(?:(?:face_with_open_eyes_and_hand_over|face_with_diagonal|open|no)_mou|h(?:and_over_mou|yacin)|mammo)t|running_shirt_with_sas|(?:(?:fishing_pole_and_|blow)fi|(?:tropical_f|petri_d)i|(?:paint|tooth)bru|banglade|jellyfi)s|(?:camera_fl|wavy_d)as|triump|menora|pouc|blus|watc|das|has)h|(?:s(?:o(?:(?:uth_georgia_south_sandwich|lomon)_island|ck)|miling_face_with_three_heart|t_kitts_nevi|weat_drop|agittariu|c(?:orpiu|issor)|ymbol|hort)|twisted_rightwards_arrow|(?:northern_mariana|heard_mcdonald|(?:british_virgi|us_virgi|pitcair|cayma)n|turks_caicos|us_outlying|(?:falk|a)land|marshall|c(?:anary|ocos)|faroe)_island|(?:face_holding_back_tea|(?:c(?:ard_index_divid|rossed_fing)|pinched_fing)e|night_with_sta)r|(?:two_(?:wo)?men_holding|people_holding|heart|open)_hand|(?:sunrise_over_mountai|(?:congratul|united_n)atio|jea)n|(?:caribbean_)?netherland|(?:f(?:lower_playing_car|ace_in_clou)|crossed_swor|prayer_bea)d|(?:money_with_win|nest_with_eg|crossed_fla|hotsprin)g|revolving_heart|(?:high_brightne|(?:expression|wire)le|(?:tumbler|wine)_gla|milk_gla|compa|dre)s|performing_art|earth_america|orthodox_cros|l(?:ow_brightnes|a(?:tin_cros|o)|ung)|no_pedestrian|c(?:ontrol_kno|lu)b|b(?:ookmark_tab|rick|ean)|nesting_doll|cook_island|(?:fleur_de_l|tenn)i|(?:o(?:ncoming_b|phiuch|ctop)|hi(?:ppopotam|bisc)|trolleyb|m(?:(?:rs|x)_cla|auriti|inib)|belar|cact|abac|(?:cyp|tau)r)u|medal_sport|(?:chopstic|firewor)k|rhinocero|(?:p(?:aw_prin|eanu)|footprin)t|two_heart|princes|(?:hondur|baham)a|barbado|aquariu|c(?:ustom|hain)|maraca|comoro|flag|wale|hug|vh)s|(?:(?:diamond_shape_with_a_dot_ins|playground_sl)id|(?:(?:first_quarter|last_quarter|full|new)_moon_with|(?:zipper|money)_mouth|dotted_line|upside_down|c(?:rying_c|owboy_h)at|(?:disguis|nauseat)ed|neutral|monocle|panda|tired|woozy|clown|nerd|zany|fox)_fac|s(?:t(?:uck_out_tongue_winking_ey|eam_locomotiv)|(?:lightly_(?:frown|smil)|neez|h(?:ush|ak))ing_fac|(?:tudio_micropho|(?:hinto_shr|lot_mach)i|ierra_leo|axopho)n|mall_airplan|un_with_fac|a(?:luting_fac|tellit|k)|haved_ic|y(?:nagogu|ring)|n(?:owfl)?ak|urinam|pong)|(?:black_(?:medium_)?small|white_(?:(?:medium_)?small|large)|(?:black|white)_medium|black_large|orange|purple|yellow|b(?:rown|lue)|red)_squar|(?:(?:perso|woma)n_with_|man_with_)?probing_can|(?:p(?:ut_litter_in_its_pl|outing_f)|frowning_f|cold_f|wind_f|hot_f)ac|(?:arrows_c(?:ounterc)?lockwi|computer_mou|derelict_hou|carousel_hor|c(?:ity_sunri|hee)|heartpul|briefca|racehor|pig_no|lacros)s|(?:(?:face_with_head_band|ideograph_advant|adhesive_band|under|pack)a|currency_exchan|l(?:eft_l)?ugga|woman_jud|name_bad|man_jud|jud)g|face_with_peeking_ey|(?:(?:e(?:uropean_post_off|ar_of_r)|post_off)i|information_sour|ambulan)c|artificial_satellit|(?:busts?_in_silhouet|(?:vulcan_sal|parach)u|m(?:usical_no|ayot)|ro(?:ller_ska|set)|timor_les|ice_ska)t|(?:(?:incoming|red)_envelo|s(?:ao_tome_princi|tethosco)|(?:micro|tele)sco|citysca)p|(?:(?:(?:convenience|department)_st|musical_sc)o|f(?:light_depar|ramed_pic)tu|love_you_gestu|heart_on_fi|japanese_og|cote_divoi|perseve|singapo)r|b(?:ullettrain_sid|eliz|on)|(?:(?:female_|male_)?dete|radioa)ctiv|(?:christmas|deciduous|evergreen|tanabata|palm)_tre|(?:vibration_mo|cape_ver)d|(?:fortune_cook|neckt|self)i|(?:fork_and_)?knif|athletic_sho|(?:p(?:lead|arty)|drool|curs|melt|yawn|ly)ing_fac|vomiting_fac|(?:(?:c(?:urling_st|ycl)|meat_on_b|repeat_|headst)o|(?:fire_eng|tanger|ukra)i|rice_sce|(?:micro|i)pho|champag|pho)n|(?:cricket|video)_gam|(?:boxing_glo|oli)v|(?:d(?:ragon|izzy)|monkey)_fac|(?:m(?:artin|ozamb)iq|fond)u|wind_chim|test_tub|flat_sho|m(?:a(?:ns_sho|t)|icrob|oos|ut)|(?:handsh|fish_c|moon_c|cupc)ak|nail_car|zimbabw|ho(?:neybe|l)|ice_cub|airplan|pensiv|c(?:a(?:n(?:dl|o)|k)|o(?:ffe|oki))|tongu|purs|f(?:lut|iv)|d(?:at|ov)|n(?:iu|os)|kit|rag|ax)e|(?:(?:british_indian_ocean_territo|(?:plate_with_cutl|batt)e|medal_milita|low_batte|hunga|wea)r|family_(?:woman_(?:woman_(?:girl|boy)|girl|boy)|man_(?:woman_(?:girl|boy)|man_(?:girl|boy)|girl|boy))_bo|person_feeding_bab|woman_feeding_bab|s(?:u(?:spension_railwa|nn)|t(?:atue_of_libert|_barthelem|rawberr))|(?:m(?:ountain_cable|ilky_)|aerial_tram)wa|articulated_lorr|man_feeding_bab|mountain_railwa|partly_sunn|(?:vatican_c|infin)it|(?:outbox_tr|inbox_tr|birthd|motorw|paragu|urugu|norw|x_r)a|butterfl|ring_buo|t(?:urke|roph)|angr|fogg)y|(?:(?:perso|woma)n_in_motorized_wheelchai|(?:(?:notebook_with_decorative_c|four_leaf_cl)ov|(?:index_pointing_at_the_vie|white_flo)w|(?:face_with_thermome|non\-potable_wa|woman_firefigh|desktop_compu|m(?:an_firefigh|otor_scoo)|(?:ro(?:ller_coa|o)|oy)s|potable_wa|kick_scoo|thermome|firefigh|helicop|ot)t|(?:woman_factory_wor|(?:woman_office|woman_health|health)_wor|man_(?:factory|office|health)_wor|(?:factory|office)_wor|rice_crac|black_jo|firecrac)k|telephone_receiv|(?:palms_up_toget|f(?:ire_extinguis|eat)|teac)h|(?:(?:open_)?file_fol|level_sli)d|police_offic|f(?:lying_sauc|arm)|woman_teach|roll_of_pap|(?:m(?:iddle_f|an_s)in|woman_sin|hambur|plun|dag)g|do_not_litt|wilted_flow|woman_farm|man_(?:teach|farm)|(?:bell_pe|hot_pe|fli)pp|l(?:o(?:udspeak|ve_lett|bst)|edg|add)|tokyo_tow|c(?:ucumb|lapp|anc)|b(?:e(?:ginn|av)|adg)|print|hamst)e|(?:perso|woma)n_in_manual_wheelchai|m(?:an(?:_in_motorized|(?:_in_man)?ual)|otorized)_wheelchai|(?:person_(?:white|curly|red)_|wheelc)hai|triangular_rule|(?:film_project|e(?:l_salv|cu)ad|elevat|tract|anch)o|s(?:traight_rul|pace_invad|crewdriv|nowboard|unflow|peak|wimm|ing|occ|how|urf|ki)e|r(?:ed_ca|unne|azo)|d(?:o(?:lla|o)|ee)|barbe)r|(?:(?:cloud_with_(?:lightning_and_)?ra|japanese_gobl|round_pushp|liechtenste|mandar|pengu|dolph|bahra|pushp|viol)i|(?:couple(?:_with_heart_wo|kiss_)man|construction_worker|(?:mountain_bik|bow|row)ing|lotus_position|(?:w(?:eight_lift|alk)|climb)ing|white_haired|curly_haired|raising_hand|super(?:villain|hero)|red_haired|basketball|s(?:(?:wimm|urf)ing|assy)|haircut|no_good|(?:vampir|massag)e|b(?:iking|ald)|zombie|fairy|mage|elf|ng)_(?:wo)?ma|(?:(?:couple_with_heart_man|isle_of)_m|(?:couplekiss_woman_|(?:b(?:ouncing_ball|lond_haired)|tipping_hand|pregnant|kneeling|deaf)_|frowning_|s(?:tanding|auna)_|po(?:uting_|lice)|running_|blonde_|o(?:lder|k)_)wom|(?:perso|woma)n_with_turb|(?:b(?:ouncing_ball|lond_haired)|tipping_hand|pregnant|kneeling|deaf)_m|f(?:olding_hand_f|rowning_m)|man_with_turb|(?:turkmen|afghan|pak)ist|s(?:tanding_m|(?:outh_s)?ud|auna_m)|po(?:uting_|lice)m|running_m|azerbaij|k(?:yrgyz|azakh)st|tajikist|uzbekist|o(?:lder_m|k_m|ce)|(?:orang|bh)ut|taiw|jord)a|s(?:mall_red_triangle_dow|(?:valbard_jan_may|int_maart|ev)e|afety_pi|top_sig|t_marti|(?:corpi|po|o)o|wede)|(?:heavy_(?:d(?:ivision|ollar)|equals|minus|plus)|no_entry|female|male)_sig|(?:arrow_(?:heading|double)_d|p(?:erson_with_cr|oint_d)|arrow_up_d|thumbsd)ow|(?:house_with_gard|l(?:ock_with_ink_p|eafy_gre)|dancing_(?:wo)?m|fountain_p|keycap_t|chick|ali|yem|od)e|(?:izakaya|jack_o)_lanter|(?:funeral_u|(?:po(?:stal_h|pc)|capric)o|unico)r|chess_paw|b(?:a(?:llo|c)o|eni|rai)|l(?:anter|io)|c(?:o(?:ff)?i|row)|melo|rame|oma|yar)n|(?:s(?:t(?:uck_out_tongue_closed_ey|_vincent_grenadin)|kull_and_crossbon|unglass|pad)|(?:french_souther|palestinia)n_territori|(?:face_with_spiral|kissing_smiling)_ey|united_arab_emirat|kissing_closed_ey|(?:clinking_|dark_sun|eye)glass|(?:no_mobile_|head)phon|womans_cloth|b(?:allet_sho|lueberri)|philippin|(?:no_bicyc|seychel)l|roll_ey|(?:cher|a)ri|p(?:ancak|isc)|maldiv|leav)es|(?:f(?:amily_(?:woman_(?:woman_)?|man_(?:woman_|man_)?)girl_gir|earfu)|(?:woman_playing_hand|m(?:an_playing_hand|irror_)|c(?:onfetti|rystal)_|volley|track|base|8)bal|(?:(?:m(?:ailbox_with_(?:no_)?m|onor)|cockt|e\-m)a|(?:person|bride|woman)_with_ve|man_with_ve|light_ra|braz|ema)i|(?:transgender|baby)_symbo|passport_contro|(?:arrow_(?:down|up)_sm|rice_b|footb)al|(?:dromedary_cam|ferris_whe|love_hot|high_he|pretz|falaf|isra)e|page_with_cur|me(?:dical_symbo|ta)|(?:n(?:ewspaper_ro|o_be)|bellhop_be)l|rugby_footbal|s(?:chool_satche|(?:peak|ee)_no_evi|oftbal|crol|anda|nai|hel)|(?:peace|atom)_symbo|hear_no_evi|cora|hote|bage|labe|rof|ow)l|(?:(?:negative_squared_cross|heavy_exclamation|part_alternation)_mar|(?:eight_spoked_)?asteris|(?:ballot_box_with_che|(?:(?:mantelpiece|alarm|timer)_c|un)lo|(?:ha(?:(?:mmer_and|ir)_p|tch(?:ing|ed)_ch)|baby_ch|joyst)i|railway_tra|lipsti|peaco)c|heavy_check_mar|white_check_mar|tr(?:opical_drin|uc)|national_par|pickup_truc|diving_mas|floppy_dis|s(?:tar_struc|hamroc|kun|har)|chipmun|denmar|duc|hoo|lin)k|(?:leftwards_arrow_with_h|arrow_right_h|(?:o(?:range|pen)|closed|blue)_b)ook|(?:woman_playing_water_pol|m(?:an(?:_(?:playing_water_pol|with_gua_pi_ma|in_tuxed)|g)|ontenegr|o(?:roc|na)c|e(?:xic|tr|m))|(?:perso|woma)n_in_tuxed|(?:trinidad_toba|vir)g|water_buffal|b(?:urkina_fas|a(?:mbo|nj)|ent)|puerto_ric|water_pol|flaming|kangaro|(?:mosqu|burr)it|(?:avoc|torn)ad|curaca|lesoth|potat|ko(?:sov|k)|tomat|d(?:ang|od)|yo_y|hoch|t(?:ac|og)|zer)o|(?:c(?:entral_african|zech)|dominican)_republic|(?:eight_pointed_black_s|six_pointed_s|qa)tar|(?:business_suit_levitat|(?:classical_buil|breast_fee)d|(?:woman_cartwhee|m(?:an_(?:cartwhee|jugg)|en_wrest)|women_wrest|woman_jugg|face_exha|cartwhee|wrest|dump)l|c(?:hildren_cross|amp)|woman_facepalm|woman_shrugg|man_(?:facepalm|shrugg)|people_hugg|(?:person_fe|woman_da|man_da)nc|fist_oncom|horse_rac|(?:no_smo|thin)k|laugh|s(?:eedl|mok)|park|w(?:arn|edd))ing|f(?:a(?:mily(?:_(?:woman_(?:woman_(?:girl|boy)|girl|boy)|man_(?:woman_(?:girl|boy)|man_(?:girl|boy)|girl|boy)))?|ctory)|o(?:u(?:ntain|r)|ot|g)|r(?:owning)?|i(?:re|s[ht])|ly|u)|(?:(?:(?:information_desk|handball|bearded)_|(?:frowning|ok)_|juggling_|mer)pers|(?:previous_track|p(?:lay_or_p)?ause|black_square|white_square|next_track|r(?:ecord|adio)|eject)_butt|(?:wa[nx]ing_(?:crescent|gibbous)_m|bowl_with_sp|crescent_m|racc)o|(?:b(?:ouncing_ball|lond_haired)|tipping_hand|pregnant|kneeling|deaf)_pers|s(?:t(?:_pierre_miquel|op_butt|ati)|tanding_pers|peech_ballo|auna_pers)|r(?:eminder_r)?ibb|thought_ballo|watermel|badmint|c(?:amero|ray)|le(?:ban|m)|oni|bis)on|(?:heavy_heart_exclama|building_construc|heart_decora|exclama)tion|(?:(?:triangular_flag_on_po|(?:(?:woman_)?technolog|m(?:ountain_bicycl|an_technolog)|bicycl)i|(?:wo)?man_scienti|(?:wo)?man_arti|s(?:afety_ve|cienti)|empty_ne)s|(?:vertical_)?traffic_ligh|(?:rescue_worker_helm|military_helm|nazar_amul|city_suns|wastebask|dropl|t(?:rump|oil)|bouqu|buck|magn|secr)e|one_piece_swimsui|(?:(?:arrow_(?:low|upp)er|point)_r|bridge_at_n|copyr|mag_r)igh|(?:bullettrain_fro|(?:potted_pl|croiss|e(?:ggpl|leph))a)n|s(?:t(?:ar_and_cresc|ud)en|cream_ca|mi(?:ley?|rk)_ca|(?:peed|ail)boa|hir)|(?:arrow_(?:low|upp)er|point)_lef|woman_astronau|r(?:o(?:tating_ligh|cke)|eceip)|heart_eyes_ca|man_astronau|(?:woman_stud|circus_t|man_stud|trid)en|(?:ringed_pla|file_cabi)ne|nut_and_bol|(?:older_)?adul|k(?:i(?:ssing_ca|wi_frui)|uwai|no)|(?:pouting_c|c(?:ut_of_m|old_sw)e|womans_h|montserr|(?:(?:motor_|row)b|lab_c)o|heartbe|toph)a|(?:woman_pil|honey_p|man_pil|[cp]arr|teap|rob)o|hiking_boo|arrow_lef|fist_righ|flashligh|f(?:ist_lef|ee)|black_ca|astronau|(?:c(?:hest|oco)|dough)nu|innocen|joy_ca|artis|(?:acce|egy)p|co(?:me|a)|pilo)t|(?:heavy_multiplication_|t\-re)x|(?:s(?:miling_face_with_te|piral_calend)|oncoming_police_c|chocolate_b|ra(?:ilway|cing)_c|police_c|polar_be|teddy_be|madagasc|blue_c|calend|myanm)ar|c(?:l(?:o(?:ud(?:_with_lightning)?|ck(?:1[0-2]?|[2-9]))|ap)?|o(?:uple(?:_with_heart|kiss)?|nstruction|mputer|ok|p|w)|a(?:r(?:d_index)?|mera)|r(?:icket|y)|h(?:art|ild))|(?:m(?:artial_arts_unifo|echanical_a)r|(?:cherry_)?blosso|b(?:aggage_clai|roo)|ice_?crea|facepal|mushroo|restroo|vietna|dru|yu)m|(?:woman_with_headscar|m(?:obile_phone_of|aple_lea)|fallen_lea|wol)f|(?:(?:closed_lock_with|old)_|field_hoc|ice_hoc|han|don)key|g(?:lobe_with_meridians|r(?:e(?:y_(?:exclama|ques)tion|e(?:n(?:_(?:square|circle|salad|apple|heart|book)|land)|ce)|y_heart|nada)|i(?:mac|nn)ing|apes)|u(?:inea_bissau|ernsey|am|n)|(?:(?:olfing|enie)_(?:wo)?|uards(?:wo)?)man|(?:inger_roo|oal_ne|hos)t|(?:uadeloup|ame_di|iraff|oos)e|ift_heart|i(?:braltar|rl)|(?:uatemal|(?:eorg|amb)i|orill|uyan|han)a|uide_dog|(?:oggl|lov)es|arlic|emini|uitar|abon|oat|ear|b)|construction_worker|(?:(?:envelope_with|bow_and)_ar|left_right_ar|raised_eyeb)row|(?:(?:oncoming_automob|crocod)i|right_anger_bubb|l(?:eft_speech_bubb|otion_bott|ady_beet)|congo_brazzavil|eye_speech_bubb|(?:large_blue|orange|purple|yellow|brown)_circ|(?:(?:european|japanese)_cas|baby_bot)t|b(?:alance_sca|eet)|s(?:ewing_need|weat_smi)|(?:black|white|red)_circ|(?:motor|re)cyc|pood|turt|tama|waff|musc|eag)le|first_quarter_moon|s(?:m(?:all_red_triangle|i(?:ley?|rk))|t(?:uck_out_tongue|ar)|hopping|leeping|p(?:arkle|ider)|unrise|nowman|chool|cream|k(?:ull|i)|weat|ix|a)|(?:(?:b(?:osnia_herzegovi|ana)|wallis_futu|(?:french_gui|botsw)a|argenti|st_hele)n|(?:(?:equatorial|papua_new)_guin|north_kor|eritr)e|t(?:ristan_da_cunh|ad)|(?:(?:(?:french_poly|indo)ne|tuni)s|(?:new_caledo|ma(?:urita|cedo)|lithua|(?:tanz|alb|rom)a|arme|esto)n|diego_garc|s(?:audi_arab|t_luc|lov(?:ak|en)|omal|erb)|e(?:arth_as|thiop)|m(?:icrone|alay)s|(?:austra|mongo)l|c(?:ambod|roat)|(?:bulga|alge)r|(?:colom|nami|zam)b|boliv|l(?:iber|atv))i|(?:wheel_of_dhar|cine|pana)m|(?:(?:(?:closed|beach|open)_)?umbrel|ceuta_melil|venezue|ang(?:uil|o)|koa)l|c(?:ongo_kinshas|anad|ub)|(?:western_saha|a(?:mpho|ndor)|zeb)r|american_samo|video_camer|m(?:o(?:vie_camer|ldov)|alt|eg)|(?:earth_af|costa_)ric|s(?:outh_afric|ri_lank|a(?:mo|nt))|bubble_te|(?:antarct|jama)ic|ni(?:caragu|geri|nj)|austri|pi(?:nat|zz)|arub|k(?:eny|aab)|indi|u7a7|l(?:lam|ib[ry])|dn)a|l(?:ast_quarter_moon|o(?:tus|ck)|ips|eo)|(?:hammer_and_wren|c(?:ockroa|hur)|facepun|wren|crut|pun)ch|s(?:nowman_with_snow|ignal_strength|weet_potato|miling_imp|p(?:ider_web|arkle[rs])|w(?:im_brief|an)|a(?:n(?:_marino|dwich)|lt)|topwatch|t(?:a(?:dium|r[2s])|ew)|l(?:e(?:epy|d)|oth)|hrimp|yria|carf|(?:hee|oa)p|ea[lt]|h(?:oe|i[pt])|o[bs])|(?:s(?:tuffed_flatbre|p(?:iral_notep|eaking_he))|(?:exploding_h|baguette_br|flatbr)e)ad|(?:arrow_(?:heading|double)_u|(?:p(?:lace_of_wor|assenger_)sh|film_str|tul)i|page_facing_u|biting_li|(?:billed_c|world_m)a|mouse_tra|(?:curly_lo|busst)o|thumbsu|lo(?:llip)?o|clam|im)p|(?:anatomical|light_blue|sparkling|kissing|mending|orange|purple|yellow|broken|b(?:rown|l(?:ack|ue))|pink)_heart|(?:(?:transgender|black)_fla|mechanical_le|(?:checkered|pirate)_fla|electric_plu|rainbow_fla|poultry_le|service_do|white_fla|luxembour|fried_eg|moneyba|h(?:edgeh|otd)o|shru)g|(?:cloud_with|mountain)_snow|(?:(?:antigua_barb|berm)u|(?:kh|ug)an|rwan)da|(?:3r|2n)d_place_medal|1(?:st_place_medal|234|00)|lotus_position|(?:w(?:eight_lift|alk)|climb)ing|(?:(?:cup_with_str|auto_ricksh)a|carpentry_sa|windo|jigsa)w|(?:(?:couch_and|diya)_la|f(?:ried_shri|uelpu))mp|(?:woman_mechan|man_mechan|alemb)ic|(?:european_un|accord|collis|reun)ion|(?:flight_arriv|hospit|portug|seneg|nep)al|card_file_box|(?:(?:oncoming_)?tax|m(?:o(?:unt_fuj|ya)|alaw)|s(?:paghett|ush|ar)|b(?:r(?:occol|une)|urund)|(?:djibou|kiriba)t|hait|fij)i|(?:shopping_c|white_he|bar_ch)art|d(?:isappointed|ominica|e(?:sert)?)|raising_hand|super(?:villain|hero)|b(?:e(?:verage_box|ers|d)|u(?:bbles|lb|g)|i(?:k(?:ini|e)|rd)|o(?:o(?:ks|t)|a[rt]|y)|read|a[cn]k)|ra(?:ised_hands|bbit2|t)|(?:hindu_tem|ap)ple|thong_sandal|a(?:r(?:row_(?:right|down|up)|t)|bc?|nt)?|r(?:a(?:i(?:sed_hand|nbow)|bbit|dio|m)|u(?:nning)?|epeat|i(?:ng|ce)|o(?:ck|se))|takeout_box|(?:flying_|mini)disc|(?:(?:interrob|yin_y)a|b(?:o(?:omera|wli)|angba)|(?:ping_p|hong_k)o|calli|mahjo)ng|b(?:a(?:llot_box|sket|th?|by)|o(?:o(?:k(?:mark)?|m)|w)|u(?:tter|s)|e(?:ll|er?|ar))?|heart_eyes|basketball|(?:paperclip|dancer|ticket)s|point_up_2|(?:wo)?man_cook|n(?:ew(?:spaper)?|o(?:tebook|_entry)|iger)|t(?:e(?:lephone|a)|o(?:oth|p)|r(?:oll)?|wo)|h(?:o(?:u(?:rglass|se)|rse)|a(?:mmer|nd)|eart)|paperclip|full_moon|(?:b(?:lack_ni|athtu|om)|her)b|(?:long|oil)_drum|pineapple|(?:clock(?:1[0-2]?|[2-9])3|u6e8)0|p(?:o(?:int_up|ut)|r(?:ince|ay)|i(?:ck|g)|en)|e(?:nvelope|ight|u(?:ro)?|gg|ar|ye|s)|m(?:o(?:u(?:ntain|se)|nkey|on)|echanic|a(?:ilbox|g|n)|irror)?|new_moon|d(?:iamonds|olls|art)|question|k(?:iss(?:ing)?|ey)|haircut|no_good|(?:vampir|massag)e|g(?:olf(?:ing)?|u(?:inea|ard)|e(?:nie|m)|ift|rin)|h(?:a(?:ndbag|msa)|ouses|earts|ut)|postbox|toolbox|(?:pencil|t(?:rain|iger)|whale|cat|dog)2|belgium|(?:volca|kimo)no|(?:vanuat|tuval|pala|naur|maca)u|tokelau|o(?:range|ne?|m|k)?|office|dancer|ticket|dragon|pencil|zombie|w(?:o(?:mens|rm|od)|ave|in[gk]|c)|m(?:o(?:sque|use2)|e(?:rman|ns)|a(?:li|sk))|jersey|tshirt|w(?:heel|oman)|dizzy|j(?:apan|oy)|t(?:rain|iger)|whale|fairy|a(?:nge[lr]|bcd|tm)|c(?:h(?:a(?:ir|d)|ile)|a(?:ndy|mel)|urry|rab|o(?:rn|ol|w2)|[dn])|p(?:ager|e(?:a(?:ch|r)|ru)|i(?:g2|ll|e)|oop)|n(?:otes|ine)|t(?:onga|hree|ent|ram|[mv])|f(?:erry|r(?:ies|ee|og)|ax)|u(?:7(?:533|981|121)|5(?:5b6|408|272)|6(?:307|70[89]))|mage|e(?:yes|nd)|i(?:ra[nq]|t)|cat|dog|elf|z(?:zz|ap)|yen|j(?:ar|p)|leg|id|u[kps]|ng|o[2x]|vs|kr|[\+\x2D]1|x|v)(:)</string>
2146 <key>name</key>
2147 <string>string.emoji.mdx</string>
2148 <key>captures</key>
2149 <dict>
2150 <key>1</key>
2151 <dict>
2152 <key>name</key>
2153 <string>punctuation.definition.gemoji.begin.mdx</string>
2154 </dict>
2155 <key>2</key>
2156 <dict>
2157 <key>name</key>
2158 <string>keyword.control.gemoji.mdx</string>
2159 </dict>
2160 <key>3</key>
2161 <dict>
2162 <key>name</key>
2163 <string>punctuation.definition.gemoji.end.mdx</string>
2164 </dict>
2165 </dict>
2166 </dict>
2167 <key>extension-github-mention</key>
2168 <dict>
2169 <key>match</key>
2170 <string>(?&lt;![0-9A-Za-z_`])(@)((?:[0-9A-Za-z][0-9A-Za-z-]{0,38})(?:\/(?:[0-9A-Za-z][0-9A-Za-z-]{0,38}))?)(?![0-9A-Za-z_`])</string>
2171 <key>name</key>
2172 <string>string.mention.mdx</string>
2173 <key>captures</key>
2174 <dict>
2175 <key>1</key>
2176 <dict>
2177 <key>name</key>
2178 <string>punctuation.definition.mention.begin.mdx</string>
2179 </dict>
2180 <key>2</key>
2181 <dict>
2182 <key>name</key>
2183 <string>string.other.link.mention.mdx</string>
2184 </dict>
2185 </dict>
2186 </dict>
2187 <key>extension-github-reference</key>
2188 <dict>
2189 <key>patterns</key>
2190 <array>
2191 <dict>
2192 <key>match</key>
2193 <string>(?&lt;![0-9A-Za-z_])(?:((?i:ghsa-|cve-))([A-Za-z0-9]+)|((?i:gh-|#))([0-9]+))(?![0-9A-Za-z_])</string>
2194 <key>name</key>
2195 <string>string.reference.mdx</string>
2196 <key>captures</key>
2197 <dict>
2198 <key>1</key>
2199 <dict>
2200 <key>name</key>
2201 <string>punctuation.definition.reference.begin.mdx</string>
2202 </dict>
2203 <key>2</key>
2204 <dict>
2205 <key>name</key>
2206 <string>string.other.link.reference.security-advisory.mdx</string>
2207 </dict>
2208 <key>3</key>
2209 <dict>
2210 <key>name</key>
2211 <string>punctuation.definition.reference.begin.mdx</string>
2212 </dict>
2213 <key>4</key>
2214 <dict>
2215 <key>name</key>
2216 <string>string.other.link.reference.issue-or-pr.mdx</string>
2217 </dict>
2218 </dict>
2219 </dict>
2220 <dict>
2221 <key>match</key>
2222 <string>(?&lt;![^\t\n\r \(@\[\{])((?:[0-9A-Za-z][0-9A-Za-z-]{0,38})(?:\/(?:(?:\.git[0-9A-Za-z_-]|\.(?!git)|[0-9A-Za-z_-])+))?)(#)([0-9]+)(?![0-9A-Za-z_])</string>
2223 <key>name</key>
2224 <string>string.reference.mdx</string>
2225 <key>captures</key>
2226 <dict>
2227 <key>1</key>
2228 <dict>
2229 <key>name</key>
2230 <string>string.other.link.reference.user.mdx</string>
2231 </dict>
2232 <key>2</key>
2233 <dict>
2234 <key>name</key>
2235 <string>punctuation.definition.reference.begin.mdx</string>
2236 </dict>
2237 <key>3</key>
2238 <dict>
2239 <key>name</key>
2240 <string>string.other.link.reference.issue-or-pr.mdx</string>
2241 </dict>
2242 </dict>
2243 </dict>
2244 </array>
2245 </dict>
2246 <key>extension-math-flow</key>
2247 <dict>
2248 <key>begin</key>
2249 <string>(?:^|\G)[\t ]*(\${2,})([^\n\r\$]*)$</string>
2250 <key>beginCaptures</key>
2251 <dict>
2252 <key>1</key>
2253 <dict>
2254 <key>name</key>
2255 <string>string.other.begin.math.flow.mdx</string>
2256 </dict>
2257 <key>2</key>
2258 <dict>
2259 <key>patterns</key>
2260 <array>
2261 <dict>
2262 <key>include</key>
2263 <string>#markdown-string</string>
2264 </dict>
2265 </array>
2266 </dict>
2267 </dict>
2268 <key>contentName</key>
2269 <string>markup.raw.math.flow.mdx</string>
2270 <key>end</key>
2271 <string>(\1)(?:[\t ]*$)</string>
2272 <key>endCaptures</key>
2273 <dict>
2274 <key>1</key>
2275 <dict>
2276 <key>name</key>
2277 <string>string.other.end.math.flow.mdx</string>
2278 </dict>
2279 </dict>
2280 <key>name</key>
2281 <string>markup.code.other.mdx</string>
2282 </dict>
2283 <key>extension-math-text</key>
2284 <dict>
2285 <key>match</key>
2286 <string>(?&lt;!\$)(\${2,})(?!\$)(.+?)(?&lt;!\$)(\1)(?!\$)</string>
2287 <key>captures</key>
2288 <dict>
2289 <key>1</key>
2290 <dict>
2291 <key>name</key>
2292 <string>string.other.begin.math.mdx</string>
2293 </dict>
2294 <key>2</key>
2295 <dict>
2296 <key>name</key>
2297 <string>markup.raw.math.mdx markup.inline.raw.math.mdx</string>
2298 </dict>
2299 <key>3</key>
2300 <dict>
2301 <key>name</key>
2302 <string>string.other.end.math.mdx</string>
2303 </dict>
2304 </dict>
2305 </dict>
2306 <key>extension-mdx-esm</key>
2307 <dict>
2308 <key>name</key>
2309 <string>meta.embedded.tsx</string>
2310 <key>begin</key>
2311 <string>(?:^|\G)(?=(?i:export|import)[ ])</string>
2312 <key>end</key>
2313 <string>^(?=[\t ]*$)|$</string>
2314 <key>patterns</key>
2315 <array>
2316 <dict>
2317 <key>include</key>
2318 <string>source.tsx#statements</string>
2319 </dict>
2320 </array>
2321 </dict>
2322 <key>extension-mdx-expression-flow</key>
2323 <dict>
2324 <key>begin</key>
2325 <string>(?:^|\G)[\t ]*(\{)(?!.*\}[\t ]*.)</string>
2326 <key>beginCaptures</key>
2327 <dict>
2328 <key>1</key>
2329 <dict>
2330 <key>name</key>
2331 <string>string.other.begin.expression.mdx.js</string>
2332 </dict>
2333 </dict>
2334 <key>contentName</key>
2335 <string>meta.embedded.tsx</string>
2336 <key>end</key>
2337 <string>(\})(?:[\t ]*$)</string>
2338 <key>endCaptures</key>
2339 <dict>
2340 <key>1</key>
2341 <dict>
2342 <key>name</key>
2343 <string>string.other.begin.expression.mdx.js</string>
2344 </dict>
2345 </dict>
2346 <key>patterns</key>
2347 <array>
2348 <dict>
2349 <key>include</key>
2350 <string>source.tsx#expression</string>
2351 </dict>
2352 </array>
2353 </dict>
2354 <key>extension-mdx-expression-text</key>
2355 <dict>
2356 <key>begin</key>
2357 <string>\{</string>
2358 <key>beginCaptures</key>
2359 <dict>
2360 <key>0</key>
2361 <dict>
2362 <key>name</key>
2363 <string>string.other.begin.expression.mdx.js</string>
2364 </dict>
2365 </dict>
2366 <key>contentName</key>
2367 <string>meta.embedded.tsx</string>
2368 <key>end</key>
2369 <string>\}</string>
2370 <key>endCaptures</key>
2371 <dict>
2372 <key>0</key>
2373 <dict>
2374 <key>name</key>
2375 <string>string.other.begin.expression.mdx.js</string>
2376 </dict>
2377 </dict>
2378 <key>patterns</key>
2379 <array>
2380 <dict>
2381 <key>include</key>
2382 <string>source.tsx#expression</string>
2383 </dict>
2384 </array>
2385 </dict>
2386 <key>extension-mdx-jsx-flow</key>
2387 <dict>
2388 <key>begin</key>
2389 <string>(?&lt;=^|\G|\&gt;)[\t ]*(&lt;)(?=(?![\t\n\r ]))(?:\s*(/))?(?:\s*(?:(?:((?:[_$[:alpha:]][-_$[:alnum:]]*))\s*(:)\s*((?:[_$[:alpha:]][-_$[:alnum:]]*)))|((?:(?:[_$[:alpha:]][_$[:alnum:]]*)(?:\s*\.\s*(?:[_$[:alpha:]][-_$[:alnum:]]*))+))|((?:[_$[:upper:]][_$[:alnum:]]*))|((?:[_$[:alpha:]][-_$[:alnum:]]*)))(?=[\s\/\&gt;\{]))?</string>
2390 <key>beginCaptures</key>
2391 <dict>
2392 <key>1</key>
2393 <dict>
2394 <key>name</key>
2395 <string>punctuation.definition.tag.end.jsx</string>
2396 </dict>
2397 <key>2</key>
2398 <dict>
2399 <key>name</key>
2400 <string>punctuation.definition.tag.closing.jsx</string>
2401 </dict>
2402 <key>3</key>
2403 <dict>
2404 <key>name</key>
2405 <string>entity.name.tag.namespace.jsx</string>
2406 </dict>
2407 <key>4</key>
2408 <dict>
2409 <key>name</key>
2410 <string>punctuation.separator.namespace.jsx</string>
2411 </dict>
2412 <key>5</key>
2413 <dict>
2414 <key>name</key>
2415 <string>entity.name.tag.local.jsx</string>
2416 </dict>
2417 <key>6</key>
2418 <dict>
2419 <key>name</key>
2420 <string>support.class.component.jsx</string>
2421 </dict>
2422 <key>7</key>
2423 <dict>
2424 <key>name</key>
2425 <string>support.class.component.jsx</string>
2426 </dict>
2427 <key>8</key>
2428 <dict>
2429 <key>name</key>
2430 <string>entity.name.tag.jsx</string>
2431 </dict>
2432 </dict>
2433 <key>patterns</key>
2434 <array>
2435 <dict>
2436 <key>include</key>
2437 <string>source.tsx#jsx-tag-attribute-name</string>
2438 </dict>
2439 <dict>
2440 <key>include</key>
2441 <string>source.tsx#jsx-tag-attribute-assignment</string>
2442 </dict>
2443 <dict>
2444 <key>include</key>
2445 <string>source.tsx#jsx-string-double-quoted</string>
2446 </dict>
2447 <dict>
2448 <key>include</key>
2449 <string>source.tsx#jsx-string-single-quoted</string>
2450 </dict>
2451 <dict>
2452 <key>include</key>
2453 <string>source.tsx#jsx-evaluated-code</string>
2454 </dict>
2455 <dict>
2456 <key>include</key>
2457 <string>source.tsx#jsx-tag-attributes-illegal</string>
2458 </dict>
2459 </array>
2460 <key>end</key>
2461 <string>(?:(\/)\s*)?(&gt;)</string>
2462 <key>endCaptures</key>
2463 <dict>
2464 <key>1</key>
2465 <dict>
2466 <key>name</key>
2467 <string>punctuation.definition.tag.self-closing.jsx</string>
2468 </dict>
2469 <key>2</key>
2470 <dict>
2471 <key>name</key>
2472 <string>punctuation.definition.tag.end.jsx</string>
2473 </dict>
2474 </dict>
2475 </dict>
2476 <key>extension-mdx-jsx-text</key>
2477 <dict>
2478 <key>begin</key>
2479 <string>(&lt;)(?=(?![\t\n\r ]))(?:\s*(/))?(?:\s*(?:(?:((?:[_$[:alpha:]][-_$[:alnum:]]*))\s*(:)\s*((?:[_$[:alpha:]][-_$[:alnum:]]*)))|((?:(?:[_$[:alpha:]][_$[:alnum:]]*)(?:\s*\.\s*(?:[_$[:alpha:]][-_$[:alnum:]]*))+))|((?:[_$[:upper:]][_$[:alnum:]]*))|((?:[_$[:alpha:]][-_$[:alnum:]]*)))(?=[\s\/\&gt;\{]))?</string>
2480 <key>beginCaptures</key>
2481 <dict>
2482 <key>1</key>
2483 <dict>
2484 <key>name</key>
2485 <string>punctuation.definition.tag.end.jsx</string>
2486 </dict>
2487 <key>2</key>
2488 <dict>
2489 <key>name</key>
2490 <string>punctuation.definition.tag.closing.jsx</string>
2491 </dict>
2492 <key>3</key>
2493 <dict>
2494 <key>name</key>
2495 <string>entity.name.tag.namespace.jsx</string>
2496 </dict>
2497 <key>4</key>
2498 <dict>
2499 <key>name</key>
2500 <string>punctuation.separator.namespace.jsx</string>
2501 </dict>
2502 <key>5</key>
2503 <dict>
2504 <key>name</key>
2505 <string>entity.name.tag.local.jsx</string>
2506 </dict>
2507 <key>6</key>
2508 <dict>
2509 <key>name</key>
2510 <string>support.class.component.jsx</string>
2511 </dict>
2512 <key>7</key>
2513 <dict>
2514 <key>name</key>
2515 <string>support.class.component.jsx</string>
2516 </dict>
2517 <key>8</key>
2518 <dict>
2519 <key>name</key>
2520 <string>entity.name.tag.jsx</string>
2521 </dict>
2522 </dict>
2523 <key>patterns</key>
2524 <array>
2525 <dict>
2526 <key>include</key>
2527 <string>source.tsx#jsx-tag-attribute-name</string>
2528 </dict>
2529 <dict>
2530 <key>include</key>
2531 <string>source.tsx#jsx-tag-attribute-assignment</string>
2532 </dict>
2533 <dict>
2534 <key>include</key>
2535 <string>source.tsx#jsx-string-double-quoted</string>
2536 </dict>
2537 <dict>
2538 <key>include</key>
2539 <string>source.tsx#jsx-string-single-quoted</string>
2540 </dict>
2541 <dict>
2542 <key>include</key>
2543 <string>source.tsx#jsx-evaluated-code</string>
2544 </dict>
2545 <dict>
2546 <key>include</key>
2547 <string>source.tsx#jsx-tag-attributes-illegal</string>
2548 </dict>
2549 </array>
2550 <key>end</key>
2551 <string>(?:(\/)\s*)?(&gt;)</string>
2552 <key>endCaptures</key>
2553 <dict>
2554 <key>1</key>
2555 <dict>
2556 <key>name</key>
2557 <string>punctuation.definition.tag.self-closing.jsx</string>
2558 </dict>
2559 <key>2</key>
2560 <dict>
2561 <key>name</key>
2562 <string>punctuation.definition.tag.end.jsx</string>
2563 </dict>
2564 </dict>
2565 </dict>
2566 <key>extension-toml</key>
2567 <dict>
2568 <key>begin</key>
2569 <string>\A\+{3}$</string>
2570 <key>end</key>
2571 <string>^\+{3}$</string>
2572 <key>beginCaptures</key>
2573 <dict>
2574 <key>0</key>
2575 <dict>
2576 <key>name</key>
2577 <string>string.other.begin.toml</string>
2578 </dict>
2579 </dict>
2580 <key>endCaptures</key>
2581 <dict>
2582 <key>0</key>
2583 <dict>
2584 <key>name</key>
2585 <string>string.other.end.toml</string>
2586 </dict>
2587 </dict>
2588 <key>contentName</key>
2589 <string>meta.embedded.toml</string>
2590 <key>patterns</key>
2591 <array>
2592 <dict>
2593 <key>include</key>
2594 <string>source.toml</string>
2595 </dict>
2596 </array>
2597 </dict>
2598 <key>extension-yaml</key>
2599 <dict>
2600 <key>begin</key>
2601 <string>\A-{3}$</string>
2602 <key>end</key>
2603 <string>^-{3}$</string>
2604 <key>beginCaptures</key>
2605 <dict>
2606 <key>0</key>
2607 <dict>
2608 <key>name</key>
2609 <string>string.other.begin.yaml</string>
2610 </dict>
2611 </dict>
2612 <key>endCaptures</key>
2613 <dict>
2614 <key>0</key>
2615 <dict>
2616 <key>name</key>
2617 <string>string.other.end.yaml</string>
2618 </dict>
2619 </dict>
2620 <key>contentName</key>
2621 <string>meta.embedded.yaml</string>
2622 <key>patterns</key>
2623 <array>
2624 <dict>
2625 <key>include</key>
2626 <string>source.yaml</string>
2627 </dict>
2628 </array>
2629 </dict>
2630 <key>whatwg-html-data-character-reference-named-terminated</key>
2631 <dict>
2632 <key>match</key>
2633 <string>(&amp;)((?:C(?:(?:o(?:unterClockwiseCo)?|lockwiseCo)ntourIntegra|cedi)|(?:(?:Not(?:S(?:quareSu(?:per|b)set|u(?:cceeds|(?:per|b)set))|Precedes|Greater|Tilde|Less)|Not(?:Righ|Lef)tTriangle|(?:Not(?:(?:Succeed|Precede|Les)s|Greater)|(?:Precede|Succeed)s|Less)Slant|SquareSu(?:per|b)set|(?:Not(?:Greater|Tilde)|Tilde|Less)Full|RightTriangle|LeftTriangle|Greater(?:Slant|Full)|Precedes|Succeeds|Superset|NotHump|Subset|Tilde|Hump)Equ|int(?:er)?c|DotEqu)a|DoubleContourIntegra|(?:n(?:short)?parall|shortparall|p(?:arall|rur))e|(?:rightarrowta|l(?:eftarrowta|ced|ata|Ata)|sced|rata|perm|rced|rAta|ced)i|Proportiona|smepars|e(?:qvpars|pars|xc|um)|Integra|suphso|rarr[pt]|n(?:pars|tg)|l(?:arr[pt]|cei)|Rarrt|(?:hybu|fora)l|ForAl|[GKLNR-Tcknt]cedi|rcei|iexc|gime|fras|[uy]um|oso|dso|ium|Ium)l|D(?:o(?:uble(?:(?:L(?:ong(?:Left)?R|eftR)ight|L(?:ongL)?eft|UpDown|Right|Up)Arrow|Do(?:wnArrow|t))|wn(?:ArrowUpA|TeeA|a)rrow)|iacriticalDot|strok|ashv|cy)|(?:(?:(?:N(?:(?:otN)?estedGreater|ot(?:Greater|Less))|Less(?:Equal)?)Great|GreaterGreat|l[lr]corn|mark|east)e|Not(?:Double)?VerticalBa|(?:Not(?:Righ|Lef)tTriangleB|(?:(?:Righ|Lef)tDown|Right(?:Up)?|Left(?:Up)?)VectorB|RightTriangleB|Left(?:Triangle|Arrow)B|RightArrowB|V(?:er(?:ticalB|b)|b)|UpArrowB|l(?:ur(?:ds|u)h|dr(?:us|d)h|trP|owb|H)|profal|r(?:ulu|dld)h|b(?:igst|rvb)|(?:wed|ve[er])b|s(?:wn|es)w|n(?:wne|ese|sp|hp)|gtlP|d(?:oll|uh|H)|(?:hor|ov)b|u(?:dh|H)|r(?:lh|H)|ohb|hb|St)a|D(?:o(?:wn(?:(?:Left(?:Right|Tee)|RightTee)Vecto|(?:(?:Righ|Lef)tVector|Arrow)Ba)|ubleVerticalBa)|a(?:gge|r)|sc|f)|(?:(?:(?:Righ|Lef)tDown|(?:Righ|Lef)tUp)Tee|(?:Righ|Lef)tUpDown)Vecto|VerticalSeparato|(?:Left(?:Right|Tee)|RightTee)Vecto|less(?:eqq?)?gt|e(?:qslantgt|sc)|(?:RightF|LeftF|[lr]f)loo|u(?:[lr]corne|ar)|timesba|(?:plusa|cirs|apa)ci|U(?:arroci|f)|(?:dzigr|s(?:u(?:pl|br)|imr|[lr])|zigr|angz|nvH|l(?:tl|B)|r[Br])ar|UnderBa|(?:plus|harr|top|mid|of)ci|O(?:verBa|sc|f)|dd?agge|s(?:olba|sc)|g(?:t(?:rar|ci)|sc|f)|c(?:opys|u(?:po|ep)|sc|f)|(?:n(?:(?:v[lr]|w|r)A|l[Aa]|h[Aa]|eA)|x[hlr][Aa]|u(?:ua|da|A)|s[ew]A|rla|o[lr]a|rba|rAa|l[Ablr]a|h(?:oa|A)|era|d(?:ua|A)|cra|vA)r|o(?:lci|sc|ro|pa)|ropa|roar|l(?:o(?:pa|ar)|sc|Ar)|i(?:ma|s)c|ltci|dd?ar|a(?:ma|s)c|R(?:Bar|sc|f)|I(?:mac|f)|(?:u(?:ma|s)|oma|ema|Oma|Ema|[wyz]s|qs|ks|fs|Zs|Ys|Xs|Ws|Vs|Us|Ss|Qs|Ns|Ms|Ks|Is|Gs|Fs|Cs|Bs)c|Umac|x(?:sc|f)|v(?:sc|f)|rsc|n(?:ld|f)|m(?:sc|ld|ac|f)|rAr|h(?:sc|f)|b(?:sc|f)|psc|P(?:sc|f)|L(?:sc|ar|f)|jsc|J(?:sc|f)|E(?:sc|f)|[HT]sc|[yz]f|wf|tf|qf|pf|kf|jf|Zf|Yf|Xf|Wf|Vf|Tf|Sf|Qf|Nf|Mf|Kf|Hf|Gf|Ff|Cf|Bf)r|(?:Diacritical(?:Double)?A|[EINOSYZaisz]a)cute|(?:(?:N(?:egative(?:VeryThin|Thi(?:ck|n))|onBreaking)|NegativeMedium|ZeroWidth|VeryThin|Medium|Thi(?:ck|n))Spac|Filled(?:Very)?SmallSquar|Empty(?:Very)?SmallSquar|(?:N(?:ot(?:Succeeds|Greater|Tilde|Less)T|t)|DiacriticalT|VerticalT|PrecedesT|SucceedsT|NotEqualT|GreaterT|TildeT|EqualT|LessT|at|Ut|It)ild|(?:(?:DiacriticalG|[EIOUaiu]g)ra|(?:u|U)?bre|(?:o|e)?gra)v|(?:doublebar|curly|big|x)wedg|H(?:orizontalLin|ilbertSpac)|Double(?:Righ|Lef)tTe|(?:(?:measured|uw)ang|exponentia|dwang|ssmi|fema)l|(?:Poincarepla|reali|pho|oli)n|(?:black)?lozeng|(?:VerticalL|(?:prof|imag)l)in|SmallCircl|(?:black|dot)squar|rmoustach|l(?:moustach|angl)|(?:b(?:ack)?pr|(?:tri|xo)t|[qt]pr)im|[Tt]herefor|(?:DownB|[Gag]b)rev|(?:infint|nv[lr]tr)i|b(?:arwedg|owti)|an(?:dslop|gl)|(?:cu(?:rly)?v|rthr|lthr|b(?:ig|ar)v|xv)e|n(?:s(?:qsu[bp]|ccu)|prcu)|orslop|NewLin|maltes|Becaus|rangl|incar|(?:otil|Otil|t(?:ra|il))d|[inu]tild|s(?:mil|imn)|(?:sc|pr)cu|Wedg|Prim|Brev)e|(?:CloseCurly(?:Double)?Quo|OpenCurly(?:Double)?Quo|[ry]?acu)te|(?:Reverse(?:Up)?|Up)Equilibrium|C(?:apitalDifferentialD|(?:oproduc|(?:ircleD|enterD|d)o)t|on(?:grue|i)nt|conint|upCap|o(?:lone|pf)|OPY|hi)|(?:(?:(?:left)?rightsquig|(?:longleftr|twoheadr|nleftr|nLeftr|longr|hookr|nR|Rr)ight|(?:twohead|hook)left|longleft|updown|Updown|nright|Right|nleft|nLeft|down|up|Up)a|L(?:(?:ong(?:left)?righ|(?:ong)?lef)ta|eft(?:(?:right)?a|RightA|TeeA))|RightTeeA|LongLeftA|UpTeeA)rrow|(?:(?:RightArrow|Short|Upper|Lower)Left|(?:L(?:eftArrow|o(?:wer|ng))|LongLeft|Short|Upper)Right|ShortUp)Arrow|(?:b(?:lacktriangle(?:righ|lef)|ulle|no)|RightDoubleBracke|RightAngleBracke|Left(?:Doub|Ang)leBracke|(?:vartriangle|downharpoon|c(?:ircl|urv)earrow|upharpoon|looparrow)righ|(?:vartriangle|downharpoon|c(?:ircl|urv)earrow|upharpoon|looparrow|mapsto)lef|(?:UnderBrack|OverBrack|emptys|targ|Sups)e|diamondsui|c(?:ircledas|lubsui|are)|(?:spade|heart)sui|(?:(?:c(?:enter|t)|lmi|ino)d|(?:Triple|mD)D|n(?:otin|e)d|(?:ncong|doteq|su[bp]e|e[gl]s)d|l(?:ess|t)d|isind|c(?:ong|up|ap)?d|b(?:igod|N)|t(?:(?:ri)?d|opb)|s(?:ub|im)d|midd|g(?:tr?)?d|Lmid|DotD|(?:xo|ut|z)d|e(?:s?d|rD|fD|DD)|dtd|Zd|Id|Gd|Ed)o|realpar|i(?:magpar|iin)|S(?:uchTha|qr)|su[bp]mul|(?:(?:lt|i)que|gtque|(?:mid|low)a|e(?:que|xi))s|Produc|s(?:updo|e[cx])|r(?:parg|ec)|lparl|vangr|hamil|(?:homt|[lr]fis|ufis|dfis)h|phmma|t(?:wix|in)|quo|o(?:do|as)|fla|eDo)t|(?:(?:Square)?Intersecti|(?:straight|back|var)epsil|SquareUni|expectati|upsil|epsil|Upsil|eq?col|Epsil|(?:omic|Omic|rca|lca|eca|Sca|[NRTt]ca|Lca|Eca|[Zdz]ca|Dca)r|scar|ncar|herc|ccar|Ccar|iog|Iog)on|Not(?:S(?:quareSu(?:per|b)set|u(?:cceeds|(?:per|b)set))|Precedes|Greater|Tilde|Less)?|(?:(?:(?:Not(?:Reverse)?|Reverse)E|comp|E)leme|NotCongrue|(?:n[gl]|l)eqsla|geqsla|q(?:uat)?i|perc|iiii|coni|cwi|awi|oi)nt|(?:(?:rightleftharpo|leftrightharpo|quaterni)on|(?:(?:N(?:ot(?:NestedLess|Greater|Less)|estedLess)L|(?:eqslant|gtr(?:eqq?)?)l|LessL)e|Greater(?:Equal)?Le|cro)s|(?:rightright|leftleft|upup)arrow|rightleftarrow|(?:(?:(?:righ|lef)tthree|divideon|b(?:igo|ox)|[lr]o)t|InvisibleT)ime|downdownarrow|(?:(?:smallset|tri|dot|box)m|PlusM)inu|(?:RoundImpli|complex|Impli|Otim)e|C(?:ircle(?:Time|Minu|Plu)|ayley|ros)|(?:rationa|mode)l|NotExist|(?:(?:UnionP|MinusP|(?:b(?:ig[ou]|ox)|tri|s(?:u[bp]|im)|dot|xu|mn)p)l|(?:xo|u)pl|o(?:min|pl)|ropl|lopl|epl)u|otimesa|integer|e(?:linter|qual)|setminu|rarrbf|larrb?f|olcros|rarrf|mstpo|lesge|gesle|Exist|[lr]time|strn|napo|fltn|ccap|apo)s|(?:b(?:(?:lack|ig)triangledow|etwee)|(?:righ|lef)tharpoondow|(?:triangle|mapsto)dow|(?:nv|i)infi|ssetm|plusm|lagra|d(?:[lr]cor|isi)|c(?:ompf|aro)|s?frow|(?:hyph|curr)e|kgree|thor|ogo|ye)n|Not(?:Righ|Lef)tTriangle|(?:Up(?:Arrow)?|Short)DownArrow|(?:(?:n(?:triangle(?:righ|lef)t|succ|prec)|(?:trianglerigh|trianglelef|sqsu[bp]se|ques)t|backsim)e|lvertneq|gvertneq|(?:suc|pre)cneq|a(?:pprox|symp)e|(?:succ|prec|vee)e|circe)q|(?:UnderParenthes|OverParenthes|xn)is|(?:(?:Righ|Lef)tDown|Right(?:Up)?|Left(?:Up)?)Vector|D(?:o(?:wn(?:RightVector|LeftVector|Arrow|Tee)|t)|el|D)|l(?:eftrightarrows|br(?:k(?:sl[du]|e)|ac[ek])|tri[ef]|s(?:im[eg]|qb|h)|hard|a(?:tes|ngd|p)|o[pz]f|rm|gE|fr|eg|cy)|(?:NotHumpDownHum|(?:righ|lef)tharpoonu|big(?:(?:triangle|sqc)u|c[au])|HumpDownHum|m(?:apstou|lc)|(?:capbr|xsq)cu|smash|rarr[al]|(?:weie|sha)r|larrl|velli|(?:thin|punc)s|h(?:elli|airs)|(?:u[lr]c|vp)ro|d[lr]cro|c(?:upc[au]|apc[au])|thka|scna|prn?a|oper|n(?:ums|va|cu|bs)|ens|xc[au]|Ma)p|l(?:eftrightarrow|e(?:ftarrow|s(?:dot)?)?|moust|a(?:rrb?|te?|ng)|t(?:ri)?|sim|par|oz|l|g)|n(?:triangle(?:righ|lef)t|succ|prec)|SquareSu(?:per|b)set|(?:I(?:nvisibleComm|ot)|(?:varthe|iio)t|varkapp|(?:vars|S)igm|(?:diga|mco)mm|Cedill|lambd|Lambd|delt|Thet|omeg|Omeg|Kapp|Delt|nabl|zet|to[es]|rdc|ldc|iot|Zet|Bet|Et)a|b(?:lacktriangle|arwed|u(?:mpe?|ll)|sol|o(?:x[HVhv]|t)|brk|ne)|(?:trianglerigh|trianglelef|sqsu[bp]se|ques)t|RightT(?:riangl|e)e|(?:(?:varsu[bp]setn|su(?:psetn?|bsetn?))eq|nsu[bp]seteq|colone|(?:wedg|sim)e|nsime|lneq|gneq)q|DifferentialD|(?:(?:fall|ris)ingdots|(?:suc|pre)ccurly|ddots)eq|A(?:pplyFunction|ssign|(?:tild|grav|brev)e|acute|o(?:gon|pf)|lpha|(?:mac|sc|f)r|c(?:irc|y)|ring|Elig|uml|nd|MP)|(?:varsu[bp]setn|su(?:psetn?|bsetn?))eq|L(?:eft(?:T(?:riangl|e)e|Arrow)|l)|G(?:reaterEqual|amma)|E(?:xponentialE|quilibrium|sim|cy|TH|NG)|(?:(?:RightCeil|LeftCeil|varnoth|ar|Ur)in|(?:b(?:ack)?co|uri)n|vzigza|roan|loan|ffli|amal|sun|rin|n(?:tl|an)|Ran|Lan)g|(?:thick|succn?|precn?|less|g(?:tr|n)|ln|n)approx|(?:s(?:traightph|em)|(?:rtril|xu|u[lr]|xd|v[lr])tr|varph|l[lr]tr|b(?:sem|eps)|Ph)i|(?:circledd|osl|n(?:v[Dd]|V[Dd]|d)|hsl|V(?:vd|D)|Osl|v[Dd]|md)ash|(?:(?:RuleDelay|imp|cuw)e|(?:n(?:s(?:hort)?)?|short|rn)mi|D(?:Dotrah|iamon)|(?:i(?:nt)?pr|peri)o|odsol|llhar|c(?:opro|irmi)|(?:capa|anda|pou)n|Barwe|napi|api)d|(?:cu(?:rlyeq(?:suc|pre)|es)|telre|[ou]dbla|Udbla|Odbla|radi|lesc|gesc|dbla)c|(?:circled|big|eq|[is]|c|x|a|S|[hw]|W|H|G|E|C)circ|rightarrow|R(?:ightArrow|arr|e)|Pr(?:oportion)?|(?:longmapst|varpropt|p(?:lustw|ropt)|varrh|numer|(?:rsa|lsa|sb)qu|m(?:icr|h)|[lr]aqu|bdqu|eur)o|UnderBrace|ImaginaryI|B(?:ernoullis|a(?:ckslash|rv)|umpeq|cy)|(?:(?:Laplace|Mellin|zee)tr|Fo(?:uriertr|p)|(?:profsu|ssta)r|ordero|origo|[ps]op|nop|mop|i(?:op|mo)|h(?:op|al)|f(?:op|no)|dop|bop|Rop|Pop|Nop|Lop|Iop|Hop|Dop|[GJKMOQSTV-Zgjkoqvwyz]op|Bop)f|nsu[bp]seteq|t(?:ri(?:angleq|e)|imesd|he(?:tav|re4)|au)|O(?:verBrace|r)|(?:(?:pitchfo|checkma|t(?:opfo|b)|rob|rbb|l[bo]b)r|intlarh|b(?:brktbr|l(?:oc|an))|perten|NoBrea|rarrh|s[ew]arh|n[ew]arh|l(?:arrh|hbl)|uhbl|Hace)k|(?:NotCupC|(?:mu(?:lti)?|x)m|cupbrc)ap|t(?:riangle|imes|heta|opf?)|Precedes|Succeeds|Superset|NotEqual|(?:n(?:atural|exist|les)|s(?:qc[au]p|mte)|prime)s|c(?:ir(?:cled[RS]|[Ee])|u(?:rarrm|larrp|darr[lr]|ps)|o(?:mmat|pf)|aps|hi)|b(?:sol(?:hsu)?b|ump(?:eq|E)|ox(?:box|[Vv][HLRhlr]|[Hh][DUdu]|[DUdu][LRlr])|e(?:rnou|t[ah])|lk(?:34|1[24])|cy)|(?:l(?:esdot|squ|dqu)o|rsquo|rdquo|ngt)r|a(?:n(?:g(?:msda[a-h]|st|e)|d[dv])|st|p[Ee]|mp|fr|c[Edy])|(?:g(?:esdoto|E)|[lr]haru)l|(?:angrtvb|lrhar|nis)d|(?:(?:th(?:ic)?k|succn?|p(?:r(?:ecn?|n)?|lus)|rarr|l(?:ess|arr)|su[bp]|par|scn|g(?:tr|n)|ne|sc|n[glv]|ln|eq?)si|thetasy|ccupss|alefsy|botto)m|trpezium|(?:hks[ew]|dr?bk|bk)arow|(?:(?:[lr]a|d|c)empty|b(?:nequi|empty)|plank|nequi|odi)v|(?:(?:sc|rp|n)pol|point|fpart)int|(?:c(?:irf|wco)|awco)nint|PartialD|n(?:s(?:u[bp](?:set)?|c)|rarr|ot(?:ni|in)?|warr|e(?:arr)?|a(?:tur|p)|vlt|p(?:re?|ar)|um?|l[et]|ge|i)|n(?:atural|exist|les)|d(?:i(?:am(?:ond)?|v(?:ide)?)|tri|ash|ot|d)|backsim|l(?:esdot|squ|dqu)o|g(?:esdoto|E)|U(?:p(?:Arrow|si)|nion|arr)|angrtvb|p(?:l(?:anckh|us(?:d[ou]|[be]))|ar(?:sl|t)|r(?:od|nE|E)|erp|iv|m)|n(?:ot(?:niv[a-c]|in(?:v[a-c]|E))|rarr[cw]|s(?:u[bp][Ee]|c[er])|part|v(?:le|g[et])|g(?:es|E)|c(?:ap|y)|apE|lE|iv|Ll|Gg)|m(?:inus(?:du|b)|ale|cy|p)|rbr(?:k(?:sl[du]|e)|ac[ek])|(?:suphsu|tris|rcu|lcu)b|supdsub|(?:s[ew]a|n[ew]a)rrow|(?:b(?:ecaus|sim)|n(?:[lr]tri|bump)|csu[bp])e|equivDD|u(?:rcorn|lcorn|psi)|timesb|s(?:u(?:p(?:set)?|b(?:set)?)|q(?:su[bp]|u)|i(?:gma|m)|olb?|dot|mt|fr|ce?)|p(?:l(?:anck|us)|r(?:op|ec?)?|ara?|i)|o(?:times|r(?:d(?:er)?)?)|m(?:i(?:nusd?|d)|a(?:p(?:sto)?|lt)|u)|rmoust|g(?:e(?:s(?:dot|l)?|q)?|sim|n(?:ap|e)|t|l|g)|(?:spade|heart)s|c(?:u(?:rarr|larr|p)|o(?:m(?:ma|p)|lon|py|ng)|lubs|heck|cups|irc?|ent|ap)|colone|a(?:p(?:prox)?|n(?:g(?:msd|rt)?|d)|symp|f|c)|S(?:quare|u[bp]|c)|Subset|b(?:ecaus|sim)|vsu[bp]n[Ee]|s(?:u(?:psu[bp]|b(?:su[bp]|n[Ee]|E)|pn[Ee]|p[1-3E]|m)|q(?:u(?:ar[ef]|f)|su[bp]e)|igma[fv]|etmn|dot[be]|par|mid|hc?y|c[Ey])|f(?:rac(?:78|5[68]|45|3[458]|2[35]|1[2-68])|fr)|e(?:m(?:sp1[34]|ptyv)|psiv|c(?:irc|y)|t[ah]|ng|ll|fr|e)|(?:kappa|isins|vBar|fork|rho|phi|n[GL]t)v|divonx|V(?:dashl|ee)|gammad|G(?:ammad|cy|[Tgt])|[Ldhlt]strok|[HT]strok|(?:c(?:ylct|hc)|(?:s(?:oft|hch)|hard|S(?:OFT|HCH)|jser|J(?:ser|uk)|HARD|tsh|TSH|juk|iuk|I(?:uk|[EO])|zh|yi|nj|lj|k[hj]|gj|dj|ZH|Y[AIU]|NJ|LJ|K[HJ]|GJ|D[JSZ])c|ubrc|Ubrc|(?:yu|i[eo]|dz|v|p|f)c|TSc|SHc|CHc|Vc|Pc|Mc|Fc)y|(?:(?:wre|jm)at|dalet|a(?:ngs|le)p|imat|[lr]ds)h|[CLRUceglnou]acute|ff?llig|(?:f(?:fi|[ij])|sz|oe|ij|ae|OE|IJ)lig|r(?:a(?:tio|rr|ng)|tri|par|eal)|s[ew]arr|s(?:qc[au]p|mte)|prime|rarrb|i(?:n(?:fin|t)?|sin|t|i|c)|e(?:quiv|m(?:pty|sp)|p(?:si|ar)|cir|l|g)|kappa|isins|ncong|doteq|(?:wedg|sim)e|nsime|rsquo|rdquo|[lr]haru|V(?:dash|ert)|Tilde|lrhar|gamma|Equal|UpTee|n(?:[lr]tri|bump)|C(?:olon|up|ap)|v(?:arpi|ert)|u(?:psih|ml)|vnsu[bp]|r(?:tri[ef]|e(?:als|g)|a(?:rr[cw]|ng[de]|ce)|sh|lm|x)|rhard|sim[gl]E|i(?:sin[Ev]|mage|f[fr]|cy)|harrw|(?:n[gl]|l)eqq|g(?:sim[el]|tcc|e(?:qq|l)|nE|l[Eaj]|gg|ap)|ocirc|starf|utrif|d(?:trif|i(?:ams|e)|ashv|sc[ry]|fr|eg)|[du]har[lr]|T(?:HORN|a[bu])|(?:TRAD|[gl]vn)E|odash|[EUaeu]o(?:gon|pf)|alpha|[IJOUYgjuy]c(?:irc|y)|v(?:arr|ee)|succ|sim[gl]|harr|ln(?:ap|e)|lesg|(?:n[gl]|l)eq|ocir|star|utri|vBar|fork|su[bp]e|nsim|lneq|gneq|csu[bp]|zwn?j|yacy|x(?:opf|i)|scnE|o(?:r(?:d[fm]|v)|mid|lt|hm|gt|fr|cy|S)|scap|rsqb|ropf|ltcc|tsc[ry]|QUOT|[EOUYao]uml|rho|phi|n[GL]t|e[gl]s|ngt|I(?:nt|m)|nis|rfr|rcy|lnE|lEg|ufr|S(?:um|cy)|R(?:sh|ho)|psi|Ps?i|[NRTt]cy|L(?:sh|cy|[Tt])|kcy|Kcy|Hat|REG|[Zdz]cy|wr|lE|wp|Xi|Nu|Mu)(;)</string>
2634 <key>name</key>
2635 <string>constant.language.character-reference.named.html</string>
2636 <key>captures</key>
2637 <dict>
2638 <key>1</key>
2639 <dict>
2640 <key>name</key>
2641 <string>punctuation.definition.character-reference.begin.html</string>
2642 </dict>
2643 <key>2</key>
2644 <dict>
2645 <key>name</key>
2646 <string>keyword.control.character-reference.html</string>
2647 </dict>
2648 <key>3</key>
2649 <dict>
2650 <key>name</key>
2651 <string>punctuation.definition.character-reference.end.html</string>
2652 </dict>
2653 </dict>
2654 </dict>
2655 <key>commonmark-code-fenced-apib</key>
2656 <dict>
2657 <key>patterns</key>
2658 <array>
2659 <dict>
2660 <key>begin</key>
2661 <string>(?:^|\G)[\t ]*(`{3,})(?:[\t ]*((?i:api\x2dblueprint|(?:.*\.)?apib))(?:[\t ]+((?:[^\n\r`])+))?)(?:[\t ]*$)</string>
2662 <key>beginCaptures</key>
2663 <dict>
2664 <key>1</key>
2665 <dict>
2666 <key>name</key>
2667 <string>string.other.begin.code.fenced.mdx</string>
2668 </dict>
2669 <key>2</key>
2670 <dict>
2671 <key>name</key>
2672 <string>entity.name.function.mdx</string>
2673 <key>patterns</key>
2674 <array>
2675 <dict>
2676 <key>include</key>
2677 <string>#markdown-string</string>
2678 </dict>
2679 </array>
2680 </dict>
2681 <key>3</key>
2682 <dict>
2683 <key>patterns</key>
2684 <array>
2685 <dict>
2686 <key>include</key>
2687 <string>#markdown-string</string>
2688 </dict>
2689 </array>
2690 </dict>
2691 </dict>
2692 <key>end</key>
2693 <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string>
2694 <key>endCaptures</key>
2695 <dict>
2696 <key>1</key>
2697 <dict>
2698 <key>name</key>
2699 <string>string.other.end.code.fenced.mdx</string>
2700 </dict>
2701 </dict>
2702 <key>name</key>
2703 <string>markup.code.apib.mdx</string>
2704 <key>patterns</key>
2705 <array>
2706 <dict>
2707 <key>begin</key>
2708 <string>(^|\G)(\s*)(.*)</string>
2709 <key>while</key>
2710 <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string>
2711 <key>contentName</key>
2712 <string>meta.embedded.apib</string>
2713 <key>patterns</key>
2714 <array>
2715 <dict>
2716 <key>include</key>
2717 <string>text.html.markdown.source.gfm.apib</string>
2718 </dict>
2719 </array>
2720 </dict>
2721 </array>
2722 </dict>
2723 <dict>
2724 <key>begin</key>
2725 <string>(?:^|\G)[\t ]*(~{3,})(?:[\t ]*((?i:api\x2dblueprint|(?:.*\.)?apib))(?:[\t ]+((?:[^\n\r])+))?)(?:[\t ]*$)</string>
2726 <key>beginCaptures</key>
2727 <dict>
2728 <key>1</key>
2729 <dict>
2730 <key>name</key>
2731 <string>string.other.begin.code.fenced.mdx</string>
2732 </dict>
2733 <key>2</key>
2734 <dict>
2735 <key>name</key>
2736 <string>entity.name.function.mdx</string>
2737 <key>patterns</key>
2738 <array>
2739 <dict>
2740 <key>include</key>
2741 <string>#markdown-string</string>
2742 </dict>
2743 </array>
2744 </dict>
2745 <key>3</key>
2746 <dict>
2747 <key>patterns</key>
2748 <array>
2749 <dict>
2750 <key>include</key>
2751 <string>#markdown-string</string>
2752 </dict>
2753 </array>
2754 </dict>
2755 </dict>
2756 <key>end</key>
2757 <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string>
2758 <key>endCaptures</key>
2759 <dict>
2760 <key>1</key>
2761 <dict>
2762 <key>name</key>
2763 <string>string.other.end.code.fenced.mdx</string>
2764 </dict>
2765 </dict>
2766 <key>name</key>
2767 <string>markup.code.apib.mdx</string>
2768 <key>patterns</key>
2769 <array>
2770 <dict>
2771 <key>begin</key>
2772 <string>(^|\G)(\s*)(.*)</string>
2773 <key>while</key>
2774 <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string>
2775 <key>contentName</key>
2776 <string>meta.embedded.apib</string>
2777 <key>patterns</key>
2778 <array>
2779 <dict>
2780 <key>include</key>
2781 <string>text.html.markdown.source.gfm.apib</string>
2782 </dict>
2783 </array>
2784 </dict>
2785 </array>
2786 </dict>
2787 </array>
2788 </dict>
2789 <key>commonmark-code-fenced-asciidoc</key>
2790 <dict>
2791 <key>patterns</key>
2792 <array>
2793 <dict>
2794 <key>begin</key>
2795 <string>(?:^|\G)[\t ]*(`{3,})(?:[\t ]*((?i:(?:.*\.)?(?:adoc|asciidoc)))(?:[\t ]+((?:[^\n\r`])+))?)(?:[\t ]*$)</string>
2796 <key>beginCaptures</key>
2797 <dict>
2798 <key>1</key>
2799 <dict>
2800 <key>name</key>
2801 <string>string.other.begin.code.fenced.mdx</string>
2802 </dict>
2803 <key>2</key>
2804 <dict>
2805 <key>name</key>
2806 <string>entity.name.function.mdx</string>
2807 <key>patterns</key>
2808 <array>
2809 <dict>
2810 <key>include</key>
2811 <string>#markdown-string</string>
2812 </dict>
2813 </array>
2814 </dict>
2815 <key>3</key>
2816 <dict>
2817 <key>patterns</key>
2818 <array>
2819 <dict>
2820 <key>include</key>
2821 <string>#markdown-string</string>
2822 </dict>
2823 </array>
2824 </dict>
2825 </dict>
2826 <key>end</key>
2827 <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string>
2828 <key>endCaptures</key>
2829 <dict>
2830 <key>1</key>
2831 <dict>
2832 <key>name</key>
2833 <string>string.other.end.code.fenced.mdx</string>
2834 </dict>
2835 </dict>
2836 <key>name</key>
2837 <string>markup.code.asciidoc.mdx</string>
2838 <key>patterns</key>
2839 <array>
2840 <dict>
2841 <key>begin</key>
2842 <string>(^|\G)(\s*)(.*)</string>
2843 <key>while</key>
2844 <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string>
2845 <key>contentName</key>
2846 <string>meta.embedded.asciidoc</string>
2847 <key>patterns</key>
2848 <array>
2849 <dict>
2850 <key>include</key>
2851 <string>text.html.asciidoc</string>
2852 </dict>
2853 </array>
2854 </dict>
2855 </array>
2856 </dict>
2857 <dict>
2858 <key>begin</key>
2859 <string>(?:^|\G)[\t ]*(~{3,})(?:[\t ]*((?i:(?:.*\.)?(?:adoc|asciidoc)))(?:[\t ]+((?:[^\n\r])+))?)(?:[\t ]*$)</string>
2860 <key>beginCaptures</key>
2861 <dict>
2862 <key>1</key>
2863 <dict>
2864 <key>name</key>
2865 <string>string.other.begin.code.fenced.mdx</string>
2866 </dict>
2867 <key>2</key>
2868 <dict>
2869 <key>name</key>
2870 <string>entity.name.function.mdx</string>
2871 <key>patterns</key>
2872 <array>
2873 <dict>
2874 <key>include</key>
2875 <string>#markdown-string</string>
2876 </dict>
2877 </array>
2878 </dict>
2879 <key>3</key>
2880 <dict>
2881 <key>patterns</key>
2882 <array>
2883 <dict>
2884 <key>include</key>
2885 <string>#markdown-string</string>
2886 </dict>
2887 </array>
2888 </dict>
2889 </dict>
2890 <key>end</key>
2891 <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string>
2892 <key>endCaptures</key>
2893 <dict>
2894 <key>1</key>
2895 <dict>
2896 <key>name</key>
2897 <string>string.other.end.code.fenced.mdx</string>
2898 </dict>
2899 </dict>
2900 <key>name</key>
2901 <string>markup.code.asciidoc.mdx</string>
2902 <key>patterns</key>
2903 <array>
2904 <dict>
2905 <key>begin</key>
2906 <string>(^|\G)(\s*)(.*)</string>
2907 <key>while</key>
2908 <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string>
2909 <key>contentName</key>
2910 <string>meta.embedded.asciidoc</string>
2911 <key>patterns</key>
2912 <array>
2913 <dict>
2914 <key>include</key>
2915 <string>text.html.asciidoc</string>
2916 </dict>
2917 </array>
2918 </dict>
2919 </array>
2920 </dict>
2921 </array>
2922 </dict>
2923 <key>commonmark-code-fenced-c</key>
2924 <dict>
2925 <key>patterns</key>
2926 <array>
2927 <dict>
2928 <key>begin</key>
2929 <string>(?:^|\G)[\t ]*(`{3,})(?:[\t ]*((?i:dtrace|dtrace\x2dscript|oncrpc|rpc|rpcgen|unified\x2dparallel\x2dc|x\x2dbitmap|x\x2dpixmap|xdr|(?:.*\.)?(?:c|cats|h|idc|opencl|upc|xbm|xpm|xs)))(?:[\t ]+((?:[^\n\r`])+))?)(?:[\t ]*$)</string>
2930 <key>beginCaptures</key>
2931 <dict>
2932 <key>1</key>
2933 <dict>
2934 <key>name</key>
2935 <string>string.other.begin.code.fenced.mdx</string>
2936 </dict>
2937 <key>2</key>
2938 <dict>
2939 <key>name</key>
2940 <string>entity.name.function.mdx</string>
2941 <key>patterns</key>
2942 <array>
2943 <dict>
2944 <key>include</key>
2945 <string>#markdown-string</string>
2946 </dict>
2947 </array>
2948 </dict>
2949 <key>3</key>
2950 <dict>
2951 <key>patterns</key>
2952 <array>
2953 <dict>
2954 <key>include</key>
2955 <string>#markdown-string</string>
2956 </dict>
2957 </array>
2958 </dict>
2959 </dict>
2960 <key>end</key>
2961 <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string>
2962 <key>endCaptures</key>
2963 <dict>
2964 <key>1</key>
2965 <dict>
2966 <key>name</key>
2967 <string>string.other.end.code.fenced.mdx</string>
2968 </dict>
2969 </dict>
2970 <key>name</key>
2971 <string>markup.code.c.mdx</string>
2972 <key>patterns</key>
2973 <array>
2974 <dict>
2975 <key>begin</key>
2976 <string>(^|\G)(\s*)(.*)</string>
2977 <key>while</key>
2978 <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string>
2979 <key>contentName</key>
2980 <string>meta.embedded.c</string>
2981 <key>patterns</key>
2982 <array>
2983 <dict>
2984 <key>include</key>
2985 <string>source.c</string>
2986 </dict>
2987 </array>
2988 </dict>
2989 </array>
2990 </dict>
2991 <dict>
2992 <key>begin</key>
2993 <string>(?:^|\G)[\t ]*(~{3,})(?:[\t ]*((?i:dtrace|dtrace\x2dscript|oncrpc|rpc|rpcgen|unified\x2dparallel\x2dc|x\x2dbitmap|x\x2dpixmap|xdr|(?:.*\.)?(?:c|cats|h|idc|opencl|upc|xbm|xpm|xs)))(?:[\t ]+((?:[^\n\r])+))?)(?:[\t ]*$)</string>
2994 <key>beginCaptures</key>
2995 <dict>
2996 <key>1</key>
2997 <dict>
2998 <key>name</key>
2999 <string>string.other.begin.code.fenced.mdx</string>
3000 </dict>
3001 <key>2</key>
3002 <dict>
3003 <key>name</key>
3004 <string>entity.name.function.mdx</string>
3005 <key>patterns</key>
3006 <array>
3007 <dict>
3008 <key>include</key>
3009 <string>#markdown-string</string>
3010 </dict>
3011 </array>
3012 </dict>
3013 <key>3</key>
3014 <dict>
3015 <key>patterns</key>
3016 <array>
3017 <dict>
3018 <key>include</key>
3019 <string>#markdown-string</string>
3020 </dict>
3021 </array>
3022 </dict>
3023 </dict>
3024 <key>end</key>
3025 <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string>
3026 <key>endCaptures</key>
3027 <dict>
3028 <key>1</key>
3029 <dict>
3030 <key>name</key>
3031 <string>string.other.end.code.fenced.mdx</string>
3032 </dict>
3033 </dict>
3034 <key>name</key>
3035 <string>markup.code.c.mdx</string>
3036 <key>patterns</key>
3037 <array>
3038 <dict>
3039 <key>begin</key>
3040 <string>(^|\G)(\s*)(.*)</string>
3041 <key>while</key>
3042 <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string>
3043 <key>contentName</key>
3044 <string>meta.embedded.c</string>
3045 <key>patterns</key>
3046 <array>
3047 <dict>
3048 <key>include</key>
3049 <string>source.c</string>
3050 </dict>
3051 </array>
3052 </dict>
3053 </array>
3054 </dict>
3055 </array>
3056 </dict>
3057 <key>commonmark-code-fenced-clojure</key>
3058 <dict>
3059 <key>patterns</key>
3060 <array>
3061 <dict>
3062 <key>begin</key>
3063 <string>(?:^|\G)[\t ]*(`{3,})(?:[\t ]*((?i:clojure|rouge|(?:.*\.)?(?:boot|cl2|clj|cljc|cljs|cljs\.hl|cljscm|cljx|edn|hic|rg|wisp)))(?:[\t ]+((?:[^\n\r`])+))?)(?:[\t ]*$)</string>
3064 <key>beginCaptures</key>
3065 <dict>
3066 <key>1</key>
3067 <dict>
3068 <key>name</key>
3069 <string>string.other.begin.code.fenced.mdx</string>
3070 </dict>
3071 <key>2</key>
3072 <dict>
3073 <key>name</key>
3074 <string>entity.name.function.mdx</string>
3075 <key>patterns</key>
3076 <array>
3077 <dict>
3078 <key>include</key>
3079 <string>#markdown-string</string>
3080 </dict>
3081 </array>
3082 </dict>
3083 <key>3</key>
3084 <dict>
3085 <key>patterns</key>
3086 <array>
3087 <dict>
3088 <key>include</key>
3089 <string>#markdown-string</string>
3090 </dict>
3091 </array>
3092 </dict>
3093 </dict>
3094 <key>end</key>
3095 <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string>
3096 <key>endCaptures</key>
3097 <dict>
3098 <key>1</key>
3099 <dict>
3100 <key>name</key>
3101 <string>string.other.end.code.fenced.mdx</string>
3102 </dict>
3103 </dict>
3104 <key>name</key>
3105 <string>markup.code.clojure.mdx</string>
3106 <key>patterns</key>
3107 <array>
3108 <dict>
3109 <key>begin</key>
3110 <string>(^|\G)(\s*)(.*)</string>
3111 <key>while</key>
3112 <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string>
3113 <key>contentName</key>
3114 <string>meta.embedded.clojure</string>
3115 <key>patterns</key>
3116 <array>
3117 <dict>
3118 <key>include</key>
3119 <string>source.clojure</string>
3120 </dict>
3121 </array>
3122 </dict>
3123 </array>
3124 </dict>
3125 <dict>
3126 <key>begin</key>
3127 <string>(?:^|\G)[\t ]*(~{3,})(?:[\t ]*((?i:clojure|rouge|(?:.*\.)?(?:boot|cl2|clj|cljc|cljs|cljs\.hl|cljscm|cljx|edn|hic|rg|wisp)))(?:[\t ]+((?:[^\n\r])+))?)(?:[\t ]*$)</string>
3128 <key>beginCaptures</key>
3129 <dict>
3130 <key>1</key>
3131 <dict>
3132 <key>name</key>
3133 <string>string.other.begin.code.fenced.mdx</string>
3134 </dict>
3135 <key>2</key>
3136 <dict>
3137 <key>name</key>
3138 <string>entity.name.function.mdx</string>
3139 <key>patterns</key>
3140 <array>
3141 <dict>
3142 <key>include</key>
3143 <string>#markdown-string</string>
3144 </dict>
3145 </array>
3146 </dict>
3147 <key>3</key>
3148 <dict>
3149 <key>patterns</key>
3150 <array>
3151 <dict>
3152 <key>include</key>
3153 <string>#markdown-string</string>
3154 </dict>
3155 </array>
3156 </dict>
3157 </dict>
3158 <key>end</key>
3159 <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string>
3160 <key>endCaptures</key>
3161 <dict>
3162 <key>1</key>
3163 <dict>
3164 <key>name</key>
3165 <string>string.other.end.code.fenced.mdx</string>
3166 </dict>
3167 </dict>
3168 <key>name</key>
3169 <string>markup.code.clojure.mdx</string>
3170 <key>patterns</key>
3171 <array>
3172 <dict>
3173 <key>begin</key>
3174 <string>(^|\G)(\s*)(.*)</string>
3175 <key>while</key>
3176 <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string>
3177 <key>contentName</key>
3178 <string>meta.embedded.clojure</string>
3179 <key>patterns</key>
3180 <array>
3181 <dict>
3182 <key>include</key>
3183 <string>source.clojure</string>
3184 </dict>
3185 </array>
3186 </dict>
3187 </array>
3188 </dict>
3189 </array>
3190 </dict>
3191 <key>commonmark-code-fenced-coffee</key>
3192 <dict>
3193 <key>patterns</key>
3194 <array>
3195 <dict>
3196 <key>begin</key>
3197 <string>(?:^|\G)[\t ]*(`{3,})(?:[\t ]*((?i:coffee\x2dscript|coffeescript|(?:.*\.)?(?:_coffee|cjsx|coffee|cson|em|emberscript|iced)))(?:[\t ]+((?:[^\n\r`])+))?)(?:[\t ]*$)</string>
3198 <key>beginCaptures</key>
3199 <dict>
3200 <key>1</key>
3201 <dict>
3202 <key>name</key>
3203 <string>string.other.begin.code.fenced.mdx</string>
3204 </dict>
3205 <key>2</key>
3206 <dict>
3207 <key>name</key>
3208 <string>entity.name.function.mdx</string>
3209 <key>patterns</key>
3210 <array>
3211 <dict>
3212 <key>include</key>
3213 <string>#markdown-string</string>
3214 </dict>
3215 </array>
3216 </dict>
3217 <key>3</key>
3218 <dict>
3219 <key>patterns</key>
3220 <array>
3221 <dict>
3222 <key>include</key>
3223 <string>#markdown-string</string>
3224 </dict>
3225 </array>
3226 </dict>
3227 </dict>
3228 <key>end</key>
3229 <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string>
3230 <key>endCaptures</key>
3231 <dict>
3232 <key>1</key>
3233 <dict>
3234 <key>name</key>
3235 <string>string.other.end.code.fenced.mdx</string>
3236 </dict>
3237 </dict>
3238 <key>name</key>
3239 <string>markup.code.coffee.mdx</string>
3240 <key>patterns</key>
3241 <array>
3242 <dict>
3243 <key>begin</key>
3244 <string>(^|\G)(\s*)(.*)</string>
3245 <key>while</key>
3246 <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string>
3247 <key>contentName</key>
3248 <string>meta.embedded.coffee</string>
3249 <key>patterns</key>
3250 <array>
3251 <dict>
3252 <key>include</key>
3253 <string>source.coffee</string>
3254 </dict>
3255 </array>
3256 </dict>
3257 </array>
3258 </dict>
3259 <dict>
3260 <key>begin</key>
3261 <string>(?:^|\G)[\t ]*(~{3,})(?:[\t ]*((?i:coffee\x2dscript|coffeescript|(?:.*\.)?(?:_coffee|cjsx|coffee|cson|em|emberscript|iced)))(?:[\t ]+((?:[^\n\r])+))?)(?:[\t ]*$)</string>
3262 <key>beginCaptures</key>
3263 <dict>
3264 <key>1</key>
3265 <dict>
3266 <key>name</key>
3267 <string>string.other.begin.code.fenced.mdx</string>
3268 </dict>
3269 <key>2</key>
3270 <dict>
3271 <key>name</key>
3272 <string>entity.name.function.mdx</string>
3273 <key>patterns</key>
3274 <array>
3275 <dict>
3276 <key>include</key>
3277 <string>#markdown-string</string>
3278 </dict>
3279 </array>
3280 </dict>
3281 <key>3</key>
3282 <dict>
3283 <key>patterns</key>
3284 <array>
3285 <dict>
3286 <key>include</key>
3287 <string>#markdown-string</string>
3288 </dict>
3289 </array>
3290 </dict>
3291 </dict>
3292 <key>end</key>
3293 <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string>
3294 <key>endCaptures</key>
3295 <dict>
3296 <key>1</key>
3297 <dict>
3298 <key>name</key>
3299 <string>string.other.end.code.fenced.mdx</string>
3300 </dict>
3301 </dict>
3302 <key>name</key>
3303 <string>markup.code.coffee.mdx</string>
3304 <key>patterns</key>
3305 <array>
3306 <dict>
3307 <key>begin</key>
3308 <string>(^|\G)(\s*)(.*)</string>
3309 <key>while</key>
3310 <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string>
3311 <key>contentName</key>
3312 <string>meta.embedded.coffee</string>
3313 <key>patterns</key>
3314 <array>
3315 <dict>
3316 <key>include</key>
3317 <string>source.coffee</string>
3318 </dict>
3319 </array>
3320 </dict>
3321 </array>
3322 </dict>
3323 </array>
3324 </dict>
3325 <key>commonmark-code-fenced-console</key>
3326 <dict>
3327 <key>patterns</key>
3328 <array>
3329 <dict>
3330 <key>begin</key>
3331 <string>(?:^|\G)[\t ]*(`{3,})(?:[\t ]*((?i:pycon|python\x2dconsole))(?:[\t ]+((?:[^\n\r`])+))?)(?:[\t ]*$)</string>
3332 <key>beginCaptures</key>
3333 <dict>
3334 <key>1</key>
3335 <dict>
3336 <key>name</key>
3337 <string>string.other.begin.code.fenced.mdx</string>
3338 </dict>
3339 <key>2</key>
3340 <dict>
3341 <key>name</key>
3342 <string>entity.name.function.mdx</string>
3343 <key>patterns</key>
3344 <array>
3345 <dict>
3346 <key>include</key>
3347 <string>#markdown-string</string>
3348 </dict>
3349 </array>
3350 </dict>
3351 <key>3</key>
3352 <dict>
3353 <key>patterns</key>
3354 <array>
3355 <dict>
3356 <key>include</key>
3357 <string>#markdown-string</string>
3358 </dict>
3359 </array>
3360 </dict>
3361 </dict>
3362 <key>end</key>
3363 <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string>
3364 <key>endCaptures</key>
3365 <dict>
3366 <key>1</key>
3367 <dict>
3368 <key>name</key>
3369 <string>string.other.end.code.fenced.mdx</string>
3370 </dict>
3371 </dict>
3372 <key>name</key>
3373 <string>markup.code.console.mdx</string>
3374 <key>patterns</key>
3375 <array>
3376 <dict>
3377 <key>begin</key>
3378 <string>(^|\G)(\s*)(.*)</string>
3379 <key>while</key>
3380 <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string>
3381 <key>contentName</key>
3382 <string>meta.embedded.console</string>
3383 <key>patterns</key>
3384 <array>
3385 <dict>
3386 <key>include</key>
3387 <string>text.python.console</string>
3388 </dict>
3389 </array>
3390 </dict>
3391 </array>
3392 </dict>
3393 <dict>
3394 <key>begin</key>
3395 <string>(?:^|\G)[\t ]*(~{3,})(?:[\t ]*((?i:pycon|python\x2dconsole))(?:[\t ]+((?:[^\n\r])+))?)(?:[\t ]*$)</string>
3396 <key>beginCaptures</key>
3397 <dict>
3398 <key>1</key>
3399 <dict>
3400 <key>name</key>
3401 <string>string.other.begin.code.fenced.mdx</string>
3402 </dict>
3403 <key>2</key>
3404 <dict>
3405 <key>name</key>
3406 <string>entity.name.function.mdx</string>
3407 <key>patterns</key>
3408 <array>
3409 <dict>
3410 <key>include</key>
3411 <string>#markdown-string</string>
3412 </dict>
3413 </array>
3414 </dict>
3415 <key>3</key>
3416 <dict>
3417 <key>patterns</key>
3418 <array>
3419 <dict>
3420 <key>include</key>
3421 <string>#markdown-string</string>
3422 </dict>
3423 </array>
3424 </dict>
3425 </dict>
3426 <key>end</key>
3427 <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string>
3428 <key>endCaptures</key>
3429 <dict>
3430 <key>1</key>
3431 <dict>
3432 <key>name</key>
3433 <string>string.other.end.code.fenced.mdx</string>
3434 </dict>
3435 </dict>
3436 <key>name</key>
3437 <string>markup.code.console.mdx</string>
3438 <key>patterns</key>
3439 <array>
3440 <dict>
3441 <key>begin</key>
3442 <string>(^|\G)(\s*)(.*)</string>
3443 <key>while</key>
3444 <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string>
3445 <key>contentName</key>
3446 <string>meta.embedded.console</string>
3447 <key>patterns</key>
3448 <array>
3449 <dict>
3450 <key>include</key>
3451 <string>text.python.console</string>
3452 </dict>
3453 </array>
3454 </dict>
3455 </array>
3456 </dict>
3457 </array>
3458 </dict>
3459 <key>commonmark-code-fenced-cpp</key>
3460 <dict>
3461 <key>patterns</key>
3462 <array>
3463 <dict>
3464 <key>begin</key>
3465 <string>(?:^|\G)[\t ]*(`{3,})(?:[\t ]*((?i:ags|ags\x2dscript|asymptote|c\+\+|edje\x2ddata\x2dcollection|game\x2dmaker\x2dlanguage|swig|(?:.*\.)?(?:asc|ash|asy|c\+\+|cc|cp|cpp|cppm|cxx|edc|gml|h\+\+|hh|hpp|hxx|inl|ino|ipp|ixx|metal|re|tcc|tpp|txx)))(?:[\t ]+((?:[^\n\r`])+))?)(?:[\t ]*$)</string>
3466 <key>beginCaptures</key>
3467 <dict>
3468 <key>1</key>
3469 <dict>
3470 <key>name</key>
3471 <string>string.other.begin.code.fenced.mdx</string>
3472 </dict>
3473 <key>2</key>
3474 <dict>
3475 <key>name</key>
3476 <string>entity.name.function.mdx</string>
3477 <key>patterns</key>
3478 <array>
3479 <dict>
3480 <key>include</key>
3481 <string>#markdown-string</string>
3482 </dict>
3483 </array>
3484 </dict>
3485 <key>3</key>
3486 <dict>
3487 <key>patterns</key>
3488 <array>
3489 <dict>
3490 <key>include</key>
3491 <string>#markdown-string</string>
3492 </dict>
3493 </array>
3494 </dict>
3495 </dict>
3496 <key>end</key>
3497 <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string>
3498 <key>endCaptures</key>
3499 <dict>
3500 <key>1</key>
3501 <dict>
3502 <key>name</key>
3503 <string>string.other.end.code.fenced.mdx</string>
3504 </dict>
3505 </dict>
3506 <key>name</key>
3507 <string>markup.code.cpp.mdx</string>
3508 <key>patterns</key>
3509 <array>
3510 <dict>
3511 <key>begin</key>
3512 <string>(^|\G)(\s*)(.*)</string>
3513 <key>while</key>
3514 <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string>
3515 <key>contentName</key>
3516 <string>meta.embedded.cpp</string>
3517 <key>patterns</key>
3518 <array>
3519 <dict>
3520 <key>include</key>
3521 <string>source.c++</string>
3522 </dict>
3523 <dict>
3524 <key>include</key>
3525 <string>source.cpp</string>
3526 </dict>
3527 </array>
3528 </dict>
3529 </array>
3530 </dict>
3531 <dict>
3532 <key>begin</key>
3533 <string>(?:^|\G)[\t ]*(~{3,})(?:[\t ]*((?i:ags|ags\x2dscript|asymptote|c\+\+|edje\x2ddata\x2dcollection|game\x2dmaker\x2dlanguage|swig|(?:.*\.)?(?:asc|ash|asy|c\+\+|cc|cp|cpp|cppm|cxx|edc|gml|h\+\+|hh|hpp|hxx|inl|ino|ipp|ixx|metal|re|tcc|tpp|txx)))(?:[\t ]+((?:[^\n\r])+))?)(?:[\t ]*$)</string>
3534 <key>beginCaptures</key>
3535 <dict>
3536 <key>1</key>
3537 <dict>
3538 <key>name</key>
3539 <string>string.other.begin.code.fenced.mdx</string>
3540 </dict>
3541 <key>2</key>
3542 <dict>
3543 <key>name</key>
3544 <string>entity.name.function.mdx</string>
3545 <key>patterns</key>
3546 <array>
3547 <dict>
3548 <key>include</key>
3549 <string>#markdown-string</string>
3550 </dict>
3551 </array>
3552 </dict>
3553 <key>3</key>
3554 <dict>
3555 <key>patterns</key>
3556 <array>
3557 <dict>
3558 <key>include</key>
3559 <string>#markdown-string</string>
3560 </dict>
3561 </array>
3562 </dict>
3563 </dict>
3564 <key>end</key>
3565 <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string>
3566 <key>endCaptures</key>
3567 <dict>
3568 <key>1</key>
3569 <dict>
3570 <key>name</key>
3571 <string>string.other.end.code.fenced.mdx</string>
3572 </dict>
3573 </dict>
3574 <key>name</key>
3575 <string>markup.code.cpp.mdx</string>
3576 <key>patterns</key>
3577 <array>
3578 <dict>
3579 <key>begin</key>
3580 <string>(^|\G)(\s*)(.*)</string>
3581 <key>while</key>
3582 <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string>
3583 <key>contentName</key>
3584 <string>meta.embedded.cpp</string>
3585 <key>patterns</key>
3586 <array>
3587 <dict>
3588 <key>include</key>
3589 <string>source.c++</string>
3590 </dict>
3591 <dict>
3592 <key>include</key>
3593 <string>source.cpp</string>
3594 </dict>
3595 </array>
3596 </dict>
3597 </array>
3598 </dict>
3599 </array>
3600 </dict>
3601 <key>commonmark-code-fenced-cs</key>
3602 <dict>
3603 <key>patterns</key>
3604 <array>
3605 <dict>
3606 <key>begin</key>
3607 <string>(?:^|\G)[\t ]*(`{3,})(?:[\t ]*((?i:beef|c#|cakescript|csharp|(?:.*\.)?(?:bf|cake|cs|cs\.pp|csx|eq|linq|uno)))(?:[\t ]+((?:[^\n\r`])+))?)(?:[\t ]*$)</string>
3608 <key>beginCaptures</key>
3609 <dict>
3610 <key>1</key>
3611 <dict>
3612 <key>name</key>
3613 <string>string.other.begin.code.fenced.mdx</string>
3614 </dict>
3615 <key>2</key>
3616 <dict>
3617 <key>name</key>
3618 <string>entity.name.function.mdx</string>
3619 <key>patterns</key>
3620 <array>
3621 <dict>
3622 <key>include</key>
3623 <string>#markdown-string</string>
3624 </dict>
3625 </array>
3626 </dict>
3627 <key>3</key>
3628 <dict>
3629 <key>patterns</key>
3630 <array>
3631 <dict>
3632 <key>include</key>
3633 <string>#markdown-string</string>
3634 </dict>
3635 </array>
3636 </dict>
3637 </dict>
3638 <key>end</key>
3639 <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string>
3640 <key>endCaptures</key>
3641 <dict>
3642 <key>1</key>
3643 <dict>
3644 <key>name</key>
3645 <string>string.other.end.code.fenced.mdx</string>
3646 </dict>
3647 </dict>
3648 <key>name</key>
3649 <string>markup.code.cs.mdx</string>
3650 <key>patterns</key>
3651 <array>
3652 <dict>
3653 <key>begin</key>
3654 <string>(^|\G)(\s*)(.*)</string>
3655 <key>while</key>
3656 <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string>
3657 <key>contentName</key>
3658 <string>meta.embedded.cs</string>
3659 <key>patterns</key>
3660 <array>
3661 <dict>
3662 <key>include</key>
3663 <string>source.cs</string>
3664 </dict>
3665 </array>
3666 </dict>
3667 </array>
3668 </dict>
3669 <dict>
3670 <key>begin</key>
3671 <string>(?:^|\G)[\t ]*(~{3,})(?:[\t ]*((?i:beef|c#|cakescript|csharp|(?:.*\.)?(?:bf|cake|cs|cs\.pp|csx|eq|linq|uno)))(?:[\t ]+((?:[^\n\r])+))?)(?:[\t ]*$)</string>
3672 <key>beginCaptures</key>
3673 <dict>
3674 <key>1</key>
3675 <dict>
3676 <key>name</key>
3677 <string>string.other.begin.code.fenced.mdx</string>
3678 </dict>
3679 <key>2</key>
3680 <dict>
3681 <key>name</key>
3682 <string>entity.name.function.mdx</string>
3683 <key>patterns</key>
3684 <array>
3685 <dict>
3686 <key>include</key>
3687 <string>#markdown-string</string>
3688 </dict>
3689 </array>
3690 </dict>
3691 <key>3</key>
3692 <dict>
3693 <key>patterns</key>
3694 <array>
3695 <dict>
3696 <key>include</key>
3697 <string>#markdown-string</string>
3698 </dict>
3699 </array>
3700 </dict>
3701 </dict>
3702 <key>end</key>
3703 <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string>
3704 <key>endCaptures</key>
3705 <dict>
3706 <key>1</key>
3707 <dict>
3708 <key>name</key>
3709 <string>string.other.end.code.fenced.mdx</string>
3710 </dict>
3711 </dict>
3712 <key>name</key>
3713 <string>markup.code.cs.mdx</string>
3714 <key>patterns</key>
3715 <array>
3716 <dict>
3717 <key>begin</key>
3718 <string>(^|\G)(\s*)(.*)</string>
3719 <key>while</key>
3720 <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string>
3721 <key>contentName</key>
3722 <string>meta.embedded.cs</string>
3723 <key>patterns</key>
3724 <array>
3725 <dict>
3726 <key>include</key>
3727 <string>source.cs</string>
3728 </dict>
3729 </array>
3730 </dict>
3731 </array>
3732 </dict>
3733 </array>
3734 </dict>
3735 <key>commonmark-code-fenced-css</key>
3736 <dict>
3737 <key>patterns</key>
3738 <array>
3739 <dict>
3740 <key>begin</key>
3741 <string>(?:^|\G)[\t ]*(`{3,})(?:[\t ]*((?i:(?:.*\.)?css))(?:[\t ]+((?:[^\n\r`])+))?)(?:[\t ]*$)</string>
3742 <key>beginCaptures</key>
3743 <dict>
3744 <key>1</key>
3745 <dict>
3746 <key>name</key>
3747 <string>string.other.begin.code.fenced.mdx</string>
3748 </dict>
3749 <key>2</key>
3750 <dict>
3751 <key>name</key>
3752 <string>entity.name.function.mdx</string>
3753 <key>patterns</key>
3754 <array>
3755 <dict>
3756 <key>include</key>
3757 <string>#markdown-string</string>
3758 </dict>
3759 </array>
3760 </dict>
3761 <key>3</key>
3762 <dict>
3763 <key>patterns</key>
3764 <array>
3765 <dict>
3766 <key>include</key>
3767 <string>#markdown-string</string>
3768 </dict>
3769 </array>
3770 </dict>
3771 </dict>
3772 <key>end</key>
3773 <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string>
3774 <key>endCaptures</key>
3775 <dict>
3776 <key>1</key>
3777 <dict>
3778 <key>name</key>
3779 <string>string.other.end.code.fenced.mdx</string>
3780 </dict>
3781 </dict>
3782 <key>name</key>
3783 <string>markup.code.css.mdx</string>
3784 <key>patterns</key>
3785 <array>
3786 <dict>
3787 <key>begin</key>
3788 <string>(^|\G)(\s*)(.*)</string>
3789 <key>while</key>
3790 <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string>
3791 <key>contentName</key>
3792 <string>meta.embedded.css</string>
3793 <key>patterns</key>
3794 <array>
3795 <dict>
3796 <key>include</key>
3797 <string>source.css</string>
3798 </dict>
3799 </array>
3800 </dict>
3801 </array>
3802 </dict>
3803 <dict>
3804 <key>begin</key>
3805 <string>(?:^|\G)[\t ]*(~{3,})(?:[\t ]*((?i:(?:.*\.)?css))(?:[\t ]+((?:[^\n\r])+))?)(?:[\t ]*$)</string>
3806 <key>beginCaptures</key>
3807 <dict>
3808 <key>1</key>
3809 <dict>
3810 <key>name</key>
3811 <string>string.other.begin.code.fenced.mdx</string>
3812 </dict>
3813 <key>2</key>
3814 <dict>
3815 <key>name</key>
3816 <string>entity.name.function.mdx</string>
3817 <key>patterns</key>
3818 <array>
3819 <dict>
3820 <key>include</key>
3821 <string>#markdown-string</string>
3822 </dict>
3823 </array>
3824 </dict>
3825 <key>3</key>
3826 <dict>
3827 <key>patterns</key>
3828 <array>
3829 <dict>
3830 <key>include</key>
3831 <string>#markdown-string</string>
3832 </dict>
3833 </array>
3834 </dict>
3835 </dict>
3836 <key>end</key>
3837 <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string>
3838 <key>endCaptures</key>
3839 <dict>
3840 <key>1</key>
3841 <dict>
3842 <key>name</key>
3843 <string>string.other.end.code.fenced.mdx</string>
3844 </dict>
3845 </dict>
3846 <key>name</key>
3847 <string>markup.code.css.mdx</string>
3848 <key>patterns</key>
3849 <array>
3850 <dict>
3851 <key>begin</key>
3852 <string>(^|\G)(\s*)(.*)</string>
3853 <key>while</key>
3854 <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string>
3855 <key>contentName</key>
3856 <string>meta.embedded.css</string>
3857 <key>patterns</key>
3858 <array>
3859 <dict>
3860 <key>include</key>
3861 <string>source.css</string>
3862 </dict>
3863 </array>
3864 </dict>
3865 </array>
3866 </dict>
3867 </array>
3868 </dict>
3869 <key>commonmark-code-fenced-diff</key>
3870 <dict>
3871 <key>patterns</key>
3872 <array>
3873 <dict>
3874 <key>begin</key>
3875 <string>(?:^|\G)[\t ]*(`{3,})(?:[\t ]*((?i:udiff|(?:.*\.)?(?:diff|patch)))(?:[\t ]+((?:[^\n\r`])+))?)(?:[\t ]*$)</string>
3876 <key>beginCaptures</key>
3877 <dict>
3878 <key>1</key>
3879 <dict>
3880 <key>name</key>
3881 <string>string.other.begin.code.fenced.mdx</string>
3882 </dict>
3883 <key>2</key>
3884 <dict>
3885 <key>name</key>
3886 <string>entity.name.function.mdx</string>
3887 <key>patterns</key>
3888 <array>
3889 <dict>
3890 <key>include</key>
3891 <string>#markdown-string</string>
3892 </dict>
3893 </array>
3894 </dict>
3895 <key>3</key>
3896 <dict>
3897 <key>patterns</key>
3898 <array>
3899 <dict>
3900 <key>include</key>
3901 <string>#markdown-string</string>
3902 </dict>
3903 </array>
3904 </dict>
3905 </dict>
3906 <key>end</key>
3907 <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string>
3908 <key>endCaptures</key>
3909 <dict>
3910 <key>1</key>
3911 <dict>
3912 <key>name</key>
3913 <string>string.other.end.code.fenced.mdx</string>
3914 </dict>
3915 </dict>
3916 <key>name</key>
3917 <string>markup.code.diff.mdx</string>
3918 <key>patterns</key>
3919 <array>
3920 <dict>
3921 <key>begin</key>
3922 <string>(^|\G)(\s*)(.*)</string>
3923 <key>while</key>
3924 <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string>
3925 <key>contentName</key>
3926 <string>meta.embedded.diff</string>
3927 <key>patterns</key>
3928 <array>
3929 <dict>
3930 <key>include</key>
3931 <string>source.diff</string>
3932 </dict>
3933 </array>
3934 </dict>
3935 </array>
3936 </dict>
3937 <dict>
3938 <key>begin</key>
3939 <string>(?:^|\G)[\t ]*(~{3,})(?:[\t ]*((?i:udiff|(?:.*\.)?(?:diff|patch)))(?:[\t ]+((?:[^\n\r])+))?)(?:[\t ]*$)</string>
3940 <key>beginCaptures</key>
3941 <dict>
3942 <key>1</key>
3943 <dict>
3944 <key>name</key>
3945 <string>string.other.begin.code.fenced.mdx</string>
3946 </dict>
3947 <key>2</key>
3948 <dict>
3949 <key>name</key>
3950 <string>entity.name.function.mdx</string>
3951 <key>patterns</key>
3952 <array>
3953 <dict>
3954 <key>include</key>
3955 <string>#markdown-string</string>
3956 </dict>
3957 </array>
3958 </dict>
3959 <key>3</key>
3960 <dict>
3961 <key>patterns</key>
3962 <array>
3963 <dict>
3964 <key>include</key>
3965 <string>#markdown-string</string>
3966 </dict>
3967 </array>
3968 </dict>
3969 </dict>
3970 <key>end</key>
3971 <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string>
3972 <key>endCaptures</key>
3973 <dict>
3974 <key>1</key>
3975 <dict>
3976 <key>name</key>
3977 <string>string.other.end.code.fenced.mdx</string>
3978 </dict>
3979 </dict>
3980 <key>name</key>
3981 <string>markup.code.diff.mdx</string>
3982 <key>patterns</key>
3983 <array>
3984 <dict>
3985 <key>begin</key>
3986 <string>(^|\G)(\s*)(.*)</string>
3987 <key>while</key>
3988 <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string>
3989 <key>contentName</key>
3990 <string>meta.embedded.diff</string>
3991 <key>patterns</key>
3992 <array>
3993 <dict>
3994 <key>include</key>
3995 <string>source.diff</string>
3996 </dict>
3997 </array>
3998 </dict>
3999 </array>
4000 </dict>
4001 </array>
4002 </dict>
4003 <key>commonmark-code-fenced-dockerfile</key>
4004 <dict>
4005 <key>patterns</key>
4006 <array>
4007 <dict>
4008 <key>begin</key>
4009 <string>(?:^|\G)[\t ]*(`{3,})(?:[\t ]*((?i:containerfile|(?:.*\.)?dockerfile))(?:[\t ]+((?:[^\n\r`])+))?)(?:[\t ]*$)</string>
4010 <key>beginCaptures</key>
4011 <dict>
4012 <key>1</key>
4013 <dict>
4014 <key>name</key>
4015 <string>string.other.begin.code.fenced.mdx</string>
4016 </dict>
4017 <key>2</key>
4018 <dict>
4019 <key>name</key>
4020 <string>entity.name.function.mdx</string>
4021 <key>patterns</key>
4022 <array>
4023 <dict>
4024 <key>include</key>
4025 <string>#markdown-string</string>
4026 </dict>
4027 </array>
4028 </dict>
4029 <key>3</key>
4030 <dict>
4031 <key>patterns</key>
4032 <array>
4033 <dict>
4034 <key>include</key>
4035 <string>#markdown-string</string>
4036 </dict>
4037 </array>
4038 </dict>
4039 </dict>
4040 <key>end</key>
4041 <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string>
4042 <key>endCaptures</key>
4043 <dict>
4044 <key>1</key>
4045 <dict>
4046 <key>name</key>
4047 <string>string.other.end.code.fenced.mdx</string>
4048 </dict>
4049 </dict>
4050 <key>name</key>
4051 <string>markup.code.dockerfile.mdx</string>
4052 <key>patterns</key>
4053 <array>
4054 <dict>
4055 <key>begin</key>
4056 <string>(^|\G)(\s*)(.*)</string>
4057 <key>while</key>
4058 <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string>
4059 <key>contentName</key>
4060 <string>meta.embedded.dockerfile</string>
4061 <key>patterns</key>
4062 <array>
4063 <dict>
4064 <key>include</key>
4065 <string>source.dockerfile</string>
4066 </dict>
4067 </array>
4068 </dict>
4069 </array>
4070 </dict>
4071 <dict>
4072 <key>begin</key>
4073 <string>(?:^|\G)[\t ]*(~{3,})(?:[\t ]*((?i:containerfile|(?:.*\.)?dockerfile))(?:[\t ]+((?:[^\n\r])+))?)(?:[\t ]*$)</string>
4074 <key>beginCaptures</key>
4075 <dict>
4076 <key>1</key>
4077 <dict>
4078 <key>name</key>
4079 <string>string.other.begin.code.fenced.mdx</string>
4080 </dict>
4081 <key>2</key>
4082 <dict>
4083 <key>name</key>
4084 <string>entity.name.function.mdx</string>
4085 <key>patterns</key>
4086 <array>
4087 <dict>
4088 <key>include</key>
4089 <string>#markdown-string</string>
4090 </dict>
4091 </array>
4092 </dict>
4093 <key>3</key>
4094 <dict>
4095 <key>patterns</key>
4096 <array>
4097 <dict>
4098 <key>include</key>
4099 <string>#markdown-string</string>
4100 </dict>
4101 </array>
4102 </dict>
4103 </dict>
4104 <key>end</key>
4105 <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string>
4106 <key>endCaptures</key>
4107 <dict>
4108 <key>1</key>
4109 <dict>
4110 <key>name</key>
4111 <string>string.other.end.code.fenced.mdx</string>
4112 </dict>
4113 </dict>
4114 <key>name</key>
4115 <string>markup.code.dockerfile.mdx</string>
4116 <key>patterns</key>
4117 <array>
4118 <dict>
4119 <key>begin</key>
4120 <string>(^|\G)(\s*)(.*)</string>
4121 <key>while</key>
4122 <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string>
4123 <key>contentName</key>
4124 <string>meta.embedded.dockerfile</string>
4125 <key>patterns</key>
4126 <array>
4127 <dict>
4128 <key>include</key>
4129 <string>source.dockerfile</string>
4130 </dict>
4131 </array>
4132 </dict>
4133 </array>
4134 </dict>
4135 </array>
4136 </dict>
4137 <key>commonmark-code-fenced-elixir</key>
4138 <dict>
4139 <key>patterns</key>
4140 <array>
4141 <dict>
4142 <key>begin</key>
4143 <string>(?:^|\G)[\t ]*(`{3,})(?:[\t ]*((?i:elixir|(?:.*\.)?(?:ex|exs)))(?:[\t ]+((?:[^\n\r`])+))?)(?:[\t ]*$)</string>
4144 <key>beginCaptures</key>
4145 <dict>
4146 <key>1</key>
4147 <dict>
4148 <key>name</key>
4149 <string>string.other.begin.code.fenced.mdx</string>
4150 </dict>
4151 <key>2</key>
4152 <dict>
4153 <key>name</key>
4154 <string>entity.name.function.mdx</string>
4155 <key>patterns</key>
4156 <array>
4157 <dict>
4158 <key>include</key>
4159 <string>#markdown-string</string>
4160 </dict>
4161 </array>
4162 </dict>
4163 <key>3</key>
4164 <dict>
4165 <key>patterns</key>
4166 <array>
4167 <dict>
4168 <key>include</key>
4169 <string>#markdown-string</string>
4170 </dict>
4171 </array>
4172 </dict>
4173 </dict>
4174 <key>end</key>
4175 <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string>
4176 <key>endCaptures</key>
4177 <dict>
4178 <key>1</key>
4179 <dict>
4180 <key>name</key>
4181 <string>string.other.end.code.fenced.mdx</string>
4182 </dict>
4183 </dict>
4184 <key>name</key>
4185 <string>markup.code.elixir.mdx</string>
4186 <key>patterns</key>
4187 <array>
4188 <dict>
4189 <key>begin</key>
4190 <string>(^|\G)(\s*)(.*)</string>
4191 <key>while</key>
4192 <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string>
4193 <key>contentName</key>
4194 <string>meta.embedded.elixir</string>
4195 <key>patterns</key>
4196 <array>
4197 <dict>
4198 <key>include</key>
4199 <string>source.elixir</string>
4200 </dict>
4201 </array>
4202 </dict>
4203 </array>
4204 </dict>
4205 <dict>
4206 <key>begin</key>
4207 <string>(?:^|\G)[\t ]*(~{3,})(?:[\t ]*((?i:elixir|(?:.*\.)?(?:ex|exs)))(?:[\t ]+((?:[^\n\r])+))?)(?:[\t ]*$)</string>
4208 <key>beginCaptures</key>
4209 <dict>
4210 <key>1</key>
4211 <dict>
4212 <key>name</key>
4213 <string>string.other.begin.code.fenced.mdx</string>
4214 </dict>
4215 <key>2</key>
4216 <dict>
4217 <key>name</key>
4218 <string>entity.name.function.mdx</string>
4219 <key>patterns</key>
4220 <array>
4221 <dict>
4222 <key>include</key>
4223 <string>#markdown-string</string>
4224 </dict>
4225 </array>
4226 </dict>
4227 <key>3</key>
4228 <dict>
4229 <key>patterns</key>
4230 <array>
4231 <dict>
4232 <key>include</key>
4233 <string>#markdown-string</string>
4234 </dict>
4235 </array>
4236 </dict>
4237 </dict>
4238 <key>end</key>
4239 <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string>
4240 <key>endCaptures</key>
4241 <dict>
4242 <key>1</key>
4243 <dict>
4244 <key>name</key>
4245 <string>string.other.end.code.fenced.mdx</string>
4246 </dict>
4247 </dict>
4248 <key>name</key>
4249 <string>markup.code.elixir.mdx</string>
4250 <key>patterns</key>
4251 <array>
4252 <dict>
4253 <key>begin</key>
4254 <string>(^|\G)(\s*)(.*)</string>
4255 <key>while</key>
4256 <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string>
4257 <key>contentName</key>
4258 <string>meta.embedded.elixir</string>
4259 <key>patterns</key>
4260 <array>
4261 <dict>
4262 <key>include</key>
4263 <string>source.elixir</string>
4264 </dict>
4265 </array>
4266 </dict>
4267 </array>
4268 </dict>
4269 </array>
4270 </dict>
4271 <key>commonmark-code-fenced-elm</key>
4272 <dict>
4273 <key>patterns</key>
4274 <array>
4275 <dict>
4276 <key>begin</key>
4277 <string>(?:^|\G)[\t ]*(`{3,})(?:[\t ]*((?i:(?:.*\.)?elm))(?:[\t ]+((?:[^\n\r`])+))?)(?:[\t ]*$)</string>
4278 <key>beginCaptures</key>
4279 <dict>
4280 <key>1</key>
4281 <dict>
4282 <key>name</key>
4283 <string>string.other.begin.code.fenced.mdx</string>
4284 </dict>
4285 <key>2</key>
4286 <dict>
4287 <key>name</key>
4288 <string>entity.name.function.mdx</string>
4289 <key>patterns</key>
4290 <array>
4291 <dict>
4292 <key>include</key>
4293 <string>#markdown-string</string>
4294 </dict>
4295 </array>
4296 </dict>
4297 <key>3</key>
4298 <dict>
4299 <key>patterns</key>
4300 <array>
4301 <dict>
4302 <key>include</key>
4303 <string>#markdown-string</string>
4304 </dict>
4305 </array>
4306 </dict>
4307 </dict>
4308 <key>end</key>
4309 <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string>
4310 <key>endCaptures</key>
4311 <dict>
4312 <key>1</key>
4313 <dict>
4314 <key>name</key>
4315 <string>string.other.end.code.fenced.mdx</string>
4316 </dict>
4317 </dict>
4318 <key>name</key>
4319 <string>markup.code.elm.mdx</string>
4320 <key>patterns</key>
4321 <array>
4322 <dict>
4323 <key>begin</key>
4324 <string>(^|\G)(\s*)(.*)</string>
4325 <key>while</key>
4326 <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string>
4327 <key>contentName</key>
4328 <string>meta.embedded.elm</string>
4329 <key>patterns</key>
4330 <array>
4331 <dict>
4332 <key>include</key>
4333 <string>source.elm</string>
4334 </dict>
4335 </array>
4336 </dict>
4337 </array>
4338 </dict>
4339 <dict>
4340 <key>begin</key>
4341 <string>(?:^|\G)[\t ]*(~{3,})(?:[\t ]*((?i:(?:.*\.)?elm))(?:[\t ]+((?:[^\n\r])+))?)(?:[\t ]*$)</string>
4342 <key>beginCaptures</key>
4343 <dict>
4344 <key>1</key>
4345 <dict>
4346 <key>name</key>
4347 <string>string.other.begin.code.fenced.mdx</string>
4348 </dict>
4349 <key>2</key>
4350 <dict>
4351 <key>name</key>
4352 <string>entity.name.function.mdx</string>
4353 <key>patterns</key>
4354 <array>
4355 <dict>
4356 <key>include</key>
4357 <string>#markdown-string</string>
4358 </dict>
4359 </array>
4360 </dict>
4361 <key>3</key>
4362 <dict>
4363 <key>patterns</key>
4364 <array>
4365 <dict>
4366 <key>include</key>
4367 <string>#markdown-string</string>
4368 </dict>
4369 </array>
4370 </dict>
4371 </dict>
4372 <key>end</key>
4373 <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string>
4374 <key>endCaptures</key>
4375 <dict>
4376 <key>1</key>
4377 <dict>
4378 <key>name</key>
4379 <string>string.other.end.code.fenced.mdx</string>
4380 </dict>
4381 </dict>
4382 <key>name</key>
4383 <string>markup.code.elm.mdx</string>
4384 <key>patterns</key>
4385 <array>
4386 <dict>
4387 <key>begin</key>
4388 <string>(^|\G)(\s*)(.*)</string>
4389 <key>while</key>
4390 <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string>
4391 <key>contentName</key>
4392 <string>meta.embedded.elm</string>
4393 <key>patterns</key>
4394 <array>
4395 <dict>
4396 <key>include</key>
4397 <string>source.elm</string>
4398 </dict>
4399 </array>
4400 </dict>
4401 </array>
4402 </dict>
4403 </array>
4404 </dict>
4405 <key>commonmark-code-fenced-erlang</key>
4406 <dict>
4407 <key>patterns</key>
4408 <array>
4409 <dict>
4410 <key>begin</key>
4411 <string>(?:^|\G)[\t ]*(`{3,})(?:[\t ]*((?i:erlang|(?:.*\.)?(?:app|app\.src|erl|es|escript|hrl|xrl|yrl)))(?:[\t ]+((?:[^\n\r`])+))?)(?:[\t ]*$)</string>
4412 <key>beginCaptures</key>
4413 <dict>
4414 <key>1</key>
4415 <dict>
4416 <key>name</key>
4417 <string>string.other.begin.code.fenced.mdx</string>
4418 </dict>
4419 <key>2</key>
4420 <dict>
4421 <key>name</key>
4422 <string>entity.name.function.mdx</string>
4423 <key>patterns</key>
4424 <array>
4425 <dict>
4426 <key>include</key>
4427 <string>#markdown-string</string>
4428 </dict>
4429 </array>
4430 </dict>
4431 <key>3</key>
4432 <dict>
4433 <key>patterns</key>
4434 <array>
4435 <dict>
4436 <key>include</key>
4437 <string>#markdown-string</string>
4438 </dict>
4439 </array>
4440 </dict>
4441 </dict>
4442 <key>end</key>
4443 <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string>
4444 <key>endCaptures</key>
4445 <dict>
4446 <key>1</key>
4447 <dict>
4448 <key>name</key>
4449 <string>string.other.end.code.fenced.mdx</string>
4450 </dict>
4451 </dict>
4452 <key>name</key>
4453 <string>markup.code.erlang.mdx</string>
4454 <key>patterns</key>
4455 <array>
4456 <dict>
4457 <key>begin</key>
4458 <string>(^|\G)(\s*)(.*)</string>
4459 <key>while</key>
4460 <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string>
4461 <key>contentName</key>
4462 <string>meta.embedded.erlang</string>
4463 <key>patterns</key>
4464 <array>
4465 <dict>
4466 <key>include</key>
4467 <string>source.erlang</string>
4468 </dict>
4469 </array>
4470 </dict>
4471 </array>
4472 </dict>
4473 <dict>
4474 <key>begin</key>
4475 <string>(?:^|\G)[\t ]*(~{3,})(?:[\t ]*((?i:erlang|(?:.*\.)?(?:app|app\.src|erl|es|escript|hrl|xrl|yrl)))(?:[\t ]+((?:[^\n\r])+))?)(?:[\t ]*$)</string>
4476 <key>beginCaptures</key>
4477 <dict>
4478 <key>1</key>
4479 <dict>
4480 <key>name</key>
4481 <string>string.other.begin.code.fenced.mdx</string>
4482 </dict>
4483 <key>2</key>
4484 <dict>
4485 <key>name</key>
4486 <string>entity.name.function.mdx</string>
4487 <key>patterns</key>
4488 <array>
4489 <dict>
4490 <key>include</key>
4491 <string>#markdown-string</string>
4492 </dict>
4493 </array>
4494 </dict>
4495 <key>3</key>
4496 <dict>
4497 <key>patterns</key>
4498 <array>
4499 <dict>
4500 <key>include</key>
4501 <string>#markdown-string</string>
4502 </dict>
4503 </array>
4504 </dict>
4505 </dict>
4506 <key>end</key>
4507 <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string>
4508 <key>endCaptures</key>
4509 <dict>
4510 <key>1</key>
4511 <dict>
4512 <key>name</key>
4513 <string>string.other.end.code.fenced.mdx</string>
4514 </dict>
4515 </dict>
4516 <key>name</key>
4517 <string>markup.code.erlang.mdx</string>
4518 <key>patterns</key>
4519 <array>
4520 <dict>
4521 <key>begin</key>
4522 <string>(^|\G)(\s*)(.*)</string>
4523 <key>while</key>
4524 <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string>
4525 <key>contentName</key>
4526 <string>meta.embedded.erlang</string>
4527 <key>patterns</key>
4528 <array>
4529 <dict>
4530 <key>include</key>
4531 <string>source.erlang</string>
4532 </dict>
4533 </array>
4534 </dict>
4535 </array>
4536 </dict>
4537 </array>
4538 </dict>
4539 <key>commonmark-code-fenced-gitconfig</key>
4540 <dict>
4541 <key>patterns</key>
4542 <array>
4543 <dict>
4544 <key>begin</key>
4545 <string>(?:^|\G)[\t ]*(`{3,})(?:[\t ]*((?i:git\x2dconfig|gitmodules|(?:.*\.)?gitconfig))(?:[\t ]+((?:[^\n\r`])+))?)(?:[\t ]*$)</string>
4546 <key>beginCaptures</key>
4547 <dict>
4548 <key>1</key>
4549 <dict>
4550 <key>name</key>
4551 <string>string.other.begin.code.fenced.mdx</string>
4552 </dict>
4553 <key>2</key>
4554 <dict>
4555 <key>name</key>
4556 <string>entity.name.function.mdx</string>
4557 <key>patterns</key>
4558 <array>
4559 <dict>
4560 <key>include</key>
4561 <string>#markdown-string</string>
4562 </dict>
4563 </array>
4564 </dict>
4565 <key>3</key>
4566 <dict>
4567 <key>patterns</key>
4568 <array>
4569 <dict>
4570 <key>include</key>
4571 <string>#markdown-string</string>
4572 </dict>
4573 </array>
4574 </dict>
4575 </dict>
4576 <key>end</key>
4577 <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string>
4578 <key>endCaptures</key>
4579 <dict>
4580 <key>1</key>
4581 <dict>
4582 <key>name</key>
4583 <string>string.other.end.code.fenced.mdx</string>
4584 </dict>
4585 </dict>
4586 <key>name</key>
4587 <string>markup.code.gitconfig.mdx</string>
4588 <key>patterns</key>
4589 <array>
4590 <dict>
4591 <key>begin</key>
4592 <string>(^|\G)(\s*)(.*)</string>
4593 <key>while</key>
4594 <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string>
4595 <key>contentName</key>
4596 <string>meta.embedded.gitconfig</string>
4597 <key>patterns</key>
4598 <array>
4599 <dict>
4600 <key>include</key>
4601 <string>source.gitconfig</string>
4602 </dict>
4603 </array>
4604 </dict>
4605 </array>
4606 </dict>
4607 <dict>
4608 <key>begin</key>
4609 <string>(?:^|\G)[\t ]*(~{3,})(?:[\t ]*((?i:git\x2dconfig|gitmodules|(?:.*\.)?gitconfig))(?:[\t ]+((?:[^\n\r])+))?)(?:[\t ]*$)</string>
4610 <key>beginCaptures</key>
4611 <dict>
4612 <key>1</key>
4613 <dict>
4614 <key>name</key>
4615 <string>string.other.begin.code.fenced.mdx</string>
4616 </dict>
4617 <key>2</key>
4618 <dict>
4619 <key>name</key>
4620 <string>entity.name.function.mdx</string>
4621 <key>patterns</key>
4622 <array>
4623 <dict>
4624 <key>include</key>
4625 <string>#markdown-string</string>
4626 </dict>
4627 </array>
4628 </dict>
4629 <key>3</key>
4630 <dict>
4631 <key>patterns</key>
4632 <array>
4633 <dict>
4634 <key>include</key>
4635 <string>#markdown-string</string>
4636 </dict>
4637 </array>
4638 </dict>
4639 </dict>
4640 <key>end</key>
4641 <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string>
4642 <key>endCaptures</key>
4643 <dict>
4644 <key>1</key>
4645 <dict>
4646 <key>name</key>
4647 <string>string.other.end.code.fenced.mdx</string>
4648 </dict>
4649 </dict>
4650 <key>name</key>
4651 <string>markup.code.gitconfig.mdx</string>
4652 <key>patterns</key>
4653 <array>
4654 <dict>
4655 <key>begin</key>
4656 <string>(^|\G)(\s*)(.*)</string>
4657 <key>while</key>
4658 <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string>
4659 <key>contentName</key>
4660 <string>meta.embedded.gitconfig</string>
4661 <key>patterns</key>
4662 <array>
4663 <dict>
4664 <key>include</key>
4665 <string>source.gitconfig</string>
4666 </dict>
4667 </array>
4668 </dict>
4669 </array>
4670 </dict>
4671 </array>
4672 </dict>
4673 <key>commonmark-code-fenced-go</key>
4674 <dict>
4675 <key>patterns</key>
4676 <array>
4677 <dict>
4678 <key>begin</key>
4679 <string>(?:^|\G)[\t ]*(`{3,})(?:[\t ]*((?i:golang|(?:.*\.)?go))(?:[\t ]+((?:[^\n\r`])+))?)(?:[\t ]*$)</string>
4680 <key>beginCaptures</key>
4681 <dict>
4682 <key>1</key>
4683 <dict>
4684 <key>name</key>
4685 <string>string.other.begin.code.fenced.mdx</string>
4686 </dict>
4687 <key>2</key>
4688 <dict>
4689 <key>name</key>
4690 <string>entity.name.function.mdx</string>
4691 <key>patterns</key>
4692 <array>
4693 <dict>
4694 <key>include</key>
4695 <string>#markdown-string</string>
4696 </dict>
4697 </array>
4698 </dict>
4699 <key>3</key>
4700 <dict>
4701 <key>patterns</key>
4702 <array>
4703 <dict>
4704 <key>include</key>
4705 <string>#markdown-string</string>
4706 </dict>
4707 </array>
4708 </dict>
4709 </dict>
4710 <key>end</key>
4711 <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string>
4712 <key>endCaptures</key>
4713 <dict>
4714 <key>1</key>
4715 <dict>
4716 <key>name</key>
4717 <string>string.other.end.code.fenced.mdx</string>
4718 </dict>
4719 </dict>
4720 <key>name</key>
4721 <string>markup.code.go.mdx</string>
4722 <key>patterns</key>
4723 <array>
4724 <dict>
4725 <key>begin</key>
4726 <string>(^|\G)(\s*)(.*)</string>
4727 <key>while</key>
4728 <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string>
4729 <key>contentName</key>
4730 <string>meta.embedded.go</string>
4731 <key>patterns</key>
4732 <array>
4733 <dict>
4734 <key>include</key>
4735 <string>source.go</string>
4736 </dict>
4737 </array>
4738 </dict>
4739 </array>
4740 </dict>
4741 <dict>
4742 <key>begin</key>
4743 <string>(?:^|\G)[\t ]*(~{3,})(?:[\t ]*((?i:golang|(?:.*\.)?go))(?:[\t ]+((?:[^\n\r])+))?)(?:[\t ]*$)</string>
4744 <key>beginCaptures</key>
4745 <dict>
4746 <key>1</key>
4747 <dict>
4748 <key>name</key>
4749 <string>string.other.begin.code.fenced.mdx</string>
4750 </dict>
4751 <key>2</key>
4752 <dict>
4753 <key>name</key>
4754 <string>entity.name.function.mdx</string>
4755 <key>patterns</key>
4756 <array>
4757 <dict>
4758 <key>include</key>
4759 <string>#markdown-string</string>
4760 </dict>
4761 </array>
4762 </dict>
4763 <key>3</key>
4764 <dict>
4765 <key>patterns</key>
4766 <array>
4767 <dict>
4768 <key>include</key>
4769 <string>#markdown-string</string>
4770 </dict>
4771 </array>
4772 </dict>
4773 </dict>
4774 <key>end</key>
4775 <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string>
4776 <key>endCaptures</key>
4777 <dict>
4778 <key>1</key>
4779 <dict>
4780 <key>name</key>
4781 <string>string.other.end.code.fenced.mdx</string>
4782 </dict>
4783 </dict>
4784 <key>name</key>
4785 <string>markup.code.go.mdx</string>
4786 <key>patterns</key>
4787 <array>
4788 <dict>
4789 <key>begin</key>
4790 <string>(^|\G)(\s*)(.*)</string>
4791 <key>while</key>
4792 <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string>
4793 <key>contentName</key>
4794 <string>meta.embedded.go</string>
4795 <key>patterns</key>
4796 <array>
4797 <dict>
4798 <key>include</key>
4799 <string>source.go</string>
4800 </dict>
4801 </array>
4802 </dict>
4803 </array>
4804 </dict>
4805 </array>
4806 </dict>
4807 <key>commonmark-code-fenced-graphql</key>
4808 <dict>
4809 <key>patterns</key>
4810 <array>
4811 <dict>
4812 <key>begin</key>
4813 <string>(?:^|\G)[\t ]*(`{3,})(?:[\t ]*((?i:(?:.*\.)?(?:gql|graphql|graphqls)))(?:[\t ]+((?:[^\n\r`])+))?)(?:[\t ]*$)</string>
4814 <key>beginCaptures</key>
4815 <dict>
4816 <key>1</key>
4817 <dict>
4818 <key>name</key>
4819 <string>string.other.begin.code.fenced.mdx</string>
4820 </dict>
4821 <key>2</key>
4822 <dict>
4823 <key>name</key>
4824 <string>entity.name.function.mdx</string>
4825 <key>patterns</key>
4826 <array>
4827 <dict>
4828 <key>include</key>
4829 <string>#markdown-string</string>
4830 </dict>
4831 </array>
4832 </dict>
4833 <key>3</key>
4834 <dict>
4835 <key>patterns</key>
4836 <array>
4837 <dict>
4838 <key>include</key>
4839 <string>#markdown-string</string>
4840 </dict>
4841 </array>
4842 </dict>
4843 </dict>
4844 <key>end</key>
4845 <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string>
4846 <key>endCaptures</key>
4847 <dict>
4848 <key>1</key>
4849 <dict>
4850 <key>name</key>
4851 <string>string.other.end.code.fenced.mdx</string>
4852 </dict>
4853 </dict>
4854 <key>name</key>
4855 <string>markup.code.graphql.mdx</string>
4856 <key>patterns</key>
4857 <array>
4858 <dict>
4859 <key>begin</key>
4860 <string>(^|\G)(\s*)(.*)</string>
4861 <key>while</key>
4862 <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string>
4863 <key>contentName</key>
4864 <string>meta.embedded.graphql</string>
4865 <key>patterns</key>
4866 <array>
4867 <dict>
4868 <key>include</key>
4869 <string>source.graphql</string>
4870 </dict>
4871 </array>
4872 </dict>
4873 </array>
4874 </dict>
4875 <dict>
4876 <key>begin</key>
4877 <string>(?:^|\G)[\t ]*(~{3,})(?:[\t ]*((?i:(?:.*\.)?(?:gql|graphql|graphqls)))(?:[\t ]+((?:[^\n\r])+))?)(?:[\t ]*$)</string>
4878 <key>beginCaptures</key>
4879 <dict>
4880 <key>1</key>
4881 <dict>
4882 <key>name</key>
4883 <string>string.other.begin.code.fenced.mdx</string>
4884 </dict>
4885 <key>2</key>
4886 <dict>
4887 <key>name</key>
4888 <string>entity.name.function.mdx</string>
4889 <key>patterns</key>
4890 <array>
4891 <dict>
4892 <key>include</key>
4893 <string>#markdown-string</string>
4894 </dict>
4895 </array>
4896 </dict>
4897 <key>3</key>
4898 <dict>
4899 <key>patterns</key>
4900 <array>
4901 <dict>
4902 <key>include</key>
4903 <string>#markdown-string</string>
4904 </dict>
4905 </array>
4906 </dict>
4907 </dict>
4908 <key>end</key>
4909 <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string>
4910 <key>endCaptures</key>
4911 <dict>
4912 <key>1</key>
4913 <dict>
4914 <key>name</key>
4915 <string>string.other.end.code.fenced.mdx</string>
4916 </dict>
4917 </dict>
4918 <key>name</key>
4919 <string>markup.code.graphql.mdx</string>
4920 <key>patterns</key>
4921 <array>
4922 <dict>
4923 <key>begin</key>
4924 <string>(^|\G)(\s*)(.*)</string>
4925 <key>while</key>
4926 <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string>
4927 <key>contentName</key>
4928 <string>meta.embedded.graphql</string>
4929 <key>patterns</key>
4930 <array>
4931 <dict>
4932 <key>include</key>
4933 <string>source.graphql</string>
4934 </dict>
4935 </array>
4936 </dict>
4937 </array>
4938 </dict>
4939 </array>
4940 </dict>
4941 <key>commonmark-code-fenced-haskell</key>
4942 <dict>
4943 <key>patterns</key>
4944 <array>
4945 <dict>
4946 <key>begin</key>
4947 <string>(?:^|\G)[\t ]*(`{3,})(?:[\t ]*((?i:c2hs|c2hs\x2dhaskell|frege|haskell|(?:.*\.)?(?:chs|dhall|hs|hs\x2dboot|hsc)))(?:[\t ]+((?:[^\n\r`])+))?)(?:[\t ]*$)</string>
4948 <key>beginCaptures</key>
4949 <dict>
4950 <key>1</key>
4951 <dict>
4952 <key>name</key>
4953 <string>string.other.begin.code.fenced.mdx</string>
4954 </dict>
4955 <key>2</key>
4956 <dict>
4957 <key>name</key>
4958 <string>entity.name.function.mdx</string>
4959 <key>patterns</key>
4960 <array>
4961 <dict>
4962 <key>include</key>
4963 <string>#markdown-string</string>
4964 </dict>
4965 </array>
4966 </dict>
4967 <key>3</key>
4968 <dict>
4969 <key>patterns</key>
4970 <array>
4971 <dict>
4972 <key>include</key>
4973 <string>#markdown-string</string>
4974 </dict>
4975 </array>
4976 </dict>
4977 </dict>
4978 <key>end</key>
4979 <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string>
4980 <key>endCaptures</key>
4981 <dict>
4982 <key>1</key>
4983 <dict>
4984 <key>name</key>
4985 <string>string.other.end.code.fenced.mdx</string>
4986 </dict>
4987 </dict>
4988 <key>name</key>
4989 <string>markup.code.haskell.mdx</string>
4990 <key>patterns</key>
4991 <array>
4992 <dict>
4993 <key>begin</key>
4994 <string>(^|\G)(\s*)(.*)</string>
4995 <key>while</key>
4996 <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string>
4997 <key>contentName</key>
4998 <string>meta.embedded.haskell</string>
4999 <key>patterns</key>
5000 <array>
5001 <dict>
5002 <key>include</key>
5003 <string>source.haskell</string>
5004 </dict>
5005 </array>
5006 </dict>
5007 </array>
5008 </dict>
5009 <dict>
5010 <key>begin</key>
5011 <string>(?:^|\G)[\t ]*(~{3,})(?:[\t ]*((?i:c2hs|c2hs\x2dhaskell|frege|haskell|(?:.*\.)?(?:chs|dhall|hs|hs\x2dboot|hsc)))(?:[\t ]+((?:[^\n\r])+))?)(?:[\t ]*$)</string>
5012 <key>beginCaptures</key>
5013 <dict>
5014 <key>1</key>
5015 <dict>
5016 <key>name</key>
5017 <string>string.other.begin.code.fenced.mdx</string>
5018 </dict>
5019 <key>2</key>
5020 <dict>
5021 <key>name</key>
5022 <string>entity.name.function.mdx</string>
5023 <key>patterns</key>
5024 <array>
5025 <dict>
5026 <key>include</key>
5027 <string>#markdown-string</string>
5028 </dict>
5029 </array>
5030 </dict>
5031 <key>3</key>
5032 <dict>
5033 <key>patterns</key>
5034 <array>
5035 <dict>
5036 <key>include</key>
5037 <string>#markdown-string</string>
5038 </dict>
5039 </array>
5040 </dict>
5041 </dict>
5042 <key>end</key>
5043 <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string>
5044 <key>endCaptures</key>
5045 <dict>
5046 <key>1</key>
5047 <dict>
5048 <key>name</key>
5049 <string>string.other.end.code.fenced.mdx</string>
5050 </dict>
5051 </dict>
5052 <key>name</key>
5053 <string>markup.code.haskell.mdx</string>
5054 <key>patterns</key>
5055 <array>
5056 <dict>
5057 <key>begin</key>
5058 <string>(^|\G)(\s*)(.*)</string>
5059 <key>while</key>
5060 <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string>
5061 <key>contentName</key>
5062 <string>meta.embedded.haskell</string>
5063 <key>patterns</key>
5064 <array>
5065 <dict>
5066 <key>include</key>
5067 <string>source.haskell</string>
5068 </dict>
5069 </array>
5070 </dict>
5071 </array>
5072 </dict>
5073 </array>
5074 </dict>
5075 <key>commonmark-code-fenced-html</key>
5076 <dict>
5077 <key>patterns</key>
5078 <array>
5079 <dict>
5080 <key>begin</key>
5081 <string>(?:^|\G)[\t ]*(`{3,})(?:[\t ]*((?i:html|(?:.*\.)?(?:hta|htm|html\.hl|kit|mtml|xht|xhtml)))(?:[\t ]+((?:[^\n\r`])+))?)(?:[\t ]*$)</string>
5082 <key>beginCaptures</key>
5083 <dict>
5084 <key>1</key>
5085 <dict>
5086 <key>name</key>
5087 <string>string.other.begin.code.fenced.mdx</string>
5088 </dict>
5089 <key>2</key>
5090 <dict>
5091 <key>name</key>
5092 <string>entity.name.function.mdx</string>
5093 <key>patterns</key>
5094 <array>
5095 <dict>
5096 <key>include</key>
5097 <string>#markdown-string</string>
5098 </dict>
5099 </array>
5100 </dict>
5101 <key>3</key>
5102 <dict>
5103 <key>patterns</key>
5104 <array>
5105 <dict>
5106 <key>include</key>
5107 <string>#markdown-string</string>
5108 </dict>
5109 </array>
5110 </dict>
5111 </dict>
5112 <key>end</key>
5113 <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string>
5114 <key>endCaptures</key>
5115 <dict>
5116 <key>1</key>
5117 <dict>
5118 <key>name</key>
5119 <string>string.other.end.code.fenced.mdx</string>
5120 </dict>
5121 </dict>
5122 <key>name</key>
5123 <string>markup.code.html.mdx</string>
5124 <key>patterns</key>
5125 <array>
5126 <dict>
5127 <key>begin</key>
5128 <string>(^|\G)(\s*)(.*)</string>
5129 <key>while</key>
5130 <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string>
5131 <key>contentName</key>
5132 <string>meta.embedded.html</string>
5133 <key>patterns</key>
5134 <array>
5135 <dict>
5136 <key>include</key>
5137 <string>text.html.basic</string>
5138 </dict>
5139 </array>
5140 </dict>
5141 </array>
5142 </dict>
5143 <dict>
5144 <key>begin</key>
5145 <string>(?:^|\G)[\t ]*(~{3,})(?:[\t ]*((?i:html|(?:.*\.)?(?:hta|htm|html\.hl|kit|mtml|xht|xhtml)))(?:[\t ]+((?:[^\n\r])+))?)(?:[\t ]*$)</string>
5146 <key>beginCaptures</key>
5147 <dict>
5148 <key>1</key>
5149 <dict>
5150 <key>name</key>
5151 <string>string.other.begin.code.fenced.mdx</string>
5152 </dict>
5153 <key>2</key>
5154 <dict>
5155 <key>name</key>
5156 <string>entity.name.function.mdx</string>
5157 <key>patterns</key>
5158 <array>
5159 <dict>
5160 <key>include</key>
5161 <string>#markdown-string</string>
5162 </dict>
5163 </array>
5164 </dict>
5165 <key>3</key>
5166 <dict>
5167 <key>patterns</key>
5168 <array>
5169 <dict>
5170 <key>include</key>
5171 <string>#markdown-string</string>
5172 </dict>
5173 </array>
5174 </dict>
5175 </dict>
5176 <key>end</key>
5177 <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string>
5178 <key>endCaptures</key>
5179 <dict>
5180 <key>1</key>
5181 <dict>
5182 <key>name</key>
5183 <string>string.other.end.code.fenced.mdx</string>
5184 </dict>
5185 </dict>
5186 <key>name</key>
5187 <string>markup.code.html.mdx</string>
5188 <key>patterns</key>
5189 <array>
5190 <dict>
5191 <key>begin</key>
5192 <string>(^|\G)(\s*)(.*)</string>
5193 <key>while</key>
5194 <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string>
5195 <key>contentName</key>
5196 <string>meta.embedded.html</string>
5197 <key>patterns</key>
5198 <array>
5199 <dict>
5200 <key>include</key>
5201 <string>text.html.basic</string>
5202 </dict>
5203 </array>
5204 </dict>
5205 </array>
5206 </dict>
5207 </array>
5208 </dict>
5209 <key>commonmark-code-fenced-ini</key>
5210 <dict>
5211 <key>patterns</key>
5212 <array>
5213 <dict>
5214 <key>begin</key>
5215 <string>(?:^|\G)[\t ]*(`{3,})(?:[\t ]*((?i:altium|altium\x2ddesigner|dosini|(?:.*\.)?(?:cnf|dof|ini|lektorproject|outjob|pcbdoc|prefs|prjpcb|properties|schdoc|url)))(?:[\t ]+((?:[^\n\r`])+))?)(?:[\t ]*$)</string>
5216 <key>beginCaptures</key>
5217 <dict>
5218 <key>1</key>
5219 <dict>
5220 <key>name</key>
5221 <string>string.other.begin.code.fenced.mdx</string>
5222 </dict>
5223 <key>2</key>
5224 <dict>
5225 <key>name</key>
5226 <string>entity.name.function.mdx</string>
5227 <key>patterns</key>
5228 <array>
5229 <dict>
5230 <key>include</key>
5231 <string>#markdown-string</string>
5232 </dict>
5233 </array>
5234 </dict>
5235 <key>3</key>
5236 <dict>
5237 <key>patterns</key>
5238 <array>
5239 <dict>
5240 <key>include</key>
5241 <string>#markdown-string</string>
5242 </dict>
5243 </array>
5244 </dict>
5245 </dict>
5246 <key>end</key>
5247 <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string>
5248 <key>endCaptures</key>
5249 <dict>
5250 <key>1</key>
5251 <dict>
5252 <key>name</key>
5253 <string>string.other.end.code.fenced.mdx</string>
5254 </dict>
5255 </dict>
5256 <key>name</key>
5257 <string>markup.code.ini.mdx</string>
5258 <key>patterns</key>
5259 <array>
5260 <dict>
5261 <key>begin</key>
5262 <string>(^|\G)(\s*)(.*)</string>
5263 <key>while</key>
5264 <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string>
5265 <key>contentName</key>
5266 <string>meta.embedded.ini</string>
5267 <key>patterns</key>
5268 <array>
5269 <dict>
5270 <key>include</key>
5271 <string>source.ini</string>
5272 </dict>
5273 </array>
5274 </dict>
5275 </array>
5276 </dict>
5277 <dict>
5278 <key>begin</key>
5279 <string>(?:^|\G)[\t ]*(~{3,})(?:[\t ]*((?i:altium|altium\x2ddesigner|dosini|(?:.*\.)?(?:cnf|dof|ini|lektorproject|outjob|pcbdoc|prefs|prjpcb|properties|schdoc|url)))(?:[\t ]+((?:[^\n\r])+))?)(?:[\t ]*$)</string>
5280 <key>beginCaptures</key>
5281 <dict>
5282 <key>1</key>
5283 <dict>
5284 <key>name</key>
5285 <string>string.other.begin.code.fenced.mdx</string>
5286 </dict>
5287 <key>2</key>
5288 <dict>
5289 <key>name</key>
5290 <string>entity.name.function.mdx</string>
5291 <key>patterns</key>
5292 <array>
5293 <dict>
5294 <key>include</key>
5295 <string>#markdown-string</string>
5296 </dict>
5297 </array>
5298 </dict>
5299 <key>3</key>
5300 <dict>
5301 <key>patterns</key>
5302 <array>
5303 <dict>
5304 <key>include</key>
5305 <string>#markdown-string</string>
5306 </dict>
5307 </array>
5308 </dict>
5309 </dict>
5310 <key>end</key>
5311 <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string>
5312 <key>endCaptures</key>
5313 <dict>
5314 <key>1</key>
5315 <dict>
5316 <key>name</key>
5317 <string>string.other.end.code.fenced.mdx</string>
5318 </dict>
5319 </dict>
5320 <key>name</key>
5321 <string>markup.code.ini.mdx</string>
5322 <key>patterns</key>
5323 <array>
5324 <dict>
5325 <key>begin</key>
5326 <string>(^|\G)(\s*)(.*)</string>
5327 <key>while</key>
5328 <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string>
5329 <key>contentName</key>
5330 <string>meta.embedded.ini</string>
5331 <key>patterns</key>
5332 <array>
5333 <dict>
5334 <key>include</key>
5335 <string>source.ini</string>
5336 </dict>
5337 </array>
5338 </dict>
5339 </array>
5340 </dict>
5341 </array>
5342 </dict>
5343 <key>commonmark-code-fenced-java</key>
5344 <dict>
5345 <key>patterns</key>
5346 <array>
5347 <dict>
5348 <key>begin</key>
5349 <string>(?:^|\G)[\t ]*(`{3,})(?:[\t ]*((?i:chuck|unrealscript|(?:.*\.)?(?:ck|jav|java|jsh|uc)))(?:[\t ]+((?:[^\n\r`])+))?)(?:[\t ]*$)</string>
5350 <key>beginCaptures</key>
5351 <dict>
5352 <key>1</key>
5353 <dict>
5354 <key>name</key>
5355 <string>string.other.begin.code.fenced.mdx</string>
5356 </dict>
5357 <key>2</key>
5358 <dict>
5359 <key>name</key>
5360 <string>entity.name.function.mdx</string>
5361 <key>patterns</key>
5362 <array>
5363 <dict>
5364 <key>include</key>
5365 <string>#markdown-string</string>
5366 </dict>
5367 </array>
5368 </dict>
5369 <key>3</key>
5370 <dict>
5371 <key>patterns</key>
5372 <array>
5373 <dict>
5374 <key>include</key>
5375 <string>#markdown-string</string>
5376 </dict>
5377 </array>
5378 </dict>
5379 </dict>
5380 <key>end</key>
5381 <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string>
5382 <key>endCaptures</key>
5383 <dict>
5384 <key>1</key>
5385 <dict>
5386 <key>name</key>
5387 <string>string.other.end.code.fenced.mdx</string>
5388 </dict>
5389 </dict>
5390 <key>name</key>
5391 <string>markup.code.java.mdx</string>
5392 <key>patterns</key>
5393 <array>
5394 <dict>
5395 <key>begin</key>
5396 <string>(^|\G)(\s*)(.*)</string>
5397 <key>while</key>
5398 <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string>
5399 <key>contentName</key>
5400 <string>meta.embedded.java</string>
5401 <key>patterns</key>
5402 <array>
5403 <dict>
5404 <key>include</key>
5405 <string>source.java</string>
5406 </dict>
5407 </array>
5408 </dict>
5409 </array>
5410 </dict>
5411 <dict>
5412 <key>begin</key>
5413 <string>(?:^|\G)[\t ]*(~{3,})(?:[\t ]*((?i:chuck|unrealscript|(?:.*\.)?(?:ck|jav|java|jsh|uc)))(?:[\t ]+((?:[^\n\r])+))?)(?:[\t ]*$)</string>
5414 <key>beginCaptures</key>
5415 <dict>
5416 <key>1</key>
5417 <dict>
5418 <key>name</key>
5419 <string>string.other.begin.code.fenced.mdx</string>
5420 </dict>
5421 <key>2</key>
5422 <dict>
5423 <key>name</key>
5424 <string>entity.name.function.mdx</string>
5425 <key>patterns</key>
5426 <array>
5427 <dict>
5428 <key>include</key>
5429 <string>#markdown-string</string>
5430 </dict>
5431 </array>
5432 </dict>
5433 <key>3</key>
5434 <dict>
5435 <key>patterns</key>
5436 <array>
5437 <dict>
5438 <key>include</key>
5439 <string>#markdown-string</string>
5440 </dict>
5441 </array>
5442 </dict>
5443 </dict>
5444 <key>end</key>
5445 <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string>
5446 <key>endCaptures</key>
5447 <dict>
5448 <key>1</key>
5449 <dict>
5450 <key>name</key>
5451 <string>string.other.end.code.fenced.mdx</string>
5452 </dict>
5453 </dict>
5454 <key>name</key>
5455 <string>markup.code.java.mdx</string>
5456 <key>patterns</key>
5457 <array>
5458 <dict>
5459 <key>begin</key>
5460 <string>(^|\G)(\s*)(.*)</string>
5461 <key>while</key>
5462 <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string>
5463 <key>contentName</key>
5464 <string>meta.embedded.java</string>
5465 <key>patterns</key>
5466 <array>
5467 <dict>
5468 <key>include</key>
5469 <string>source.java</string>
5470 </dict>
5471 </array>
5472 </dict>
5473 </array>
5474 </dict>
5475 </array>
5476 </dict>
5477 <key>commonmark-code-fenced-js</key>
5478 <dict>
5479 <key>patterns</key>
5480 <array>
5481 <dict>
5482 <key>begin</key>
5483 <string>(?:^|\G)[\t ]*(`{3,})(?:[\t ]*((?i:cycript|javascript\+erb|json\x2dwith\x2dcomments|node|qt\x2dscript|(?:.*\.)?(?:_js|bones|cjs|code\x2dsnippets|code\x2dworkspace|cy|es6|jake|javascript|js|js\.erb|jsb|jscad|jsfl|jslib|jsm|json5|jsonc|jsonld|jspre|jss|jsx|mjs|njs|pac|sjs|ssjs|sublime\x2dbuild|sublime\x2dcolor\x2dscheme|sublime\x2dcommands|sublime\x2dcompletions|sublime\x2dkeymap|sublime\x2dmacro|sublime\x2dmenu|sublime\x2dmousemap|sublime\x2dproject|sublime\x2dsettings|sublime\x2dtheme|sublime\x2dworkspace|sublime_metrics|sublime_session|xsjs|xsjslib)))(?:[\t ]+((?:[^\n\r`])+))?)(?:[\t ]*$)</string>
5484 <key>beginCaptures</key>
5485 <dict>
5486 <key>1</key>
5487 <dict>
5488 <key>name</key>
5489 <string>string.other.begin.code.fenced.mdx</string>
5490 </dict>
5491 <key>2</key>
5492 <dict>
5493 <key>name</key>
5494 <string>entity.name.function.mdx</string>
5495 <key>patterns</key>
5496 <array>
5497 <dict>
5498 <key>include</key>
5499 <string>#markdown-string</string>
5500 </dict>
5501 </array>
5502 </dict>
5503 <key>3</key>
5504 <dict>
5505 <key>patterns</key>
5506 <array>
5507 <dict>
5508 <key>include</key>
5509 <string>#markdown-string</string>
5510 </dict>
5511 </array>
5512 </dict>
5513 </dict>
5514 <key>end</key>
5515 <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string>
5516 <key>endCaptures</key>
5517 <dict>
5518 <key>1</key>
5519 <dict>
5520 <key>name</key>
5521 <string>string.other.end.code.fenced.mdx</string>
5522 </dict>
5523 </dict>
5524 <key>name</key>
5525 <string>markup.code.js.mdx</string>
5526 <key>patterns</key>
5527 <array>
5528 <dict>
5529 <key>begin</key>
5530 <string>(^|\G)(\s*)(.*)</string>
5531 <key>while</key>
5532 <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string>
5533 <key>contentName</key>
5534 <string>meta.embedded.js</string>
5535 <key>patterns</key>
5536 <array>
5537 <dict>
5538 <key>include</key>
5539 <string>source.js</string>
5540 </dict>
5541 </array>
5542 </dict>
5543 </array>
5544 </dict>
5545 <dict>
5546 <key>begin</key>
5547 <string>(?:^|\G)[\t ]*(~{3,})(?:[\t ]*((?i:cycript|javascript\+erb|json\x2dwith\x2dcomments|node|qt\x2dscript|(?:.*\.)?(?:_js|bones|cjs|code\x2dsnippets|code\x2dworkspace|cy|es6|jake|javascript|js|js\.erb|jsb|jscad|jsfl|jslib|jsm|json5|jsonc|jsonld|jspre|jss|jsx|mjs|njs|pac|sjs|ssjs|sublime\x2dbuild|sublime\x2dcolor\x2dscheme|sublime\x2dcommands|sublime\x2dcompletions|sublime\x2dkeymap|sublime\x2dmacro|sublime\x2dmenu|sublime\x2dmousemap|sublime\x2dproject|sublime\x2dsettings|sublime\x2dtheme|sublime\x2dworkspace|sublime_metrics|sublime_session|xsjs|xsjslib)))(?:[\t ]+((?:[^\n\r])+))?)(?:[\t ]*$)</string>
5548 <key>beginCaptures</key>
5549 <dict>
5550 <key>1</key>
5551 <dict>
5552 <key>name</key>
5553 <string>string.other.begin.code.fenced.mdx</string>
5554 </dict>
5555 <key>2</key>
5556 <dict>
5557 <key>name</key>
5558 <string>entity.name.function.mdx</string>
5559 <key>patterns</key>
5560 <array>
5561 <dict>
5562 <key>include</key>
5563 <string>#markdown-string</string>
5564 </dict>
5565 </array>
5566 </dict>
5567 <key>3</key>
5568 <dict>
5569 <key>patterns</key>
5570 <array>
5571 <dict>
5572 <key>include</key>
5573 <string>#markdown-string</string>
5574 </dict>
5575 </array>
5576 </dict>
5577 </dict>
5578 <key>end</key>
5579 <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string>
5580 <key>endCaptures</key>
5581 <dict>
5582 <key>1</key>
5583 <dict>
5584 <key>name</key>
5585 <string>string.other.end.code.fenced.mdx</string>
5586 </dict>
5587 </dict>
5588 <key>name</key>
5589 <string>markup.code.js.mdx</string>
5590 <key>patterns</key>
5591 <array>
5592 <dict>
5593 <key>begin</key>
5594 <string>(^|\G)(\s*)(.*)</string>
5595 <key>while</key>
5596 <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string>
5597 <key>contentName</key>
5598 <string>meta.embedded.js</string>
5599 <key>patterns</key>
5600 <array>
5601 <dict>
5602 <key>include</key>
5603 <string>source.js</string>
5604 </dict>
5605 </array>
5606 </dict>
5607 </array>
5608 </dict>
5609 </array>
5610 </dict>
5611 <key>commonmark-code-fenced-json</key>
5612 <dict>
5613 <key>patterns</key>
5614 <array>
5615 <dict>
5616 <key>begin</key>
5617 <string>(?:^|\G)[\t ]*(`{3,})(?:[\t ]*((?i:ecere\x2dprojects|ipython\x2dnotebook|jupyter\x2dnotebook|max|max/msp|maxmsp|oasv2\x2djson|oasv3\x2djson|(?:.*\.)?(?:4dform|4dproject|avsc|epj|geojson|gltf|har|ice|ipynb|json|json|json|json\x2dtmlanguage|jsonl|maxhelp|maxpat|maxproj|mcmeta|mxt|pat|sarif|tfstate|tfstate\.backup|topojson|webapp|webmanifest|yy|yyp)))(?:[\t ]+((?:[^\n\r`])+))?)(?:[\t ]*$)</string>
5618 <key>beginCaptures</key>
5619 <dict>
5620 <key>1</key>
5621 <dict>
5622 <key>name</key>
5623 <string>string.other.begin.code.fenced.mdx</string>
5624 </dict>
5625 <key>2</key>
5626 <dict>
5627 <key>name</key>
5628 <string>entity.name.function.mdx</string>
5629 <key>patterns</key>
5630 <array>
5631 <dict>
5632 <key>include</key>
5633 <string>#markdown-string</string>
5634 </dict>
5635 </array>
5636 </dict>
5637 <key>3</key>
5638 <dict>
5639 <key>patterns</key>
5640 <array>
5641 <dict>
5642 <key>include</key>
5643 <string>#markdown-string</string>
5644 </dict>
5645 </array>
5646 </dict>
5647 </dict>
5648 <key>end</key>
5649 <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string>
5650 <key>endCaptures</key>
5651 <dict>
5652 <key>1</key>
5653 <dict>
5654 <key>name</key>
5655 <string>string.other.end.code.fenced.mdx</string>
5656 </dict>
5657 </dict>
5658 <key>name</key>
5659 <string>markup.code.json.mdx</string>
5660 <key>patterns</key>
5661 <array>
5662 <dict>
5663 <key>begin</key>
5664 <string>(^|\G)(\s*)(.*)</string>
5665 <key>while</key>
5666 <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string>
5667 <key>contentName</key>
5668 <string>meta.embedded.json</string>
5669 <key>patterns</key>
5670 <array>
5671 <dict>
5672 <key>include</key>
5673 <string>source.json</string>
5674 </dict>
5675 </array>
5676 </dict>
5677 </array>
5678 </dict>
5679 <dict>
5680 <key>begin</key>
5681 <string>(?:^|\G)[\t ]*(~{3,})(?:[\t ]*((?i:ecere\x2dprojects|ipython\x2dnotebook|jupyter\x2dnotebook|max|max/msp|maxmsp|oasv2\x2djson|oasv3\x2djson|(?:.*\.)?(?:4dform|4dproject|avsc|epj|geojson|gltf|har|ice|ipynb|json|json|json|json\x2dtmlanguage|jsonl|maxhelp|maxpat|maxproj|mcmeta|mxt|pat|sarif|tfstate|tfstate\.backup|topojson|webapp|webmanifest|yy|yyp)))(?:[\t ]+((?:[^\n\r])+))?)(?:[\t ]*$)</string>
5682 <key>beginCaptures</key>
5683 <dict>
5684 <key>1</key>
5685 <dict>
5686 <key>name</key>
5687 <string>string.other.begin.code.fenced.mdx</string>
5688 </dict>
5689 <key>2</key>
5690 <dict>
5691 <key>name</key>
5692 <string>entity.name.function.mdx</string>
5693 <key>patterns</key>
5694 <array>
5695 <dict>
5696 <key>include</key>
5697 <string>#markdown-string</string>
5698 </dict>
5699 </array>
5700 </dict>
5701 <key>3</key>
5702 <dict>
5703 <key>patterns</key>
5704 <array>
5705 <dict>
5706 <key>include</key>
5707 <string>#markdown-string</string>
5708 </dict>
5709 </array>
5710 </dict>
5711 </dict>
5712 <key>end</key>
5713 <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string>
5714 <key>endCaptures</key>
5715 <dict>
5716 <key>1</key>
5717 <dict>
5718 <key>name</key>
5719 <string>string.other.end.code.fenced.mdx</string>
5720 </dict>
5721 </dict>
5722 <key>name</key>
5723 <string>markup.code.json.mdx</string>
5724 <key>patterns</key>
5725 <array>
5726 <dict>
5727 <key>begin</key>
5728 <string>(^|\G)(\s*)(.*)</string>
5729 <key>while</key>
5730 <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string>
5731 <key>contentName</key>
5732 <string>meta.embedded.json</string>
5733 <key>patterns</key>
5734 <array>
5735 <dict>
5736 <key>include</key>
5737 <string>source.json</string>
5738 </dict>
5739 </array>
5740 </dict>
5741 </array>
5742 </dict>
5743 </array>
5744 </dict>
5745 <key>commonmark-code-fenced-julia</key>
5746 <dict>
5747 <key>patterns</key>
5748 <array>
5749 <dict>
5750 <key>begin</key>
5751 <string>(?:^|\G)[\t ]*(`{3,})(?:[\t ]*((?i:julia|(?:.*\.)?jl))(?:[\t ]+((?:[^\n\r`])+))?)(?:[\t ]*$)</string>
5752 <key>beginCaptures</key>
5753 <dict>
5754 <key>1</key>
5755 <dict>
5756 <key>name</key>
5757 <string>string.other.begin.code.fenced.mdx</string>
5758 </dict>
5759 <key>2</key>
5760 <dict>
5761 <key>name</key>
5762 <string>entity.name.function.mdx</string>
5763 <key>patterns</key>
5764 <array>
5765 <dict>
5766 <key>include</key>
5767 <string>#markdown-string</string>
5768 </dict>
5769 </array>
5770 </dict>
5771 <key>3</key>
5772 <dict>
5773 <key>patterns</key>
5774 <array>
5775 <dict>
5776 <key>include</key>
5777 <string>#markdown-string</string>
5778 </dict>
5779 </array>
5780 </dict>
5781 </dict>
5782 <key>end</key>
5783 <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string>
5784 <key>endCaptures</key>
5785 <dict>
5786 <key>1</key>
5787 <dict>
5788 <key>name</key>
5789 <string>string.other.end.code.fenced.mdx</string>
5790 </dict>
5791 </dict>
5792 <key>name</key>
5793 <string>markup.code.julia.mdx</string>
5794 <key>patterns</key>
5795 <array>
5796 <dict>
5797 <key>begin</key>
5798 <string>(^|\G)(\s*)(.*)</string>
5799 <key>while</key>
5800 <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string>
5801 <key>contentName</key>
5802 <string>meta.embedded.julia</string>
5803 <key>patterns</key>
5804 <array>
5805 <dict>
5806 <key>include</key>
5807 <string>source.julia</string>
5808 </dict>
5809 </array>
5810 </dict>
5811 </array>
5812 </dict>
5813 <dict>
5814 <key>begin</key>
5815 <string>(?:^|\G)[\t ]*(~{3,})(?:[\t ]*((?i:julia|(?:.*\.)?jl))(?:[\t ]+((?:[^\n\r])+))?)(?:[\t ]*$)</string>
5816 <key>beginCaptures</key>
5817 <dict>
5818 <key>1</key>
5819 <dict>
5820 <key>name</key>
5821 <string>string.other.begin.code.fenced.mdx</string>
5822 </dict>
5823 <key>2</key>
5824 <dict>
5825 <key>name</key>
5826 <string>entity.name.function.mdx</string>
5827 <key>patterns</key>
5828 <array>
5829 <dict>
5830 <key>include</key>
5831 <string>#markdown-string</string>
5832 </dict>
5833 </array>
5834 </dict>
5835 <key>3</key>
5836 <dict>
5837 <key>patterns</key>
5838 <array>
5839 <dict>
5840 <key>include</key>
5841 <string>#markdown-string</string>
5842 </dict>
5843 </array>
5844 </dict>
5845 </dict>
5846 <key>end</key>
5847 <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string>
5848 <key>endCaptures</key>
5849 <dict>
5850 <key>1</key>
5851 <dict>
5852 <key>name</key>
5853 <string>string.other.end.code.fenced.mdx</string>
5854 </dict>
5855 </dict>
5856 <key>name</key>
5857 <string>markup.code.julia.mdx</string>
5858 <key>patterns</key>
5859 <array>
5860 <dict>
5861 <key>begin</key>
5862 <string>(^|\G)(\s*)(.*)</string>
5863 <key>while</key>
5864 <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string>
5865 <key>contentName</key>
5866 <string>meta.embedded.julia</string>
5867 <key>patterns</key>
5868 <array>
5869 <dict>
5870 <key>include</key>
5871 <string>source.julia</string>
5872 </dict>
5873 </array>
5874 </dict>
5875 </array>
5876 </dict>
5877 </array>
5878 </dict>
5879 <key>commonmark-code-fenced-kotlin</key>
5880 <dict>
5881 <key>patterns</key>
5882 <array>
5883 <dict>
5884 <key>begin</key>
5885 <string>(?:^|\G)[\t ]*(`{3,})(?:[\t ]*((?i:gradle\x2dkotlin\x2ddsl|kotlin|(?:.*\.)?(?:gradle\.kts|kt|ktm|kts)))(?:[\t ]+((?:[^\n\r`])+))?)(?:[\t ]*$)</string>
5886 <key>beginCaptures</key>
5887 <dict>
5888 <key>1</key>
5889 <dict>
5890 <key>name</key>
5891 <string>string.other.begin.code.fenced.mdx</string>
5892 </dict>
5893 <key>2</key>
5894 <dict>
5895 <key>name</key>
5896 <string>entity.name.function.mdx</string>
5897 <key>patterns</key>
5898 <array>
5899 <dict>
5900 <key>include</key>
5901 <string>#markdown-string</string>
5902 </dict>
5903 </array>
5904 </dict>
5905 <key>3</key>
5906 <dict>
5907 <key>patterns</key>
5908 <array>
5909 <dict>
5910 <key>include</key>
5911 <string>#markdown-string</string>
5912 </dict>
5913 </array>
5914 </dict>
5915 </dict>
5916 <key>end</key>
5917 <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string>
5918 <key>endCaptures</key>
5919 <dict>
5920 <key>1</key>
5921 <dict>
5922 <key>name</key>
5923 <string>string.other.end.code.fenced.mdx</string>
5924 </dict>
5925 </dict>
5926 <key>name</key>
5927 <string>markup.code.kotlin.mdx</string>
5928 <key>patterns</key>
5929 <array>
5930 <dict>
5931 <key>begin</key>
5932 <string>(^|\G)(\s*)(.*)</string>
5933 <key>while</key>
5934 <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string>
5935 <key>contentName</key>
5936 <string>meta.embedded.kotlin</string>
5937 <key>patterns</key>
5938 <array>
5939 <dict>
5940 <key>include</key>
5941 <string>source.kotlin</string>
5942 </dict>
5943 </array>
5944 </dict>
5945 </array>
5946 </dict>
5947 <dict>
5948 <key>begin</key>
5949 <string>(?:^|\G)[\t ]*(~{3,})(?:[\t ]*((?i:gradle\x2dkotlin\x2ddsl|kotlin|(?:.*\.)?(?:gradle\.kts|kt|ktm|kts)))(?:[\t ]+((?:[^\n\r])+))?)(?:[\t ]*$)</string>
5950 <key>beginCaptures</key>
5951 <dict>
5952 <key>1</key>
5953 <dict>
5954 <key>name</key>
5955 <string>string.other.begin.code.fenced.mdx</string>
5956 </dict>
5957 <key>2</key>
5958 <dict>
5959 <key>name</key>
5960 <string>entity.name.function.mdx</string>
5961 <key>patterns</key>
5962 <array>
5963 <dict>
5964 <key>include</key>
5965 <string>#markdown-string</string>
5966 </dict>
5967 </array>
5968 </dict>
5969 <key>3</key>
5970 <dict>
5971 <key>patterns</key>
5972 <array>
5973 <dict>
5974 <key>include</key>
5975 <string>#markdown-string</string>
5976 </dict>
5977 </array>
5978 </dict>
5979 </dict>
5980 <key>end</key>
5981 <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string>
5982 <key>endCaptures</key>
5983 <dict>
5984 <key>1</key>
5985 <dict>
5986 <key>name</key>
5987 <string>string.other.end.code.fenced.mdx</string>
5988 </dict>
5989 </dict>
5990 <key>name</key>
5991 <string>markup.code.kotlin.mdx</string>
5992 <key>patterns</key>
5993 <array>
5994 <dict>
5995 <key>begin</key>
5996 <string>(^|\G)(\s*)(.*)</string>
5997 <key>while</key>
5998 <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string>
5999 <key>contentName</key>
6000 <string>meta.embedded.kotlin</string>
6001 <key>patterns</key>
6002 <array>
6003 <dict>
6004 <key>include</key>
6005 <string>source.kotlin</string>
6006 </dict>
6007 </array>
6008 </dict>
6009 </array>
6010 </dict>
6011 </array>
6012 </dict>
6013 <key>commonmark-code-fenced-less</key>
6014 <dict>
6015 <key>patterns</key>
6016 <array>
6017 <dict>
6018 <key>begin</key>
6019 <string>(?:^|\G)[\t ]*(`{3,})(?:[\t ]*((?i:less\x2dcss|(?:.*\.)?less))(?:[\t ]+((?:[^\n\r`])+))?)(?:[\t ]*$)</string>
6020 <key>beginCaptures</key>
6021 <dict>
6022 <key>1</key>
6023 <dict>
6024 <key>name</key>
6025 <string>string.other.begin.code.fenced.mdx</string>
6026 </dict>
6027 <key>2</key>
6028 <dict>
6029 <key>name</key>
6030 <string>entity.name.function.mdx</string>
6031 <key>patterns</key>
6032 <array>
6033 <dict>
6034 <key>include</key>
6035 <string>#markdown-string</string>
6036 </dict>
6037 </array>
6038 </dict>
6039 <key>3</key>
6040 <dict>
6041 <key>patterns</key>
6042 <array>
6043 <dict>
6044 <key>include</key>
6045 <string>#markdown-string</string>
6046 </dict>
6047 </array>
6048 </dict>
6049 </dict>
6050 <key>end</key>
6051 <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string>
6052 <key>endCaptures</key>
6053 <dict>
6054 <key>1</key>
6055 <dict>
6056 <key>name</key>
6057 <string>string.other.end.code.fenced.mdx</string>
6058 </dict>
6059 </dict>
6060 <key>name</key>
6061 <string>markup.code.less.mdx</string>
6062 <key>patterns</key>
6063 <array>
6064 <dict>
6065 <key>begin</key>
6066 <string>(^|\G)(\s*)(.*)</string>
6067 <key>while</key>
6068 <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string>
6069 <key>contentName</key>
6070 <string>meta.embedded.less</string>
6071 <key>patterns</key>
6072 <array>
6073 <dict>
6074 <key>include</key>
6075 <string>source.css.less</string>
6076 </dict>
6077 </array>
6078 </dict>
6079 </array>
6080 </dict>
6081 <dict>
6082 <key>begin</key>
6083 <string>(?:^|\G)[\t ]*(~{3,})(?:[\t ]*((?i:less\x2dcss|(?:.*\.)?less))(?:[\t ]+((?:[^\n\r])+))?)(?:[\t ]*$)</string>
6084 <key>beginCaptures</key>
6085 <dict>
6086 <key>1</key>
6087 <dict>
6088 <key>name</key>
6089 <string>string.other.begin.code.fenced.mdx</string>
6090 </dict>
6091 <key>2</key>
6092 <dict>
6093 <key>name</key>
6094 <string>entity.name.function.mdx</string>
6095 <key>patterns</key>
6096 <array>
6097 <dict>
6098 <key>include</key>
6099 <string>#markdown-string</string>
6100 </dict>
6101 </array>
6102 </dict>
6103 <key>3</key>
6104 <dict>
6105 <key>patterns</key>
6106 <array>
6107 <dict>
6108 <key>include</key>
6109 <string>#markdown-string</string>
6110 </dict>
6111 </array>
6112 </dict>
6113 </dict>
6114 <key>end</key>
6115 <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string>
6116 <key>endCaptures</key>
6117 <dict>
6118 <key>1</key>
6119 <dict>
6120 <key>name</key>
6121 <string>string.other.end.code.fenced.mdx</string>
6122 </dict>
6123 </dict>
6124 <key>name</key>
6125 <string>markup.code.less.mdx</string>
6126 <key>patterns</key>
6127 <array>
6128 <dict>
6129 <key>begin</key>
6130 <string>(^|\G)(\s*)(.*)</string>
6131 <key>while</key>
6132 <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string>
6133 <key>contentName</key>
6134 <string>meta.embedded.less</string>
6135 <key>patterns</key>
6136 <array>
6137 <dict>
6138 <key>include</key>
6139 <string>source.css.less</string>
6140 </dict>
6141 </array>
6142 </dict>
6143 </array>
6144 </dict>
6145 </array>
6146 </dict>
6147 <key>commonmark-code-fenced-lua</key>
6148 <dict>
6149 <key>patterns</key>
6150 <array>
6151 <dict>
6152 <key>begin</key>
6153 <string>(?:^|\G)[\t ]*(`{3,})(?:[\t ]*((?i:(?:.*\.)?(?:fcgi|lua|nse|p8|pd_lua|rbxs|rockspec|wlua)))(?:[\t ]+((?:[^\n\r`])+))?)(?:[\t ]*$)</string>
6154 <key>beginCaptures</key>
6155 <dict>
6156 <key>1</key>
6157 <dict>
6158 <key>name</key>
6159 <string>string.other.begin.code.fenced.mdx</string>
6160 </dict>
6161 <key>2</key>
6162 <dict>
6163 <key>name</key>
6164 <string>entity.name.function.mdx</string>
6165 <key>patterns</key>
6166 <array>
6167 <dict>
6168 <key>include</key>
6169 <string>#markdown-string</string>
6170 </dict>
6171 </array>
6172 </dict>
6173 <key>3</key>
6174 <dict>
6175 <key>patterns</key>
6176 <array>
6177 <dict>
6178 <key>include</key>
6179 <string>#markdown-string</string>
6180 </dict>
6181 </array>
6182 </dict>
6183 </dict>
6184 <key>end</key>
6185 <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string>
6186 <key>endCaptures</key>
6187 <dict>
6188 <key>1</key>
6189 <dict>
6190 <key>name</key>
6191 <string>string.other.end.code.fenced.mdx</string>
6192 </dict>
6193 </dict>
6194 <key>name</key>
6195 <string>markup.code.lua.mdx</string>
6196 <key>patterns</key>
6197 <array>
6198 <dict>
6199 <key>begin</key>
6200 <string>(^|\G)(\s*)(.*)</string>
6201 <key>while</key>
6202 <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string>
6203 <key>contentName</key>
6204 <string>meta.embedded.lua</string>
6205 <key>patterns</key>
6206 <array>
6207 <dict>
6208 <key>include</key>
6209 <string>source.lua</string>
6210 </dict>
6211 </array>
6212 </dict>
6213 </array>
6214 </dict>
6215 <dict>
6216 <key>begin</key>
6217 <string>(?:^|\G)[\t ]*(~{3,})(?:[\t ]*((?i:(?:.*\.)?(?:fcgi|lua|nse|p8|pd_lua|rbxs|rockspec|wlua)))(?:[\t ]+((?:[^\n\r])+))?)(?:[\t ]*$)</string>
6218 <key>beginCaptures</key>
6219 <dict>
6220 <key>1</key>
6221 <dict>
6222 <key>name</key>
6223 <string>string.other.begin.code.fenced.mdx</string>
6224 </dict>
6225 <key>2</key>
6226 <dict>
6227 <key>name</key>
6228 <string>entity.name.function.mdx</string>
6229 <key>patterns</key>
6230 <array>
6231 <dict>
6232 <key>include</key>
6233 <string>#markdown-string</string>
6234 </dict>
6235 </array>
6236 </dict>
6237 <key>3</key>
6238 <dict>
6239 <key>patterns</key>
6240 <array>
6241 <dict>
6242 <key>include</key>
6243 <string>#markdown-string</string>
6244 </dict>
6245 </array>
6246 </dict>
6247 </dict>
6248 <key>end</key>
6249 <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string>
6250 <key>endCaptures</key>
6251 <dict>
6252 <key>1</key>
6253 <dict>
6254 <key>name</key>
6255 <string>string.other.end.code.fenced.mdx</string>
6256 </dict>
6257 </dict>
6258 <key>name</key>
6259 <string>markup.code.lua.mdx</string>
6260 <key>patterns</key>
6261 <array>
6262 <dict>
6263 <key>begin</key>
6264 <string>(^|\G)(\s*)(.*)</string>
6265 <key>while</key>
6266 <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string>
6267 <key>contentName</key>
6268 <string>meta.embedded.lua</string>
6269 <key>patterns</key>
6270 <array>
6271 <dict>
6272 <key>include</key>
6273 <string>source.lua</string>
6274 </dict>
6275 </array>
6276 </dict>
6277 </array>
6278 </dict>
6279 </array>
6280 </dict>
6281 <key>commonmark-code-fenced-makefile</key>
6282 <dict>
6283 <key>patterns</key>
6284 <array>
6285 <dict>
6286 <key>begin</key>
6287 <string>(?:^|\G)[\t ]*(`{3,})(?:[\t ]*((?i:bsdmake|mf|(?:.*\.)?(?:mak|make|makefile|mk|mkfile)))(?:[\t ]+((?:[^\n\r`])+))?)(?:[\t ]*$)</string>
6288 <key>beginCaptures</key>
6289 <dict>
6290 <key>1</key>
6291 <dict>
6292 <key>name</key>
6293 <string>string.other.begin.code.fenced.mdx</string>
6294 </dict>
6295 <key>2</key>
6296 <dict>
6297 <key>name</key>
6298 <string>entity.name.function.mdx</string>
6299 <key>patterns</key>
6300 <array>
6301 <dict>
6302 <key>include</key>
6303 <string>#markdown-string</string>
6304 </dict>
6305 </array>
6306 </dict>
6307 <key>3</key>
6308 <dict>
6309 <key>patterns</key>
6310 <array>
6311 <dict>
6312 <key>include</key>
6313 <string>#markdown-string</string>
6314 </dict>
6315 </array>
6316 </dict>
6317 </dict>
6318 <key>end</key>
6319 <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string>
6320 <key>endCaptures</key>
6321 <dict>
6322 <key>1</key>
6323 <dict>
6324 <key>name</key>
6325 <string>string.other.end.code.fenced.mdx</string>
6326 </dict>
6327 </dict>
6328 <key>name</key>
6329 <string>markup.code.makefile.mdx</string>
6330 <key>patterns</key>
6331 <array>
6332 <dict>
6333 <key>begin</key>
6334 <string>(^|\G)(\s*)(.*)</string>
6335 <key>while</key>
6336 <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string>
6337 <key>contentName</key>
6338 <string>meta.embedded.makefile</string>
6339 <key>patterns</key>
6340 <array>
6341 <dict>
6342 <key>include</key>
6343 <string>source.makefile</string>
6344 </dict>
6345 </array>
6346 </dict>
6347 </array>
6348 </dict>
6349 <dict>
6350 <key>begin</key>
6351 <string>(?:^|\G)[\t ]*(~{3,})(?:[\t ]*((?i:bsdmake|mf|(?:.*\.)?(?:mak|make|makefile|mk|mkfile)))(?:[\t ]+((?:[^\n\r])+))?)(?:[\t ]*$)</string>
6352 <key>beginCaptures</key>
6353 <dict>
6354 <key>1</key>
6355 <dict>
6356 <key>name</key>
6357 <string>string.other.begin.code.fenced.mdx</string>
6358 </dict>
6359 <key>2</key>
6360 <dict>
6361 <key>name</key>
6362 <string>entity.name.function.mdx</string>
6363 <key>patterns</key>
6364 <array>
6365 <dict>
6366 <key>include</key>
6367 <string>#markdown-string</string>
6368 </dict>
6369 </array>
6370 </dict>
6371 <key>3</key>
6372 <dict>
6373 <key>patterns</key>
6374 <array>
6375 <dict>
6376 <key>include</key>
6377 <string>#markdown-string</string>
6378 </dict>
6379 </array>
6380 </dict>
6381 </dict>
6382 <key>end</key>
6383 <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string>
6384 <key>endCaptures</key>
6385 <dict>
6386 <key>1</key>
6387 <dict>
6388 <key>name</key>
6389 <string>string.other.end.code.fenced.mdx</string>
6390 </dict>
6391 </dict>
6392 <key>name</key>
6393 <string>markup.code.makefile.mdx</string>
6394 <key>patterns</key>
6395 <array>
6396 <dict>
6397 <key>begin</key>
6398 <string>(^|\G)(\s*)(.*)</string>
6399 <key>while</key>
6400 <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string>
6401 <key>contentName</key>
6402 <string>meta.embedded.makefile</string>
6403 <key>patterns</key>
6404 <array>
6405 <dict>
6406 <key>include</key>
6407 <string>source.makefile</string>
6408 </dict>
6409 </array>
6410 </dict>
6411 </array>
6412 </dict>
6413 </array>
6414 </dict>
6415 <key>commonmark-code-fenced-md</key>
6416 <dict>
6417 <key>patterns</key>
6418 <array>
6419 <dict>
6420 <key>begin</key>
6421 <string>(?:^|\G)[\t ]*(`{3,})(?:[\t ]*((?i:md|pandoc|rmarkdown|(?:.*\.)?(?:livemd|markdown|mdown|mdwn|mkd|mkdn|mkdown|qmd|rmd|ronn|scd|workbook)))(?:[\t ]+((?:[^\n\r`])+))?)(?:[\t ]*$)</string>
6422 <key>beginCaptures</key>
6423 <dict>
6424 <key>1</key>
6425 <dict>
6426 <key>name</key>
6427 <string>string.other.begin.code.fenced.mdx</string>
6428 </dict>
6429 <key>2</key>
6430 <dict>
6431 <key>name</key>
6432 <string>entity.name.function.mdx</string>
6433 <key>patterns</key>
6434 <array>
6435 <dict>
6436 <key>include</key>
6437 <string>#markdown-string</string>
6438 </dict>
6439 </array>
6440 </dict>
6441 <key>3</key>
6442 <dict>
6443 <key>patterns</key>
6444 <array>
6445 <dict>
6446 <key>include</key>
6447 <string>#markdown-string</string>
6448 </dict>
6449 </array>
6450 </dict>
6451 </dict>
6452 <key>end</key>
6453 <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string>
6454 <key>endCaptures</key>
6455 <dict>
6456 <key>1</key>
6457 <dict>
6458 <key>name</key>
6459 <string>string.other.end.code.fenced.mdx</string>
6460 </dict>
6461 </dict>
6462 <key>name</key>
6463 <string>markup.code.md.mdx</string>
6464 <key>patterns</key>
6465 <array>
6466 <dict>
6467 <key>begin</key>
6468 <string>(^|\G)(\s*)(.*)</string>
6469 <key>while</key>
6470 <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string>
6471 <key>contentName</key>
6472 <string>meta.embedded.md</string>
6473 <key>patterns</key>
6474 <array>
6475 <dict>
6476 <key>include</key>
6477 <string>text.md</string>
6478 </dict>
6479 <dict>
6480 <key>include</key>
6481 <string>source.gfm</string>
6482 </dict>
6483 <dict>
6484 <key>include</key>
6485 <string>text.html.markdown</string>
6486 </dict>
6487 </array>
6488 </dict>
6489 </array>
6490 </dict>
6491 <dict>
6492 <key>begin</key>
6493 <string>(?:^|\G)[\t ]*(~{3,})(?:[\t ]*((?i:md|pandoc|rmarkdown|(?:.*\.)?(?:livemd|markdown|mdown|mdwn|mkd|mkdn|mkdown|qmd|rmd|ronn|scd|workbook)))(?:[\t ]+((?:[^\n\r])+))?)(?:[\t ]*$)</string>
6494 <key>beginCaptures</key>
6495 <dict>
6496 <key>1</key>
6497 <dict>
6498 <key>name</key>
6499 <string>string.other.begin.code.fenced.mdx</string>
6500 </dict>
6501 <key>2</key>
6502 <dict>
6503 <key>name</key>
6504 <string>entity.name.function.mdx</string>
6505 <key>patterns</key>
6506 <array>
6507 <dict>
6508 <key>include</key>
6509 <string>#markdown-string</string>
6510 </dict>
6511 </array>
6512 </dict>
6513 <key>3</key>
6514 <dict>
6515 <key>patterns</key>
6516 <array>
6517 <dict>
6518 <key>include</key>
6519 <string>#markdown-string</string>
6520 </dict>
6521 </array>
6522 </dict>
6523 </dict>
6524 <key>end</key>
6525 <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string>
6526 <key>endCaptures</key>
6527 <dict>
6528 <key>1</key>
6529 <dict>
6530 <key>name</key>
6531 <string>string.other.end.code.fenced.mdx</string>
6532 </dict>
6533 </dict>
6534 <key>name</key>
6535 <string>markup.code.md.mdx</string>
6536 <key>patterns</key>
6537 <array>
6538 <dict>
6539 <key>begin</key>
6540 <string>(^|\G)(\s*)(.*)</string>
6541 <key>while</key>
6542 <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string>
6543 <key>contentName</key>
6544 <string>meta.embedded.md</string>
6545 <key>patterns</key>
6546 <array>
6547 <dict>
6548 <key>include</key>
6549 <string>text.md</string>
6550 </dict>
6551 <dict>
6552 <key>include</key>
6553 <string>source.gfm</string>
6554 </dict>
6555 <dict>
6556 <key>include</key>
6557 <string>text.html.markdown</string>
6558 </dict>
6559 </array>
6560 </dict>
6561 </array>
6562 </dict>
6563 </array>
6564 </dict>
6565 <key>commonmark-code-fenced-mdx</key>
6566 <dict>
6567 <key>patterns</key>
6568 <array>
6569 <dict>
6570 <key>begin</key>
6571 <string>(?:^|\G)[\t ]*(`{3,})(?:[\t ]*((?i:(?:.*\.)?mdx))(?:[\t ]+((?:[^\n\r`])+))?)(?:[\t ]*$)</string>
6572 <key>beginCaptures</key>
6573 <dict>
6574 <key>1</key>
6575 <dict>
6576 <key>name</key>
6577 <string>string.other.begin.code.fenced.mdx</string>
6578 </dict>
6579 <key>2</key>
6580 <dict>
6581 <key>name</key>
6582 <string>entity.name.function.mdx</string>
6583 <key>patterns</key>
6584 <array>
6585 <dict>
6586 <key>include</key>
6587 <string>#markdown-string</string>
6588 </dict>
6589 </array>
6590 </dict>
6591 <key>3</key>
6592 <dict>
6593 <key>patterns</key>
6594 <array>
6595 <dict>
6596 <key>include</key>
6597 <string>#markdown-string</string>
6598 </dict>
6599 </array>
6600 </dict>
6601 </dict>
6602 <key>end</key>
6603 <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string>
6604 <key>endCaptures</key>
6605 <dict>
6606 <key>1</key>
6607 <dict>
6608 <key>name</key>
6609 <string>string.other.end.code.fenced.mdx</string>
6610 </dict>
6611 </dict>
6612 <key>name</key>
6613 <string>markup.code.mdx.mdx</string>
6614 <key>patterns</key>
6615 <array>
6616 <dict>
6617 <key>begin</key>
6618 <string>(^|\G)(\s*)(.*)</string>
6619 <key>while</key>
6620 <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string>
6621 <key>contentName</key>
6622 <string>meta.embedded.mdx</string>
6623 <key>patterns</key>
6624 <array>
6625 <dict>
6626 <key>include</key>
6627 <string>source.mdx</string>
6628 </dict>
6629 </array>
6630 </dict>
6631 </array>
6632 </dict>
6633 <dict>
6634 <key>begin</key>
6635 <string>(?:^|\G)[\t ]*(~{3,})(?:[\t ]*((?i:(?:.*\.)?mdx))(?:[\t ]+((?:[^\n\r])+))?)(?:[\t ]*$)</string>
6636 <key>beginCaptures</key>
6637 <dict>
6638 <key>1</key>
6639 <dict>
6640 <key>name</key>
6641 <string>string.other.begin.code.fenced.mdx</string>
6642 </dict>
6643 <key>2</key>
6644 <dict>
6645 <key>name</key>
6646 <string>entity.name.function.mdx</string>
6647 <key>patterns</key>
6648 <array>
6649 <dict>
6650 <key>include</key>
6651 <string>#markdown-string</string>
6652 </dict>
6653 </array>
6654 </dict>
6655 <key>3</key>
6656 <dict>
6657 <key>patterns</key>
6658 <array>
6659 <dict>
6660 <key>include</key>
6661 <string>#markdown-string</string>
6662 </dict>
6663 </array>
6664 </dict>
6665 </dict>
6666 <key>end</key>
6667 <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string>
6668 <key>endCaptures</key>
6669 <dict>
6670 <key>1</key>
6671 <dict>
6672 <key>name</key>
6673 <string>string.other.end.code.fenced.mdx</string>
6674 </dict>
6675 </dict>
6676 <key>name</key>
6677 <string>markup.code.mdx.mdx</string>
6678 <key>patterns</key>
6679 <array>
6680 <dict>
6681 <key>begin</key>
6682 <string>(^|\G)(\s*)(.*)</string>
6683 <key>while</key>
6684 <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string>
6685 <key>contentName</key>
6686 <string>meta.embedded.mdx</string>
6687 <key>patterns</key>
6688 <array>
6689 <dict>
6690 <key>include</key>
6691 <string>source.mdx</string>
6692 </dict>
6693 </array>
6694 </dict>
6695 </array>
6696 </dict>
6697 </array>
6698 </dict>
6699 <key>commonmark-code-fenced-objc</key>
6700 <dict>
6701 <key>patterns</key>
6702 <array>
6703 <dict>
6704 <key>begin</key>
6705 <string>(?:^|\G)[\t ]*(`{3,})(?:[\t ]*((?i:obj\x2dc|objc|objective\x2dc|objectivec))(?:[\t ]+((?:[^\n\r`])+))?)(?:[\t ]*$)</string>
6706 <key>beginCaptures</key>
6707 <dict>
6708 <key>1</key>
6709 <dict>
6710 <key>name</key>
6711 <string>string.other.begin.code.fenced.mdx</string>
6712 </dict>
6713 <key>2</key>
6714 <dict>
6715 <key>name</key>
6716 <string>entity.name.function.mdx</string>
6717 <key>patterns</key>
6718 <array>
6719 <dict>
6720 <key>include</key>
6721 <string>#markdown-string</string>
6722 </dict>
6723 </array>
6724 </dict>
6725 <key>3</key>
6726 <dict>
6727 <key>patterns</key>
6728 <array>
6729 <dict>
6730 <key>include</key>
6731 <string>#markdown-string</string>
6732 </dict>
6733 </array>
6734 </dict>
6735 </dict>
6736 <key>end</key>
6737 <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string>
6738 <key>endCaptures</key>
6739 <dict>
6740 <key>1</key>
6741 <dict>
6742 <key>name</key>
6743 <string>string.other.end.code.fenced.mdx</string>
6744 </dict>
6745 </dict>
6746 <key>name</key>
6747 <string>markup.code.objc.mdx</string>
6748 <key>patterns</key>
6749 <array>
6750 <dict>
6751 <key>begin</key>
6752 <string>(^|\G)(\s*)(.*)</string>
6753 <key>while</key>
6754 <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string>
6755 <key>contentName</key>
6756 <string>meta.embedded.objc</string>
6757 <key>patterns</key>
6758 <array>
6759 <dict>
6760 <key>include</key>
6761 <string>source.objc</string>
6762 </dict>
6763 </array>
6764 </dict>
6765 </array>
6766 </dict>
6767 <dict>
6768 <key>begin</key>
6769 <string>(?:^|\G)[\t ]*(~{3,})(?:[\t ]*((?i:obj\x2dc|objc|objective\x2dc|objectivec))(?:[\t ]+((?:[^\n\r])+))?)(?:[\t ]*$)</string>
6770 <key>beginCaptures</key>
6771 <dict>
6772 <key>1</key>
6773 <dict>
6774 <key>name</key>
6775 <string>string.other.begin.code.fenced.mdx</string>
6776 </dict>
6777 <key>2</key>
6778 <dict>
6779 <key>name</key>
6780 <string>entity.name.function.mdx</string>
6781 <key>patterns</key>
6782 <array>
6783 <dict>
6784 <key>include</key>
6785 <string>#markdown-string</string>
6786 </dict>
6787 </array>
6788 </dict>
6789 <key>3</key>
6790 <dict>
6791 <key>patterns</key>
6792 <array>
6793 <dict>
6794 <key>include</key>
6795 <string>#markdown-string</string>
6796 </dict>
6797 </array>
6798 </dict>
6799 </dict>
6800 <key>end</key>
6801 <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string>
6802 <key>endCaptures</key>
6803 <dict>
6804 <key>1</key>
6805 <dict>
6806 <key>name</key>
6807 <string>string.other.end.code.fenced.mdx</string>
6808 </dict>
6809 </dict>
6810 <key>name</key>
6811 <string>markup.code.objc.mdx</string>
6812 <key>patterns</key>
6813 <array>
6814 <dict>
6815 <key>begin</key>
6816 <string>(^|\G)(\s*)(.*)</string>
6817 <key>while</key>
6818 <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string>
6819 <key>contentName</key>
6820 <string>meta.embedded.objc</string>
6821 <key>patterns</key>
6822 <array>
6823 <dict>
6824 <key>include</key>
6825 <string>source.objc</string>
6826 </dict>
6827 </array>
6828 </dict>
6829 </array>
6830 </dict>
6831 </array>
6832 </dict>
6833 <key>commonmark-code-fenced-perl</key>
6834 <dict>
6835 <key>patterns</key>
6836 <array>
6837 <dict>
6838 <key>begin</key>
6839 <string>(?:^|\G)[\t ]*(`{3,})(?:[\t ]*((?i:cperl|(?:.*\.)?(?:cgi|perl|ph|pl|plx|pm|psgi|t)))(?:[\t ]+((?:[^\n\r`])+))?)(?:[\t ]*$)</string>
6840 <key>beginCaptures</key>
6841 <dict>
6842 <key>1</key>
6843 <dict>
6844 <key>name</key>
6845 <string>string.other.begin.code.fenced.mdx</string>
6846 </dict>
6847 <key>2</key>
6848 <dict>
6849 <key>name</key>
6850 <string>entity.name.function.mdx</string>
6851 <key>patterns</key>
6852 <array>
6853 <dict>
6854 <key>include</key>
6855 <string>#markdown-string</string>
6856 </dict>
6857 </array>
6858 </dict>
6859 <key>3</key>
6860 <dict>
6861 <key>patterns</key>
6862 <array>
6863 <dict>
6864 <key>include</key>
6865 <string>#markdown-string</string>
6866 </dict>
6867 </array>
6868 </dict>
6869 </dict>
6870 <key>end</key>
6871 <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string>
6872 <key>endCaptures</key>
6873 <dict>
6874 <key>1</key>
6875 <dict>
6876 <key>name</key>
6877 <string>string.other.end.code.fenced.mdx</string>
6878 </dict>
6879 </dict>
6880 <key>name</key>
6881 <string>markup.code.perl.mdx</string>
6882 <key>patterns</key>
6883 <array>
6884 <dict>
6885 <key>begin</key>
6886 <string>(^|\G)(\s*)(.*)</string>
6887 <key>while</key>
6888 <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string>
6889 <key>contentName</key>
6890 <string>meta.embedded.perl</string>
6891 <key>patterns</key>
6892 <array>
6893 <dict>
6894 <key>include</key>
6895 <string>source.perl</string>
6896 </dict>
6897 </array>
6898 </dict>
6899 </array>
6900 </dict>
6901 <dict>
6902 <key>begin</key>
6903 <string>(?:^|\G)[\t ]*(~{3,})(?:[\t ]*((?i:cperl|(?:.*\.)?(?:cgi|perl|ph|pl|plx|pm|psgi|t)))(?:[\t ]+((?:[^\n\r])+))?)(?:[\t ]*$)</string>
6904 <key>beginCaptures</key>
6905 <dict>
6906 <key>1</key>
6907 <dict>
6908 <key>name</key>
6909 <string>string.other.begin.code.fenced.mdx</string>
6910 </dict>
6911 <key>2</key>
6912 <dict>
6913 <key>name</key>
6914 <string>entity.name.function.mdx</string>
6915 <key>patterns</key>
6916 <array>
6917 <dict>
6918 <key>include</key>
6919 <string>#markdown-string</string>
6920 </dict>
6921 </array>
6922 </dict>
6923 <key>3</key>
6924 <dict>
6925 <key>patterns</key>
6926 <array>
6927 <dict>
6928 <key>include</key>
6929 <string>#markdown-string</string>
6930 </dict>
6931 </array>
6932 </dict>
6933 </dict>
6934 <key>end</key>
6935 <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string>
6936 <key>endCaptures</key>
6937 <dict>
6938 <key>1</key>
6939 <dict>
6940 <key>name</key>
6941 <string>string.other.end.code.fenced.mdx</string>
6942 </dict>
6943 </dict>
6944 <key>name</key>
6945 <string>markup.code.perl.mdx</string>
6946 <key>patterns</key>
6947 <array>
6948 <dict>
6949 <key>begin</key>
6950 <string>(^|\G)(\s*)(.*)</string>
6951 <key>while</key>
6952 <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string>
6953 <key>contentName</key>
6954 <string>meta.embedded.perl</string>
6955 <key>patterns</key>
6956 <array>
6957 <dict>
6958 <key>include</key>
6959 <string>source.perl</string>
6960 </dict>
6961 </array>
6962 </dict>
6963 </array>
6964 </dict>
6965 </array>
6966 </dict>
6967 <key>commonmark-code-fenced-php</key>
6968 <dict>
6969 <key>patterns</key>
6970 <array>
6971 <dict>
6972 <key>begin</key>
6973 <string>(?:^|\G)[\t ]*(`{3,})(?:[\t ]*((?i:html\+php|inc|php|(?:.*\.)?(?:aw|ctp|php3|php4|php5|phps|phpt|phtml)))(?:[\t ]+((?:[^\n\r`])+))?)(?:[\t ]*$)</string>
6974 <key>beginCaptures</key>
6975 <dict>
6976 <key>1</key>
6977 <dict>
6978 <key>name</key>
6979 <string>string.other.begin.code.fenced.mdx</string>
6980 </dict>
6981 <key>2</key>
6982 <dict>
6983 <key>name</key>
6984 <string>entity.name.function.mdx</string>
6985 <key>patterns</key>
6986 <array>
6987 <dict>
6988 <key>include</key>
6989 <string>#markdown-string</string>
6990 </dict>
6991 </array>
6992 </dict>
6993 <key>3</key>
6994 <dict>
6995 <key>patterns</key>
6996 <array>
6997 <dict>
6998 <key>include</key>
6999 <string>#markdown-string</string>
7000 </dict>
7001 </array>
7002 </dict>
7003 </dict>
7004 <key>end</key>
7005 <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string>
7006 <key>endCaptures</key>
7007 <dict>
7008 <key>1</key>
7009 <dict>
7010 <key>name</key>
7011 <string>string.other.end.code.fenced.mdx</string>
7012 </dict>
7013 </dict>
7014 <key>name</key>
7015 <string>markup.code.php.mdx</string>
7016 <key>patterns</key>
7017 <array>
7018 <dict>
7019 <key>begin</key>
7020 <string>(^|\G)(\s*)(.*)</string>
7021 <key>while</key>
7022 <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string>
7023 <key>contentName</key>
7024 <string>meta.embedded.php</string>
7025 <key>patterns</key>
7026 <array>
7027 <dict>
7028 <key>include</key>
7029 <string>text.html.php</string>
7030 </dict>
7031 </array>
7032 </dict>
7033 </array>
7034 </dict>
7035 <dict>
7036 <key>begin</key>
7037 <string>(?:^|\G)[\t ]*(~{3,})(?:[\t ]*((?i:html\+php|inc|php|(?:.*\.)?(?:aw|ctp|php3|php4|php5|phps|phpt|phtml)))(?:[\t ]+((?:[^\n\r])+))?)(?:[\t ]*$)</string>
7038 <key>beginCaptures</key>
7039 <dict>
7040 <key>1</key>
7041 <dict>
7042 <key>name</key>
7043 <string>string.other.begin.code.fenced.mdx</string>
7044 </dict>
7045 <key>2</key>
7046 <dict>
7047 <key>name</key>
7048 <string>entity.name.function.mdx</string>
7049 <key>patterns</key>
7050 <array>
7051 <dict>
7052 <key>include</key>
7053 <string>#markdown-string</string>
7054 </dict>
7055 </array>
7056 </dict>
7057 <key>3</key>
7058 <dict>
7059 <key>patterns</key>
7060 <array>
7061 <dict>
7062 <key>include</key>
7063 <string>#markdown-string</string>
7064 </dict>
7065 </array>
7066 </dict>
7067 </dict>
7068 <key>end</key>
7069 <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string>
7070 <key>endCaptures</key>
7071 <dict>
7072 <key>1</key>
7073 <dict>
7074 <key>name</key>
7075 <string>string.other.end.code.fenced.mdx</string>
7076 </dict>
7077 </dict>
7078 <key>name</key>
7079 <string>markup.code.php.mdx</string>
7080 <key>patterns</key>
7081 <array>
7082 <dict>
7083 <key>begin</key>
7084 <string>(^|\G)(\s*)(.*)</string>
7085 <key>while</key>
7086 <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string>
7087 <key>contentName</key>
7088 <string>meta.embedded.php</string>
7089 <key>patterns</key>
7090 <array>
7091 <dict>
7092 <key>include</key>
7093 <string>text.html.php</string>
7094 </dict>
7095 </array>
7096 </dict>
7097 </array>
7098 </dict>
7099 </array>
7100 </dict>
7101 <key>commonmark-code-fenced-python</key>
7102 <dict>
7103 <key>patterns</key>
7104 <array>
7105 <dict>
7106 <key>begin</key>
7107 <string>(?:^|\G)[\t ]*(`{3,})(?:[\t ]*((?i:bazel|easybuild|python|python3|rusthon|snakemake|starlark|xonsh|(?:.*\.)?(?:bzl|eb|gyp|gypi|lmi|py|py3|pyde|pyi|pyp|pyt|pyw|rpy|sage|sagews|smk|snakefile|spec|tac|wsgi|xpy|xsh)))(?:[\t ]+((?:[^\n\r`])+))?)(?:[\t ]*$)</string>
7108 <key>beginCaptures</key>
7109 <dict>
7110 <key>1</key>
7111 <dict>
7112 <key>name</key>
7113 <string>string.other.begin.code.fenced.mdx</string>
7114 </dict>
7115 <key>2</key>
7116 <dict>
7117 <key>name</key>
7118 <string>entity.name.function.mdx</string>
7119 <key>patterns</key>
7120 <array>
7121 <dict>
7122 <key>include</key>
7123 <string>#markdown-string</string>
7124 </dict>
7125 </array>
7126 </dict>
7127 <key>3</key>
7128 <dict>
7129 <key>patterns</key>
7130 <array>
7131 <dict>
7132 <key>include</key>
7133 <string>#markdown-string</string>
7134 </dict>
7135 </array>
7136 </dict>
7137 </dict>
7138 <key>end</key>
7139 <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string>
7140 <key>endCaptures</key>
7141 <dict>
7142 <key>1</key>
7143 <dict>
7144 <key>name</key>
7145 <string>string.other.end.code.fenced.mdx</string>
7146 </dict>
7147 </dict>
7148 <key>name</key>
7149 <string>markup.code.python.mdx</string>
7150 <key>patterns</key>
7151 <array>
7152 <dict>
7153 <key>begin</key>
7154 <string>(^|\G)(\s*)(.*)</string>
7155 <key>while</key>
7156 <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string>
7157 <key>contentName</key>
7158 <string>meta.embedded.python</string>
7159 <key>patterns</key>
7160 <array>
7161 <dict>
7162 <key>include</key>
7163 <string>source.python</string>
7164 </dict>
7165 </array>
7166 </dict>
7167 </array>
7168 </dict>
7169 <dict>
7170 <key>begin</key>
7171 <string>(?:^|\G)[\t ]*(~{3,})(?:[\t ]*((?i:bazel|easybuild|python|python3|rusthon|snakemake|starlark|xonsh|(?:.*\.)?(?:bzl|eb|gyp|gypi|lmi|py|py3|pyde|pyi|pyp|pyt|pyw|rpy|sage|sagews|smk|snakefile|spec|tac|wsgi|xpy|xsh)))(?:[\t ]+((?:[^\n\r])+))?)(?:[\t ]*$)</string>
7172 <key>beginCaptures</key>
7173 <dict>
7174 <key>1</key>
7175 <dict>
7176 <key>name</key>
7177 <string>string.other.begin.code.fenced.mdx</string>
7178 </dict>
7179 <key>2</key>
7180 <dict>
7181 <key>name</key>
7182 <string>entity.name.function.mdx</string>
7183 <key>patterns</key>
7184 <array>
7185 <dict>
7186 <key>include</key>
7187 <string>#markdown-string</string>
7188 </dict>
7189 </array>
7190 </dict>
7191 <key>3</key>
7192 <dict>
7193 <key>patterns</key>
7194 <array>
7195 <dict>
7196 <key>include</key>
7197 <string>#markdown-string</string>
7198 </dict>
7199 </array>
7200 </dict>
7201 </dict>
7202 <key>end</key>
7203 <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string>
7204 <key>endCaptures</key>
7205 <dict>
7206 <key>1</key>
7207 <dict>
7208 <key>name</key>
7209 <string>string.other.end.code.fenced.mdx</string>
7210 </dict>
7211 </dict>
7212 <key>name</key>
7213 <string>markup.code.python.mdx</string>
7214 <key>patterns</key>
7215 <array>
7216 <dict>
7217 <key>begin</key>
7218 <string>(^|\G)(\s*)(.*)</string>
7219 <key>while</key>
7220 <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string>
7221 <key>contentName</key>
7222 <string>meta.embedded.python</string>
7223 <key>patterns</key>
7224 <array>
7225 <dict>
7226 <key>include</key>
7227 <string>source.python</string>
7228 </dict>
7229 </array>
7230 </dict>
7231 </array>
7232 </dict>
7233 </array>
7234 </dict>
7235 <key>commonmark-code-fenced-r</key>
7236 <dict>
7237 <key>patterns</key>
7238 <array>
7239 <dict>
7240 <key>begin</key>
7241 <string>(?:^|\G)[\t ]*(`{3,})(?:[\t ]*((?i:rscript|splus|(?:.*\.)?(?:r|rd|rsx)))(?:[\t ]+((?:[^\n\r`])+))?)(?:[\t ]*$)</string>
7242 <key>beginCaptures</key>
7243 <dict>
7244 <key>1</key>
7245 <dict>
7246 <key>name</key>
7247 <string>string.other.begin.code.fenced.mdx</string>
7248 </dict>
7249 <key>2</key>
7250 <dict>
7251 <key>name</key>
7252 <string>entity.name.function.mdx</string>
7253 <key>patterns</key>
7254 <array>
7255 <dict>
7256 <key>include</key>
7257 <string>#markdown-string</string>
7258 </dict>
7259 </array>
7260 </dict>
7261 <key>3</key>
7262 <dict>
7263 <key>patterns</key>
7264 <array>
7265 <dict>
7266 <key>include</key>
7267 <string>#markdown-string</string>
7268 </dict>
7269 </array>
7270 </dict>
7271 </dict>
7272 <key>end</key>
7273 <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string>
7274 <key>endCaptures</key>
7275 <dict>
7276 <key>1</key>
7277 <dict>
7278 <key>name</key>
7279 <string>string.other.end.code.fenced.mdx</string>
7280 </dict>
7281 </dict>
7282 <key>name</key>
7283 <string>markup.code.r.mdx</string>
7284 <key>patterns</key>
7285 <array>
7286 <dict>
7287 <key>begin</key>
7288 <string>(^|\G)(\s*)(.*)</string>
7289 <key>while</key>
7290 <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string>
7291 <key>contentName</key>
7292 <string>meta.embedded.r</string>
7293 <key>patterns</key>
7294 <array>
7295 <dict>
7296 <key>include</key>
7297 <string>source.r</string>
7298 </dict>
7299 </array>
7300 </dict>
7301 </array>
7302 </dict>
7303 <dict>
7304 <key>begin</key>
7305 <string>(?:^|\G)[\t ]*(~{3,})(?:[\t ]*((?i:rscript|splus|(?:.*\.)?(?:r|rd|rsx)))(?:[\t ]+((?:[^\n\r])+))?)(?:[\t ]*$)</string>
7306 <key>beginCaptures</key>
7307 <dict>
7308 <key>1</key>
7309 <dict>
7310 <key>name</key>
7311 <string>string.other.begin.code.fenced.mdx</string>
7312 </dict>
7313 <key>2</key>
7314 <dict>
7315 <key>name</key>
7316 <string>entity.name.function.mdx</string>
7317 <key>patterns</key>
7318 <array>
7319 <dict>
7320 <key>include</key>
7321 <string>#markdown-string</string>
7322 </dict>
7323 </array>
7324 </dict>
7325 <key>3</key>
7326 <dict>
7327 <key>patterns</key>
7328 <array>
7329 <dict>
7330 <key>include</key>
7331 <string>#markdown-string</string>
7332 </dict>
7333 </array>
7334 </dict>
7335 </dict>
7336 <key>end</key>
7337 <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string>
7338 <key>endCaptures</key>
7339 <dict>
7340 <key>1</key>
7341 <dict>
7342 <key>name</key>
7343 <string>string.other.end.code.fenced.mdx</string>
7344 </dict>
7345 </dict>
7346 <key>name</key>
7347 <string>markup.code.r.mdx</string>
7348 <key>patterns</key>
7349 <array>
7350 <dict>
7351 <key>begin</key>
7352 <string>(^|\G)(\s*)(.*)</string>
7353 <key>while</key>
7354 <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string>
7355 <key>contentName</key>
7356 <string>meta.embedded.r</string>
7357 <key>patterns</key>
7358 <array>
7359 <dict>
7360 <key>include</key>
7361 <string>source.r</string>
7362 </dict>
7363 </array>
7364 </dict>
7365 </array>
7366 </dict>
7367 </array>
7368 </dict>
7369 <key>commonmark-code-fenced-raku</key>
7370 <dict>
7371 <key>patterns</key>
7372 <array>
7373 <dict>
7374 <key>begin</key>
7375 <string>(?:^|\G)[\t ]*(`{3,})(?:[\t ]*((?i:perl\x2d6|perl6|pod\x2d6|(?:.*\.)?(?:6pl|6pm|nqp|p6|p6l|p6m|pl6|pm6|pod|pod6|raku|rakumod)))(?:[\t ]+((?:[^\n\r`])+))?)(?:[\t ]*$)</string>
7376 <key>beginCaptures</key>
7377 <dict>
7378 <key>1</key>
7379 <dict>
7380 <key>name</key>
7381 <string>string.other.begin.code.fenced.mdx</string>
7382 </dict>
7383 <key>2</key>
7384 <dict>
7385 <key>name</key>
7386 <string>entity.name.function.mdx</string>
7387 <key>patterns</key>
7388 <array>
7389 <dict>
7390 <key>include</key>
7391 <string>#markdown-string</string>
7392 </dict>
7393 </array>
7394 </dict>
7395 <key>3</key>
7396 <dict>
7397 <key>patterns</key>
7398 <array>
7399 <dict>
7400 <key>include</key>
7401 <string>#markdown-string</string>
7402 </dict>
7403 </array>
7404 </dict>
7405 </dict>
7406 <key>end</key>
7407 <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string>
7408 <key>endCaptures</key>
7409 <dict>
7410 <key>1</key>
7411 <dict>
7412 <key>name</key>
7413 <string>string.other.end.code.fenced.mdx</string>
7414 </dict>
7415 </dict>
7416 <key>name</key>
7417 <string>markup.code.raku.mdx</string>
7418 <key>patterns</key>
7419 <array>
7420 <dict>
7421 <key>begin</key>
7422 <string>(^|\G)(\s*)(.*)</string>
7423 <key>while</key>
7424 <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string>
7425 <key>contentName</key>
7426 <string>meta.embedded.raku</string>
7427 <key>patterns</key>
7428 <array>
7429 <dict>
7430 <key>include</key>
7431 <string>source.raku</string>
7432 </dict>
7433 </array>
7434 </dict>
7435 </array>
7436 </dict>
7437 <dict>
7438 <key>begin</key>
7439 <string>(?:^|\G)[\t ]*(~{3,})(?:[\t ]*((?i:perl\x2d6|perl6|pod\x2d6|(?:.*\.)?(?:6pl|6pm|nqp|p6|p6l|p6m|pl6|pm6|pod|pod6|raku|rakumod)))(?:[\t ]+((?:[^\n\r])+))?)(?:[\t ]*$)</string>
7440 <key>beginCaptures</key>
7441 <dict>
7442 <key>1</key>
7443 <dict>
7444 <key>name</key>
7445 <string>string.other.begin.code.fenced.mdx</string>
7446 </dict>
7447 <key>2</key>
7448 <dict>
7449 <key>name</key>
7450 <string>entity.name.function.mdx</string>
7451 <key>patterns</key>
7452 <array>
7453 <dict>
7454 <key>include</key>
7455 <string>#markdown-string</string>
7456 </dict>
7457 </array>
7458 </dict>
7459 <key>3</key>
7460 <dict>
7461 <key>patterns</key>
7462 <array>
7463 <dict>
7464 <key>include</key>
7465 <string>#markdown-string</string>
7466 </dict>
7467 </array>
7468 </dict>
7469 </dict>
7470 <key>end</key>
7471 <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string>
7472 <key>endCaptures</key>
7473 <dict>
7474 <key>1</key>
7475 <dict>
7476 <key>name</key>
7477 <string>string.other.end.code.fenced.mdx</string>
7478 </dict>
7479 </dict>
7480 <key>name</key>
7481 <string>markup.code.raku.mdx</string>
7482 <key>patterns</key>
7483 <array>
7484 <dict>
7485 <key>begin</key>
7486 <string>(^|\G)(\s*)(.*)</string>
7487 <key>while</key>
7488 <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string>
7489 <key>contentName</key>
7490 <string>meta.embedded.raku</string>
7491 <key>patterns</key>
7492 <array>
7493 <dict>
7494 <key>include</key>
7495 <string>source.raku</string>
7496 </dict>
7497 </array>
7498 </dict>
7499 </array>
7500 </dict>
7501 </array>
7502 </dict>
7503 <key>commonmark-code-fenced-ruby</key>
7504 <dict>
7505 <key>patterns</key>
7506 <array>
7507 <dict>
7508 <key>begin</key>
7509 <string>(?:^|\G)[\t ]*(`{3,})(?:[\t ]*((?i:jruby|macruby|(?:.*\.)?(?:builder|druby|duby|eye|gemspec|god|jbuilder|mirah|mspec|pluginspec|podspec|prawn|rabl|rake|rb|rbi|rbuild|rbw|rbx|ru|ruby|thor|watchr)))(?:[\t ]+((?:[^\n\r`])+))?)(?:[\t ]*$)</string>
7510 <key>beginCaptures</key>
7511 <dict>
7512 <key>1</key>
7513 <dict>
7514 <key>name</key>
7515 <string>string.other.begin.code.fenced.mdx</string>
7516 </dict>
7517 <key>2</key>
7518 <dict>
7519 <key>name</key>
7520 <string>entity.name.function.mdx</string>
7521 <key>patterns</key>
7522 <array>
7523 <dict>
7524 <key>include</key>
7525 <string>#markdown-string</string>
7526 </dict>
7527 </array>
7528 </dict>
7529 <key>3</key>
7530 <dict>
7531 <key>patterns</key>
7532 <array>
7533 <dict>
7534 <key>include</key>
7535 <string>#markdown-string</string>
7536 </dict>
7537 </array>
7538 </dict>
7539 </dict>
7540 <key>end</key>
7541 <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string>
7542 <key>endCaptures</key>
7543 <dict>
7544 <key>1</key>
7545 <dict>
7546 <key>name</key>
7547 <string>string.other.end.code.fenced.mdx</string>
7548 </dict>
7549 </dict>
7550 <key>name</key>
7551 <string>markup.code.ruby.mdx</string>
7552 <key>patterns</key>
7553 <array>
7554 <dict>
7555 <key>begin</key>
7556 <string>(^|\G)(\s*)(.*)</string>
7557 <key>while</key>
7558 <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string>
7559 <key>contentName</key>
7560 <string>meta.embedded.ruby</string>
7561 <key>patterns</key>
7562 <array>
7563 <dict>
7564 <key>include</key>
7565 <string>source.ruby</string>
7566 </dict>
7567 </array>
7568 </dict>
7569 </array>
7570 </dict>
7571 <dict>
7572 <key>begin</key>
7573 <string>(?:^|\G)[\t ]*(~{3,})(?:[\t ]*((?i:jruby|macruby|(?:.*\.)?(?:builder|druby|duby|eye|gemspec|god|jbuilder|mirah|mspec|pluginspec|podspec|prawn|rabl|rake|rb|rbi|rbuild|rbw|rbx|ru|ruby|thor|watchr)))(?:[\t ]+((?:[^\n\r])+))?)(?:[\t ]*$)</string>
7574 <key>beginCaptures</key>
7575 <dict>
7576 <key>1</key>
7577 <dict>
7578 <key>name</key>
7579 <string>string.other.begin.code.fenced.mdx</string>
7580 </dict>
7581 <key>2</key>
7582 <dict>
7583 <key>name</key>
7584 <string>entity.name.function.mdx</string>
7585 <key>patterns</key>
7586 <array>
7587 <dict>
7588 <key>include</key>
7589 <string>#markdown-string</string>
7590 </dict>
7591 </array>
7592 </dict>
7593 <key>3</key>
7594 <dict>
7595 <key>patterns</key>
7596 <array>
7597 <dict>
7598 <key>include</key>
7599 <string>#markdown-string</string>
7600 </dict>
7601 </array>
7602 </dict>
7603 </dict>
7604 <key>end</key>
7605 <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string>
7606 <key>endCaptures</key>
7607 <dict>
7608 <key>1</key>
7609 <dict>
7610 <key>name</key>
7611 <string>string.other.end.code.fenced.mdx</string>
7612 </dict>
7613 </dict>
7614 <key>name</key>
7615 <string>markup.code.ruby.mdx</string>
7616 <key>patterns</key>
7617 <array>
7618 <dict>
7619 <key>begin</key>
7620 <string>(^|\G)(\s*)(.*)</string>
7621 <key>while</key>
7622 <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string>
7623 <key>contentName</key>
7624 <string>meta.embedded.ruby</string>
7625 <key>patterns</key>
7626 <array>
7627 <dict>
7628 <key>include</key>
7629 <string>source.ruby</string>
7630 </dict>
7631 </array>
7632 </dict>
7633 </array>
7634 </dict>
7635 </array>
7636 </dict>
7637 <key>commonmark-code-fenced-rust</key>
7638 <dict>
7639 <key>patterns</key>
7640 <array>
7641 <dict>
7642 <key>begin</key>
7643 <string>(?:^|\G)[\t ]*(`{3,})(?:[\t ]*((?i:rust|(?:.*\.)?(?:rs|rs\.in)))(?:[\t ]+((?:[^\n\r`])+))?)(?:[\t ]*$)</string>
7644 <key>beginCaptures</key>
7645 <dict>
7646 <key>1</key>
7647 <dict>
7648 <key>name</key>
7649 <string>string.other.begin.code.fenced.mdx</string>
7650 </dict>
7651 <key>2</key>
7652 <dict>
7653 <key>name</key>
7654 <string>entity.name.function.mdx</string>
7655 <key>patterns</key>
7656 <array>
7657 <dict>
7658 <key>include</key>
7659 <string>#markdown-string</string>
7660 </dict>
7661 </array>
7662 </dict>
7663 <key>3</key>
7664 <dict>
7665 <key>patterns</key>
7666 <array>
7667 <dict>
7668 <key>include</key>
7669 <string>#markdown-string</string>
7670 </dict>
7671 </array>
7672 </dict>
7673 </dict>
7674 <key>end</key>
7675 <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string>
7676 <key>endCaptures</key>
7677 <dict>
7678 <key>1</key>
7679 <dict>
7680 <key>name</key>
7681 <string>string.other.end.code.fenced.mdx</string>
7682 </dict>
7683 </dict>
7684 <key>name</key>
7685 <string>markup.code.rust.mdx</string>
7686 <key>patterns</key>
7687 <array>
7688 <dict>
7689 <key>begin</key>
7690 <string>(^|\G)(\s*)(.*)</string>
7691 <key>while</key>
7692 <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string>
7693 <key>contentName</key>
7694 <string>meta.embedded.rust</string>
7695 <key>patterns</key>
7696 <array>
7697 <dict>
7698 <key>include</key>
7699 <string>source.rust</string>
7700 </dict>
7701 </array>
7702 </dict>
7703 </array>
7704 </dict>
7705 <dict>
7706 <key>begin</key>
7707 <string>(?:^|\G)[\t ]*(~{3,})(?:[\t ]*((?i:rust|(?:.*\.)?(?:rs|rs\.in)))(?:[\t ]+((?:[^\n\r])+))?)(?:[\t ]*$)</string>
7708 <key>beginCaptures</key>
7709 <dict>
7710 <key>1</key>
7711 <dict>
7712 <key>name</key>
7713 <string>string.other.begin.code.fenced.mdx</string>
7714 </dict>
7715 <key>2</key>
7716 <dict>
7717 <key>name</key>
7718 <string>entity.name.function.mdx</string>
7719 <key>patterns</key>
7720 <array>
7721 <dict>
7722 <key>include</key>
7723 <string>#markdown-string</string>
7724 </dict>
7725 </array>
7726 </dict>
7727 <key>3</key>
7728 <dict>
7729 <key>patterns</key>
7730 <array>
7731 <dict>
7732 <key>include</key>
7733 <string>#markdown-string</string>
7734 </dict>
7735 </array>
7736 </dict>
7737 </dict>
7738 <key>end</key>
7739 <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string>
7740 <key>endCaptures</key>
7741 <dict>
7742 <key>1</key>
7743 <dict>
7744 <key>name</key>
7745 <string>string.other.end.code.fenced.mdx</string>
7746 </dict>
7747 </dict>
7748 <key>name</key>
7749 <string>markup.code.rust.mdx</string>
7750 <key>patterns</key>
7751 <array>
7752 <dict>
7753 <key>begin</key>
7754 <string>(^|\G)(\s*)(.*)</string>
7755 <key>while</key>
7756 <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string>
7757 <key>contentName</key>
7758 <string>meta.embedded.rust</string>
7759 <key>patterns</key>
7760 <array>
7761 <dict>
7762 <key>include</key>
7763 <string>source.rust</string>
7764 </dict>
7765 </array>
7766 </dict>
7767 </array>
7768 </dict>
7769 </array>
7770 </dict>
7771 <key>commonmark-code-fenced-scala</key>
7772 <dict>
7773 <key>patterns</key>
7774 <array>
7775 <dict>
7776 <key>begin</key>
7777 <string>(?:^|\G)[\t ]*(`{3,})(?:[\t ]*((?i:(?:.*\.)?(?:kojo|sbt|sc|scala)))(?:[\t ]+((?:[^\n\r`])+))?)(?:[\t ]*$)</string>
7778 <key>beginCaptures</key>
7779 <dict>
7780 <key>1</key>
7781 <dict>
7782 <key>name</key>
7783 <string>string.other.begin.code.fenced.mdx</string>
7784 </dict>
7785 <key>2</key>
7786 <dict>
7787 <key>name</key>
7788 <string>entity.name.function.mdx</string>
7789 <key>patterns</key>
7790 <array>
7791 <dict>
7792 <key>include</key>
7793 <string>#markdown-string</string>
7794 </dict>
7795 </array>
7796 </dict>
7797 <key>3</key>
7798 <dict>
7799 <key>patterns</key>
7800 <array>
7801 <dict>
7802 <key>include</key>
7803 <string>#markdown-string</string>
7804 </dict>
7805 </array>
7806 </dict>
7807 </dict>
7808 <key>end</key>
7809 <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string>
7810 <key>endCaptures</key>
7811 <dict>
7812 <key>1</key>
7813 <dict>
7814 <key>name</key>
7815 <string>string.other.end.code.fenced.mdx</string>
7816 </dict>
7817 </dict>
7818 <key>name</key>
7819 <string>markup.code.scala.mdx</string>
7820 <key>patterns</key>
7821 <array>
7822 <dict>
7823 <key>begin</key>
7824 <string>(^|\G)(\s*)(.*)</string>
7825 <key>while</key>
7826 <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string>
7827 <key>contentName</key>
7828 <string>meta.embedded.scala</string>
7829 <key>patterns</key>
7830 <array>
7831 <dict>
7832 <key>include</key>
7833 <string>source.scala</string>
7834 </dict>
7835 </array>
7836 </dict>
7837 </array>
7838 </dict>
7839 <dict>
7840 <key>begin</key>
7841 <string>(?:^|\G)[\t ]*(~{3,})(?:[\t ]*((?i:(?:.*\.)?(?:kojo|sbt|sc|scala)))(?:[\t ]+((?:[^\n\r])+))?)(?:[\t ]*$)</string>
7842 <key>beginCaptures</key>
7843 <dict>
7844 <key>1</key>
7845 <dict>
7846 <key>name</key>
7847 <string>string.other.begin.code.fenced.mdx</string>
7848 </dict>
7849 <key>2</key>
7850 <dict>
7851 <key>name</key>
7852 <string>entity.name.function.mdx</string>
7853 <key>patterns</key>
7854 <array>
7855 <dict>
7856 <key>include</key>
7857 <string>#markdown-string</string>
7858 </dict>
7859 </array>
7860 </dict>
7861 <key>3</key>
7862 <dict>
7863 <key>patterns</key>
7864 <array>
7865 <dict>
7866 <key>include</key>
7867 <string>#markdown-string</string>
7868 </dict>
7869 </array>
7870 </dict>
7871 </dict>
7872 <key>end</key>
7873 <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string>
7874 <key>endCaptures</key>
7875 <dict>
7876 <key>1</key>
7877 <dict>
7878 <key>name</key>
7879 <string>string.other.end.code.fenced.mdx</string>
7880 </dict>
7881 </dict>
7882 <key>name</key>
7883 <string>markup.code.scala.mdx</string>
7884 <key>patterns</key>
7885 <array>
7886 <dict>
7887 <key>begin</key>
7888 <string>(^|\G)(\s*)(.*)</string>
7889 <key>while</key>
7890 <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string>
7891 <key>contentName</key>
7892 <string>meta.embedded.scala</string>
7893 <key>patterns</key>
7894 <array>
7895 <dict>
7896 <key>include</key>
7897 <string>source.scala</string>
7898 </dict>
7899 </array>
7900 </dict>
7901 </array>
7902 </dict>
7903 </array>
7904 </dict>
7905 <key>commonmark-code-fenced-scss</key>
7906 <dict>
7907 <key>patterns</key>
7908 <array>
7909 <dict>
7910 <key>begin</key>
7911 <string>(?:^|\G)[\t ]*(`{3,})(?:[\t ]*((?i:(?:.*\.)?scss))(?:[\t ]+((?:[^\n\r`])+))?)(?:[\t ]*$)</string>
7912 <key>beginCaptures</key>
7913 <dict>
7914 <key>1</key>
7915 <dict>
7916 <key>name</key>
7917 <string>string.other.begin.code.fenced.mdx</string>
7918 </dict>
7919 <key>2</key>
7920 <dict>
7921 <key>name</key>
7922 <string>entity.name.function.mdx</string>
7923 <key>patterns</key>
7924 <array>
7925 <dict>
7926 <key>include</key>
7927 <string>#markdown-string</string>
7928 </dict>
7929 </array>
7930 </dict>
7931 <key>3</key>
7932 <dict>
7933 <key>patterns</key>
7934 <array>
7935 <dict>
7936 <key>include</key>
7937 <string>#markdown-string</string>
7938 </dict>
7939 </array>
7940 </dict>
7941 </dict>
7942 <key>end</key>
7943 <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string>
7944 <key>endCaptures</key>
7945 <dict>
7946 <key>1</key>
7947 <dict>
7948 <key>name</key>
7949 <string>string.other.end.code.fenced.mdx</string>
7950 </dict>
7951 </dict>
7952 <key>name</key>
7953 <string>markup.code.scss.mdx</string>
7954 <key>patterns</key>
7955 <array>
7956 <dict>
7957 <key>begin</key>
7958 <string>(^|\G)(\s*)(.*)</string>
7959 <key>while</key>
7960 <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string>
7961 <key>contentName</key>
7962 <string>meta.embedded.scss</string>
7963 <key>patterns</key>
7964 <array>
7965 <dict>
7966 <key>include</key>
7967 <string>source.css.scss</string>
7968 </dict>
7969 </array>
7970 </dict>
7971 </array>
7972 </dict>
7973 <dict>
7974 <key>begin</key>
7975 <string>(?:^|\G)[\t ]*(~{3,})(?:[\t ]*((?i:(?:.*\.)?scss))(?:[\t ]+((?:[^\n\r])+))?)(?:[\t ]*$)</string>
7976 <key>beginCaptures</key>
7977 <dict>
7978 <key>1</key>
7979 <dict>
7980 <key>name</key>
7981 <string>string.other.begin.code.fenced.mdx</string>
7982 </dict>
7983 <key>2</key>
7984 <dict>
7985 <key>name</key>
7986 <string>entity.name.function.mdx</string>
7987 <key>patterns</key>
7988 <array>
7989 <dict>
7990 <key>include</key>
7991 <string>#markdown-string</string>
7992 </dict>
7993 </array>
7994 </dict>
7995 <key>3</key>
7996 <dict>
7997 <key>patterns</key>
7998 <array>
7999 <dict>
8000 <key>include</key>
8001 <string>#markdown-string</string>
8002 </dict>
8003 </array>
8004 </dict>
8005 </dict>
8006 <key>end</key>
8007 <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string>
8008 <key>endCaptures</key>
8009 <dict>
8010 <key>1</key>
8011 <dict>
8012 <key>name</key>
8013 <string>string.other.end.code.fenced.mdx</string>
8014 </dict>
8015 </dict>
8016 <key>name</key>
8017 <string>markup.code.scss.mdx</string>
8018 <key>patterns</key>
8019 <array>
8020 <dict>
8021 <key>begin</key>
8022 <string>(^|\G)(\s*)(.*)</string>
8023 <key>while</key>
8024 <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string>
8025 <key>contentName</key>
8026 <string>meta.embedded.scss</string>
8027 <key>patterns</key>
8028 <array>
8029 <dict>
8030 <key>include</key>
8031 <string>source.css.scss</string>
8032 </dict>
8033 </array>
8034 </dict>
8035 </array>
8036 </dict>
8037 </array>
8038 </dict>
8039 <key>commonmark-code-fenced-shell</key>
8040 <dict>
8041 <key>patterns</key>
8042 <array>
8043 <dict>
8044 <key>begin</key>
8045 <string>(?:^|\G)[\t ]*(`{3,})(?:[\t ]*((?i:abuild|alpine\x2dabuild|apkbuild|envrc|gentoo\x2debuild|gentoo\x2declass|openrc|openrc\x2drunscript|shell|shell\x2dscript|(?:.*\.)?(?:bash|bats|command|csh|ebuild|eclass|ksh|sh|sh\.in|tcsh|tmux|tool|zsh|zsh\x2dtheme)))(?:[\t ]+((?:[^\n\r`])+))?)(?:[\t ]*$)</string>
8046 <key>beginCaptures</key>
8047 <dict>
8048 <key>1</key>
8049 <dict>
8050 <key>name</key>
8051 <string>string.other.begin.code.fenced.mdx</string>
8052 </dict>
8053 <key>2</key>
8054 <dict>
8055 <key>name</key>
8056 <string>entity.name.function.mdx</string>
8057 <key>patterns</key>
8058 <array>
8059 <dict>
8060 <key>include</key>
8061 <string>#markdown-string</string>
8062 </dict>
8063 </array>
8064 </dict>
8065 <key>3</key>
8066 <dict>
8067 <key>patterns</key>
8068 <array>
8069 <dict>
8070 <key>include</key>
8071 <string>#markdown-string</string>
8072 </dict>
8073 </array>
8074 </dict>
8075 </dict>
8076 <key>end</key>
8077 <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string>
8078 <key>endCaptures</key>
8079 <dict>
8080 <key>1</key>
8081 <dict>
8082 <key>name</key>
8083 <string>string.other.end.code.fenced.mdx</string>
8084 </dict>
8085 </dict>
8086 <key>name</key>
8087 <string>markup.code.shell.mdx</string>
8088 <key>patterns</key>
8089 <array>
8090 <dict>
8091 <key>begin</key>
8092 <string>(^|\G)(\s*)(.*)</string>
8093 <key>while</key>
8094 <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string>
8095 <key>contentName</key>
8096 <string>meta.embedded.shell</string>
8097 <key>patterns</key>
8098 <array>
8099 <dict>
8100 <key>include</key>
8101 <string>source.shell</string>
8102 </dict>
8103 </array>
8104 </dict>
8105 </array>
8106 </dict>
8107 <dict>
8108 <key>begin</key>
8109 <string>(?:^|\G)[\t ]*(~{3,})(?:[\t ]*((?i:abuild|alpine\x2dabuild|apkbuild|envrc|gentoo\x2debuild|gentoo\x2declass|openrc|openrc\x2drunscript|shell|shell\x2dscript|(?:.*\.)?(?:bash|bats|command|csh|ebuild|eclass|ksh|sh|sh\.in|tcsh|tmux|tool|zsh|zsh\x2dtheme)))(?:[\t ]+((?:[^\n\r])+))?)(?:[\t ]*$)</string>
8110 <key>beginCaptures</key>
8111 <dict>
8112 <key>1</key>
8113 <dict>
8114 <key>name</key>
8115 <string>string.other.begin.code.fenced.mdx</string>
8116 </dict>
8117 <key>2</key>
8118 <dict>
8119 <key>name</key>
8120 <string>entity.name.function.mdx</string>
8121 <key>patterns</key>
8122 <array>
8123 <dict>
8124 <key>include</key>
8125 <string>#markdown-string</string>
8126 </dict>
8127 </array>
8128 </dict>
8129 <key>3</key>
8130 <dict>
8131 <key>patterns</key>
8132 <array>
8133 <dict>
8134 <key>include</key>
8135 <string>#markdown-string</string>
8136 </dict>
8137 </array>
8138 </dict>
8139 </dict>
8140 <key>end</key>
8141 <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string>
8142 <key>endCaptures</key>
8143 <dict>
8144 <key>1</key>
8145 <dict>
8146 <key>name</key>
8147 <string>string.other.end.code.fenced.mdx</string>
8148 </dict>
8149 </dict>
8150 <key>name</key>
8151 <string>markup.code.shell.mdx</string>
8152 <key>patterns</key>
8153 <array>
8154 <dict>
8155 <key>begin</key>
8156 <string>(^|\G)(\s*)(.*)</string>
8157 <key>while</key>
8158 <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string>
8159 <key>contentName</key>
8160 <string>meta.embedded.shell</string>
8161 <key>patterns</key>
8162 <array>
8163 <dict>
8164 <key>include</key>
8165 <string>source.shell</string>
8166 </dict>
8167 </array>
8168 </dict>
8169 </array>
8170 </dict>
8171 </array>
8172 </dict>
8173 <key>commonmark-code-fenced-shell-session</key>
8174 <dict>
8175 <key>patterns</key>
8176 <array>
8177 <dict>
8178 <key>begin</key>
8179 <string>(?:^|\G)[\t ]*(`{3,})(?:[\t ]*((?i:bash\x2dsession|console|shellsession|(?:.*\.)?sh\x2dsession))(?:[\t ]+((?:[^\n\r`])+))?)(?:[\t ]*$)</string>
8180 <key>beginCaptures</key>
8181 <dict>
8182 <key>1</key>
8183 <dict>
8184 <key>name</key>
8185 <string>string.other.begin.code.fenced.mdx</string>
8186 </dict>
8187 <key>2</key>
8188 <dict>
8189 <key>name</key>
8190 <string>entity.name.function.mdx</string>
8191 <key>patterns</key>
8192 <array>
8193 <dict>
8194 <key>include</key>
8195 <string>#markdown-string</string>
8196 </dict>
8197 </array>
8198 </dict>
8199 <key>3</key>
8200 <dict>
8201 <key>patterns</key>
8202 <array>
8203 <dict>
8204 <key>include</key>
8205 <string>#markdown-string</string>
8206 </dict>
8207 </array>
8208 </dict>
8209 </dict>
8210 <key>end</key>
8211 <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string>
8212 <key>endCaptures</key>
8213 <dict>
8214 <key>1</key>
8215 <dict>
8216 <key>name</key>
8217 <string>string.other.end.code.fenced.mdx</string>
8218 </dict>
8219 </dict>
8220 <key>name</key>
8221 <string>markup.code.shell-session.mdx</string>
8222 <key>patterns</key>
8223 <array>
8224 <dict>
8225 <key>begin</key>
8226 <string>(^|\G)(\s*)(.*)</string>
8227 <key>while</key>
8228 <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string>
8229 <key>contentName</key>
8230 <string>meta.embedded.shell-session</string>
8231 <key>patterns</key>
8232 <array>
8233 <dict>
8234 <key>include</key>
8235 <string>text.shell-session</string>
8236 </dict>
8237 </array>
8238 </dict>
8239 </array>
8240 </dict>
8241 <dict>
8242 <key>begin</key>
8243 <string>(?:^|\G)[\t ]*(~{3,})(?:[\t ]*((?i:bash\x2dsession|console|shellsession|(?:.*\.)?sh\x2dsession))(?:[\t ]+((?:[^\n\r])+))?)(?:[\t ]*$)</string>
8244 <key>beginCaptures</key>
8245 <dict>
8246 <key>1</key>
8247 <dict>
8248 <key>name</key>
8249 <string>string.other.begin.code.fenced.mdx</string>
8250 </dict>
8251 <key>2</key>
8252 <dict>
8253 <key>name</key>
8254 <string>entity.name.function.mdx</string>
8255 <key>patterns</key>
8256 <array>
8257 <dict>
8258 <key>include</key>
8259 <string>#markdown-string</string>
8260 </dict>
8261 </array>
8262 </dict>
8263 <key>3</key>
8264 <dict>
8265 <key>patterns</key>
8266 <array>
8267 <dict>
8268 <key>include</key>
8269 <string>#markdown-string</string>
8270 </dict>
8271 </array>
8272 </dict>
8273 </dict>
8274 <key>end</key>
8275 <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string>
8276 <key>endCaptures</key>
8277 <dict>
8278 <key>1</key>
8279 <dict>
8280 <key>name</key>
8281 <string>string.other.end.code.fenced.mdx</string>
8282 </dict>
8283 </dict>
8284 <key>name</key>
8285 <string>markup.code.shell-session.mdx</string>
8286 <key>patterns</key>
8287 <array>
8288 <dict>
8289 <key>begin</key>
8290 <string>(^|\G)(\s*)(.*)</string>
8291 <key>while</key>
8292 <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string>
8293 <key>contentName</key>
8294 <string>meta.embedded.shell-session</string>
8295 <key>patterns</key>
8296 <array>
8297 <dict>
8298 <key>include</key>
8299 <string>text.shell-session</string>
8300 </dict>
8301 </array>
8302 </dict>
8303 </array>
8304 </dict>
8305 </array>
8306 </dict>
8307 <key>commonmark-code-fenced-sql</key>
8308 <dict>
8309 <key>patterns</key>
8310 <array>
8311 <dict>
8312 <key>begin</key>
8313 <string>(?:^|\G)[\t ]*(`{3,})(?:[\t ]*((?i:plpgsql|sqlpl|(?:.*\.)?(?:cql|db2|ddl|mysql|pgsql|prc|sql|sql|sql|tab|udf|viw)))(?:[\t ]+((?:[^\n\r`])+))?)(?:[\t ]*$)</string>
8314 <key>beginCaptures</key>
8315 <dict>
8316 <key>1</key>
8317 <dict>
8318 <key>name</key>
8319 <string>string.other.begin.code.fenced.mdx</string>
8320 </dict>
8321 <key>2</key>
8322 <dict>
8323 <key>name</key>
8324 <string>entity.name.function.mdx</string>
8325 <key>patterns</key>
8326 <array>
8327 <dict>
8328 <key>include</key>
8329 <string>#markdown-string</string>
8330 </dict>
8331 </array>
8332 </dict>
8333 <key>3</key>
8334 <dict>
8335 <key>patterns</key>
8336 <array>
8337 <dict>
8338 <key>include</key>
8339 <string>#markdown-string</string>
8340 </dict>
8341 </array>
8342 </dict>
8343 </dict>
8344 <key>end</key>
8345 <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string>
8346 <key>endCaptures</key>
8347 <dict>
8348 <key>1</key>
8349 <dict>
8350 <key>name</key>
8351 <string>string.other.end.code.fenced.mdx</string>
8352 </dict>
8353 </dict>
8354 <key>name</key>
8355 <string>markup.code.sql.mdx</string>
8356 <key>patterns</key>
8357 <array>
8358 <dict>
8359 <key>begin</key>
8360 <string>(^|\G)(\s*)(.*)</string>
8361 <key>while</key>
8362 <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string>
8363 <key>contentName</key>
8364 <string>meta.embedded.sql</string>
8365 <key>patterns</key>
8366 <array>
8367 <dict>
8368 <key>include</key>
8369 <string>source.sql</string>
8370 </dict>
8371 </array>
8372 </dict>
8373 </array>
8374 </dict>
8375 <dict>
8376 <key>begin</key>
8377 <string>(?:^|\G)[\t ]*(~{3,})(?:[\t ]*((?i:plpgsql|sqlpl|(?:.*\.)?(?:cql|db2|ddl|mysql|pgsql|prc|sql|sql|sql|tab|udf|viw)))(?:[\t ]+((?:[^\n\r])+))?)(?:[\t ]*$)</string>
8378 <key>beginCaptures</key>
8379 <dict>
8380 <key>1</key>
8381 <dict>
8382 <key>name</key>
8383 <string>string.other.begin.code.fenced.mdx</string>
8384 </dict>
8385 <key>2</key>
8386 <dict>
8387 <key>name</key>
8388 <string>entity.name.function.mdx</string>
8389 <key>patterns</key>
8390 <array>
8391 <dict>
8392 <key>include</key>
8393 <string>#markdown-string</string>
8394 </dict>
8395 </array>
8396 </dict>
8397 <key>3</key>
8398 <dict>
8399 <key>patterns</key>
8400 <array>
8401 <dict>
8402 <key>include</key>
8403 <string>#markdown-string</string>
8404 </dict>
8405 </array>
8406 </dict>
8407 </dict>
8408 <key>end</key>
8409 <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string>
8410 <key>endCaptures</key>
8411 <dict>
8412 <key>1</key>
8413 <dict>
8414 <key>name</key>
8415 <string>string.other.end.code.fenced.mdx</string>
8416 </dict>
8417 </dict>
8418 <key>name</key>
8419 <string>markup.code.sql.mdx</string>
8420 <key>patterns</key>
8421 <array>
8422 <dict>
8423 <key>begin</key>
8424 <string>(^|\G)(\s*)(.*)</string>
8425 <key>while</key>
8426 <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string>
8427 <key>contentName</key>
8428 <string>meta.embedded.sql</string>
8429 <key>patterns</key>
8430 <array>
8431 <dict>
8432 <key>include</key>
8433 <string>source.sql</string>
8434 </dict>
8435 </array>
8436 </dict>
8437 </array>
8438 </dict>
8439 </array>
8440 </dict>
8441 <key>commonmark-code-fenced-svg</key>
8442 <dict>
8443 <key>patterns</key>
8444 <array>
8445 <dict>
8446 <key>begin</key>
8447 <string>(?:^|\G)[\t ]*(`{3,})(?:[\t ]*((?i:(?:.*\.)?svg))(?:[\t ]+((?:[^\n\r`])+))?)(?:[\t ]*$)</string>
8448 <key>beginCaptures</key>
8449 <dict>
8450 <key>1</key>
8451 <dict>
8452 <key>name</key>
8453 <string>string.other.begin.code.fenced.mdx</string>
8454 </dict>
8455 <key>2</key>
8456 <dict>
8457 <key>name</key>
8458 <string>entity.name.function.mdx</string>
8459 <key>patterns</key>
8460 <array>
8461 <dict>
8462 <key>include</key>
8463 <string>#markdown-string</string>
8464 </dict>
8465 </array>
8466 </dict>
8467 <key>3</key>
8468 <dict>
8469 <key>patterns</key>
8470 <array>
8471 <dict>
8472 <key>include</key>
8473 <string>#markdown-string</string>
8474 </dict>
8475 </array>
8476 </dict>
8477 </dict>
8478 <key>end</key>
8479 <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string>
8480 <key>endCaptures</key>
8481 <dict>
8482 <key>1</key>
8483 <dict>
8484 <key>name</key>
8485 <string>string.other.end.code.fenced.mdx</string>
8486 </dict>
8487 </dict>
8488 <key>name</key>
8489 <string>markup.code.svg.mdx</string>
8490 <key>patterns</key>
8491 <array>
8492 <dict>
8493 <key>begin</key>
8494 <string>(^|\G)(\s*)(.*)</string>
8495 <key>while</key>
8496 <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string>
8497 <key>contentName</key>
8498 <string>meta.embedded.svg</string>
8499 <key>patterns</key>
8500 <array>
8501 <dict>
8502 <key>include</key>
8503 <string>text.xml.svg</string>
8504 </dict>
8505 <dict>
8506 <key>include</key>
8507 <string>text.xml</string>
8508 </dict>
8509 </array>
8510 </dict>
8511 </array>
8512 </dict>
8513 <dict>
8514 <key>begin</key>
8515 <string>(?:^|\G)[\t ]*(~{3,})(?:[\t ]*((?i:(?:.*\.)?svg))(?:[\t ]+((?:[^\n\r])+))?)(?:[\t ]*$)</string>
8516 <key>beginCaptures</key>
8517 <dict>
8518 <key>1</key>
8519 <dict>
8520 <key>name</key>
8521 <string>string.other.begin.code.fenced.mdx</string>
8522 </dict>
8523 <key>2</key>
8524 <dict>
8525 <key>name</key>
8526 <string>entity.name.function.mdx</string>
8527 <key>patterns</key>
8528 <array>
8529 <dict>
8530 <key>include</key>
8531 <string>#markdown-string</string>
8532 </dict>
8533 </array>
8534 </dict>
8535 <key>3</key>
8536 <dict>
8537 <key>patterns</key>
8538 <array>
8539 <dict>
8540 <key>include</key>
8541 <string>#markdown-string</string>
8542 </dict>
8543 </array>
8544 </dict>
8545 </dict>
8546 <key>end</key>
8547 <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string>
8548 <key>endCaptures</key>
8549 <dict>
8550 <key>1</key>
8551 <dict>
8552 <key>name</key>
8553 <string>string.other.end.code.fenced.mdx</string>
8554 </dict>
8555 </dict>
8556 <key>name</key>
8557 <string>markup.code.svg.mdx</string>
8558 <key>patterns</key>
8559 <array>
8560 <dict>
8561 <key>begin</key>
8562 <string>(^|\G)(\s*)(.*)</string>
8563 <key>while</key>
8564 <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string>
8565 <key>contentName</key>
8566 <string>meta.embedded.svg</string>
8567 <key>patterns</key>
8568 <array>
8569 <dict>
8570 <key>include</key>
8571 <string>text.xml.svg</string>
8572 </dict>
8573 <dict>
8574 <key>include</key>
8575 <string>text.xml</string>
8576 </dict>
8577 </array>
8578 </dict>
8579 </array>
8580 </dict>
8581 </array>
8582 </dict>
8583 <key>commonmark-code-fenced-swift</key>
8584 <dict>
8585 <key>patterns</key>
8586 <array>
8587 <dict>
8588 <key>begin</key>
8589 <string>(?:^|\G)[\t ]*(`{3,})(?:[\t ]*((?i:(?:.*\.)?swift))(?:[\t ]+((?:[^\n\r`])+))?)(?:[\t ]*$)</string>
8590 <key>beginCaptures</key>
8591 <dict>
8592 <key>1</key>
8593 <dict>
8594 <key>name</key>
8595 <string>string.other.begin.code.fenced.mdx</string>
8596 </dict>
8597 <key>2</key>
8598 <dict>
8599 <key>name</key>
8600 <string>entity.name.function.mdx</string>
8601 <key>patterns</key>
8602 <array>
8603 <dict>
8604 <key>include</key>
8605 <string>#markdown-string</string>
8606 </dict>
8607 </array>
8608 </dict>
8609 <key>3</key>
8610 <dict>
8611 <key>patterns</key>
8612 <array>
8613 <dict>
8614 <key>include</key>
8615 <string>#markdown-string</string>
8616 </dict>
8617 </array>
8618 </dict>
8619 </dict>
8620 <key>end</key>
8621 <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string>
8622 <key>endCaptures</key>
8623 <dict>
8624 <key>1</key>
8625 <dict>
8626 <key>name</key>
8627 <string>string.other.end.code.fenced.mdx</string>
8628 </dict>
8629 </dict>
8630 <key>name</key>
8631 <string>markup.code.swift.mdx</string>
8632 <key>patterns</key>
8633 <array>
8634 <dict>
8635 <key>begin</key>
8636 <string>(^|\G)(\s*)(.*)</string>
8637 <key>while</key>
8638 <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string>
8639 <key>contentName</key>
8640 <string>meta.embedded.swift</string>
8641 <key>patterns</key>
8642 <array>
8643 <dict>
8644 <key>include</key>
8645 <string>source.swift</string>
8646 </dict>
8647 </array>
8648 </dict>
8649 </array>
8650 </dict>
8651 <dict>
8652 <key>begin</key>
8653 <string>(?:^|\G)[\t ]*(~{3,})(?:[\t ]*((?i:(?:.*\.)?swift))(?:[\t ]+((?:[^\n\r])+))?)(?:[\t ]*$)</string>
8654 <key>beginCaptures</key>
8655 <dict>
8656 <key>1</key>
8657 <dict>
8658 <key>name</key>
8659 <string>string.other.begin.code.fenced.mdx</string>
8660 </dict>
8661 <key>2</key>
8662 <dict>
8663 <key>name</key>
8664 <string>entity.name.function.mdx</string>
8665 <key>patterns</key>
8666 <array>
8667 <dict>
8668 <key>include</key>
8669 <string>#markdown-string</string>
8670 </dict>
8671 </array>
8672 </dict>
8673 <key>3</key>
8674 <dict>
8675 <key>patterns</key>
8676 <array>
8677 <dict>
8678 <key>include</key>
8679 <string>#markdown-string</string>
8680 </dict>
8681 </array>
8682 </dict>
8683 </dict>
8684 <key>end</key>
8685 <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string>
8686 <key>endCaptures</key>
8687 <dict>
8688 <key>1</key>
8689 <dict>
8690 <key>name</key>
8691 <string>string.other.end.code.fenced.mdx</string>
8692 </dict>
8693 </dict>
8694 <key>name</key>
8695 <string>markup.code.swift.mdx</string>
8696 <key>patterns</key>
8697 <array>
8698 <dict>
8699 <key>begin</key>
8700 <string>(^|\G)(\s*)(.*)</string>
8701 <key>while</key>
8702 <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string>
8703 <key>contentName</key>
8704 <string>meta.embedded.swift</string>
8705 <key>patterns</key>
8706 <array>
8707 <dict>
8708 <key>include</key>
8709 <string>source.swift</string>
8710 </dict>
8711 </array>
8712 </dict>
8713 </array>
8714 </dict>
8715 </array>
8716 </dict>
8717 <key>commonmark-code-fenced-toml</key>
8718 <dict>
8719 <key>patterns</key>
8720 <array>
8721 <dict>
8722 <key>begin</key>
8723 <string>(?:^|\G)[\t ]*(`{3,})(?:[\t ]*((?i:(?:.*\.)?toml))(?:[\t ]+((?:[^\n\r`])+))?)(?:[\t ]*$)</string>
8724 <key>beginCaptures</key>
8725 <dict>
8726 <key>1</key>
8727 <dict>
8728 <key>name</key>
8729 <string>string.other.begin.code.fenced.mdx</string>
8730 </dict>
8731 <key>2</key>
8732 <dict>
8733 <key>name</key>
8734 <string>entity.name.function.mdx</string>
8735 <key>patterns</key>
8736 <array>
8737 <dict>
8738 <key>include</key>
8739 <string>#markdown-string</string>
8740 </dict>
8741 </array>
8742 </dict>
8743 <key>3</key>
8744 <dict>
8745 <key>patterns</key>
8746 <array>
8747 <dict>
8748 <key>include</key>
8749 <string>#markdown-string</string>
8750 </dict>
8751 </array>
8752 </dict>
8753 </dict>
8754 <key>end</key>
8755 <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string>
8756 <key>endCaptures</key>
8757 <dict>
8758 <key>1</key>
8759 <dict>
8760 <key>name</key>
8761 <string>string.other.end.code.fenced.mdx</string>
8762 </dict>
8763 </dict>
8764 <key>name</key>
8765 <string>markup.code.toml.mdx</string>
8766 <key>patterns</key>
8767 <array>
8768 <dict>
8769 <key>begin</key>
8770 <string>(^|\G)(\s*)(.*)</string>
8771 <key>while</key>
8772 <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string>
8773 <key>contentName</key>
8774 <string>meta.embedded.toml</string>
8775 <key>patterns</key>
8776 <array>
8777 <dict>
8778 <key>include</key>
8779 <string>source.toml</string>
8780 </dict>
8781 </array>
8782 </dict>
8783 </array>
8784 </dict>
8785 <dict>
8786 <key>begin</key>
8787 <string>(?:^|\G)[\t ]*(~{3,})(?:[\t ]*((?i:(?:.*\.)?toml))(?:[\t ]+((?:[^\n\r])+))?)(?:[\t ]*$)</string>
8788 <key>beginCaptures</key>
8789 <dict>
8790 <key>1</key>
8791 <dict>
8792 <key>name</key>
8793 <string>string.other.begin.code.fenced.mdx</string>
8794 </dict>
8795 <key>2</key>
8796 <dict>
8797 <key>name</key>
8798 <string>entity.name.function.mdx</string>
8799 <key>patterns</key>
8800 <array>
8801 <dict>
8802 <key>include</key>
8803 <string>#markdown-string</string>
8804 </dict>
8805 </array>
8806 </dict>
8807 <key>3</key>
8808 <dict>
8809 <key>patterns</key>
8810 <array>
8811 <dict>
8812 <key>include</key>
8813 <string>#markdown-string</string>
8814 </dict>
8815 </array>
8816 </dict>
8817 </dict>
8818 <key>end</key>
8819 <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string>
8820 <key>endCaptures</key>
8821 <dict>
8822 <key>1</key>
8823 <dict>
8824 <key>name</key>
8825 <string>string.other.end.code.fenced.mdx</string>
8826 </dict>
8827 </dict>
8828 <key>name</key>
8829 <string>markup.code.toml.mdx</string>
8830 <key>patterns</key>
8831 <array>
8832 <dict>
8833 <key>begin</key>
8834 <string>(^|\G)(\s*)(.*)</string>
8835 <key>while</key>
8836 <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string>
8837 <key>contentName</key>
8838 <string>meta.embedded.toml</string>
8839 <key>patterns</key>
8840 <array>
8841 <dict>
8842 <key>include</key>
8843 <string>source.toml</string>
8844 </dict>
8845 </array>
8846 </dict>
8847 </array>
8848 </dict>
8849 </array>
8850 </dict>
8851 <key>commonmark-code-fenced-ts</key>
8852 <dict>
8853 <key>patterns</key>
8854 <array>
8855 <dict>
8856 <key>begin</key>
8857 <string>(?:^|\G)[\t ]*(`{3,})(?:[\t ]*((?i:typescript|(?:.*\.)?(?:cts|mts|ts)))(?:[\t ]+((?:[^\n\r`])+))?)(?:[\t ]*$)</string>
8858 <key>beginCaptures</key>
8859 <dict>
8860 <key>1</key>
8861 <dict>
8862 <key>name</key>
8863 <string>string.other.begin.code.fenced.mdx</string>
8864 </dict>
8865 <key>2</key>
8866 <dict>
8867 <key>name</key>
8868 <string>entity.name.function.mdx</string>
8869 <key>patterns</key>
8870 <array>
8871 <dict>
8872 <key>include</key>
8873 <string>#markdown-string</string>
8874 </dict>
8875 </array>
8876 </dict>
8877 <key>3</key>
8878 <dict>
8879 <key>patterns</key>
8880 <array>
8881 <dict>
8882 <key>include</key>
8883 <string>#markdown-string</string>
8884 </dict>
8885 </array>
8886 </dict>
8887 </dict>
8888 <key>end</key>
8889 <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string>
8890 <key>endCaptures</key>
8891 <dict>
8892 <key>1</key>
8893 <dict>
8894 <key>name</key>
8895 <string>string.other.end.code.fenced.mdx</string>
8896 </dict>
8897 </dict>
8898 <key>name</key>
8899 <string>markup.code.ts.mdx</string>
8900 <key>patterns</key>
8901 <array>
8902 <dict>
8903 <key>begin</key>
8904 <string>(^|\G)(\s*)(.*)</string>
8905 <key>while</key>
8906 <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string>
8907 <key>contentName</key>
8908 <string>meta.embedded.ts</string>
8909 <key>patterns</key>
8910 <array>
8911 <dict>
8912 <key>include</key>
8913 <string>source.ts</string>
8914 </dict>
8915 </array>
8916 </dict>
8917 </array>
8918 </dict>
8919 <dict>
8920 <key>begin</key>
8921 <string>(?:^|\G)[\t ]*(~{3,})(?:[\t ]*((?i:typescript|(?:.*\.)?(?:cts|mts|ts)))(?:[\t ]+((?:[^\n\r])+))?)(?:[\t ]*$)</string>
8922 <key>beginCaptures</key>
8923 <dict>
8924 <key>1</key>
8925 <dict>
8926 <key>name</key>
8927 <string>string.other.begin.code.fenced.mdx</string>
8928 </dict>
8929 <key>2</key>
8930 <dict>
8931 <key>name</key>
8932 <string>entity.name.function.mdx</string>
8933 <key>patterns</key>
8934 <array>
8935 <dict>
8936 <key>include</key>
8937 <string>#markdown-string</string>
8938 </dict>
8939 </array>
8940 </dict>
8941 <key>3</key>
8942 <dict>
8943 <key>patterns</key>
8944 <array>
8945 <dict>
8946 <key>include</key>
8947 <string>#markdown-string</string>
8948 </dict>
8949 </array>
8950 </dict>
8951 </dict>
8952 <key>end</key>
8953 <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string>
8954 <key>endCaptures</key>
8955 <dict>
8956 <key>1</key>
8957 <dict>
8958 <key>name</key>
8959 <string>string.other.end.code.fenced.mdx</string>
8960 </dict>
8961 </dict>
8962 <key>name</key>
8963 <string>markup.code.ts.mdx</string>
8964 <key>patterns</key>
8965 <array>
8966 <dict>
8967 <key>begin</key>
8968 <string>(^|\G)(\s*)(.*)</string>
8969 <key>while</key>
8970 <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string>
8971 <key>contentName</key>
8972 <string>meta.embedded.ts</string>
8973 <key>patterns</key>
8974 <array>
8975 <dict>
8976 <key>include</key>
8977 <string>source.ts</string>
8978 </dict>
8979 </array>
8980 </dict>
8981 </array>
8982 </dict>
8983 </array>
8984 </dict>
8985 <key>commonmark-code-fenced-tsx</key>
8986 <dict>
8987 <key>patterns</key>
8988 <array>
8989 <dict>
8990 <key>begin</key>
8991 <string>(?:^|\G)[\t ]*(`{3,})(?:[\t ]*((?i:(?:.*\.)?tsx))(?:[\t ]+((?:[^\n\r`])+))?)(?:[\t ]*$)</string>
8992 <key>beginCaptures</key>
8993 <dict>
8994 <key>1</key>
8995 <dict>
8996 <key>name</key>
8997 <string>string.other.begin.code.fenced.mdx</string>
8998 </dict>
8999 <key>2</key>
9000 <dict>
9001 <key>name</key>
9002 <string>entity.name.function.mdx</string>
9003 <key>patterns</key>
9004 <array>
9005 <dict>
9006 <key>include</key>
9007 <string>#markdown-string</string>
9008 </dict>
9009 </array>
9010 </dict>
9011 <key>3</key>
9012 <dict>
9013 <key>patterns</key>
9014 <array>
9015 <dict>
9016 <key>include</key>
9017 <string>#markdown-string</string>
9018 </dict>
9019 </array>
9020 </dict>
9021 </dict>
9022 <key>end</key>
9023 <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string>
9024 <key>endCaptures</key>
9025 <dict>
9026 <key>1</key>
9027 <dict>
9028 <key>name</key>
9029 <string>string.other.end.code.fenced.mdx</string>
9030 </dict>
9031 </dict>
9032 <key>name</key>
9033 <string>markup.code.tsx.mdx</string>
9034 <key>patterns</key>
9035 <array>
9036 <dict>
9037 <key>begin</key>
9038 <string>(^|\G)(\s*)(.*)</string>
9039 <key>while</key>
9040 <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string>
9041 <key>contentName</key>
9042 <string>meta.embedded.tsx</string>
9043 <key>patterns</key>
9044 <array>
9045 <dict>
9046 <key>include</key>
9047 <string>source.tsx</string>
9048 </dict>
9049 </array>
9050 </dict>
9051 </array>
9052 </dict>
9053 <dict>
9054 <key>begin</key>
9055 <string>(?:^|\G)[\t ]*(~{3,})(?:[\t ]*((?i:(?:.*\.)?tsx))(?:[\t ]+((?:[^\n\r])+))?)(?:[\t ]*$)</string>
9056 <key>beginCaptures</key>
9057 <dict>
9058 <key>1</key>
9059 <dict>
9060 <key>name</key>
9061 <string>string.other.begin.code.fenced.mdx</string>
9062 </dict>
9063 <key>2</key>
9064 <dict>
9065 <key>name</key>
9066 <string>entity.name.function.mdx</string>
9067 <key>patterns</key>
9068 <array>
9069 <dict>
9070 <key>include</key>
9071 <string>#markdown-string</string>
9072 </dict>
9073 </array>
9074 </dict>
9075 <key>3</key>
9076 <dict>
9077 <key>patterns</key>
9078 <array>
9079 <dict>
9080 <key>include</key>
9081 <string>#markdown-string</string>
9082 </dict>
9083 </array>
9084 </dict>
9085 </dict>
9086 <key>end</key>
9087 <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string>
9088 <key>endCaptures</key>
9089 <dict>
9090 <key>1</key>
9091 <dict>
9092 <key>name</key>
9093 <string>string.other.end.code.fenced.mdx</string>
9094 </dict>
9095 </dict>
9096 <key>name</key>
9097 <string>markup.code.tsx.mdx</string>
9098 <key>patterns</key>
9099 <array>
9100 <dict>
9101 <key>begin</key>
9102 <string>(^|\G)(\s*)(.*)</string>
9103 <key>while</key>
9104 <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string>
9105 <key>contentName</key>
9106 <string>meta.embedded.tsx</string>
9107 <key>patterns</key>
9108 <array>
9109 <dict>
9110 <key>include</key>
9111 <string>source.tsx</string>
9112 </dict>
9113 </array>
9114 </dict>
9115 </array>
9116 </dict>
9117 </array>
9118 </dict>
9119 <key>commonmark-code-fenced-vbnet</key>
9120 <dict>
9121 <key>patterns</key>
9122 <array>
9123 <dict>
9124 <key>begin</key>
9125 <string>(?:^|\G)[\t ]*(`{3,})(?:[\t ]*((?i:fb|freebasic|realbasic|vb\x2d\.net|vb\.net|vbnet|vbscript|visual\x2dbasic|visual\x2dbasic\x2d\.net|(?:.*\.)?(?:bi|rbbas|rbfrm|rbmnu|rbres|rbtbar|rbuistate|vb|vbhtml|vbs)))(?:[\t ]+((?:[^\n\r`])+))?)(?:[\t ]*$)</string>
9126 <key>beginCaptures</key>
9127 <dict>
9128 <key>1</key>
9129 <dict>
9130 <key>name</key>
9131 <string>string.other.begin.code.fenced.mdx</string>
9132 </dict>
9133 <key>2</key>
9134 <dict>
9135 <key>name</key>
9136 <string>entity.name.function.mdx</string>
9137 <key>patterns</key>
9138 <array>
9139 <dict>
9140 <key>include</key>
9141 <string>#markdown-string</string>
9142 </dict>
9143 </array>
9144 </dict>
9145 <key>3</key>
9146 <dict>
9147 <key>patterns</key>
9148 <array>
9149 <dict>
9150 <key>include</key>
9151 <string>#markdown-string</string>
9152 </dict>
9153 </array>
9154 </dict>
9155 </dict>
9156 <key>end</key>
9157 <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string>
9158 <key>endCaptures</key>
9159 <dict>
9160 <key>1</key>
9161 <dict>
9162 <key>name</key>
9163 <string>string.other.end.code.fenced.mdx</string>
9164 </dict>
9165 </dict>
9166 <key>name</key>
9167 <string>markup.code.vbnet.mdx</string>
9168 <key>patterns</key>
9169 <array>
9170 <dict>
9171 <key>begin</key>
9172 <string>(^|\G)(\s*)(.*)</string>
9173 <key>while</key>
9174 <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string>
9175 <key>contentName</key>
9176 <string>meta.embedded.vbnet</string>
9177 <key>patterns</key>
9178 <array>
9179 <dict>
9180 <key>include</key>
9181 <string>source.vbnet</string>
9182 </dict>
9183 </array>
9184 </dict>
9185 </array>
9186 </dict>
9187 <dict>
9188 <key>begin</key>
9189 <string>(?:^|\G)[\t ]*(~{3,})(?:[\t ]*((?i:fb|freebasic|realbasic|vb\x2d\.net|vb\.net|vbnet|vbscript|visual\x2dbasic|visual\x2dbasic\x2d\.net|(?:.*\.)?(?:bi|rbbas|rbfrm|rbmnu|rbres|rbtbar|rbuistate|vb|vbhtml|vbs)))(?:[\t ]+((?:[^\n\r])+))?)(?:[\t ]*$)</string>
9190 <key>beginCaptures</key>
9191 <dict>
9192 <key>1</key>
9193 <dict>
9194 <key>name</key>
9195 <string>string.other.begin.code.fenced.mdx</string>
9196 </dict>
9197 <key>2</key>
9198 <dict>
9199 <key>name</key>
9200 <string>entity.name.function.mdx</string>
9201 <key>patterns</key>
9202 <array>
9203 <dict>
9204 <key>include</key>
9205 <string>#markdown-string</string>
9206 </dict>
9207 </array>
9208 </dict>
9209 <key>3</key>
9210 <dict>
9211 <key>patterns</key>
9212 <array>
9213 <dict>
9214 <key>include</key>
9215 <string>#markdown-string</string>
9216 </dict>
9217 </array>
9218 </dict>
9219 </dict>
9220 <key>end</key>
9221 <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string>
9222 <key>endCaptures</key>
9223 <dict>
9224 <key>1</key>
9225 <dict>
9226 <key>name</key>
9227 <string>string.other.end.code.fenced.mdx</string>
9228 </dict>
9229 </dict>
9230 <key>name</key>
9231 <string>markup.code.vbnet.mdx</string>
9232 <key>patterns</key>
9233 <array>
9234 <dict>
9235 <key>begin</key>
9236 <string>(^|\G)(\s*)(.*)</string>
9237 <key>while</key>
9238 <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string>
9239 <key>contentName</key>
9240 <string>meta.embedded.vbnet</string>
9241 <key>patterns</key>
9242 <array>
9243 <dict>
9244 <key>include</key>
9245 <string>source.vbnet</string>
9246 </dict>
9247 </array>
9248 </dict>
9249 </array>
9250 </dict>
9251 </array>
9252 </dict>
9253 <key>commonmark-code-fenced-xml</key>
9254 <dict>
9255 <key>patterns</key>
9256 <array>
9257 <dict>
9258 <key>begin</key>
9259 <string>(?:^|\G)[\t ]*(`{3,})(?:[\t ]*((?i:collada|eagle|labview|web\x2dontology\x2dlanguage|xpages|(?:.*\.)?(?:adml|admx|ant|axaml|axml|brd|builds|ccproj|ccxml|clixml|cproject|cscfg|csdef|csproj|ct|dae|depproj|dita|ditamap|ditaval|dll\.config|dotsettings|filters|fsproj|fxml|glade|gmx|grxml|hzp|iml|ivy|jelly|jsproj|kml|launch|lvclass|lvlib|lvproj|mdpolicy|mjml|mxml|natvis|ndproj|nproj|nuspec|odd|osm|owl|pkgproj|proj|props|ps1xml|psc1|pt|qhelp|rdf|resx|rss|sch|sch|scxml|sfproj|shproj|srdf|storyboard|sublime\x2dsnippet|targets|tml|ui|urdf|ux|vbproj|vcxproj|vsixmanifest|vssettings|vstemplate|vxml|wixproj|wsdl|wsf|wxi|wxl|wxs|x3d|xacro|xaml|xib|xlf|xliff|xmi|xml|xml\.dist|xmp|xpl|xproc|xproj|xsd|xsp\x2dconfig|xsp\.metadata|xspec|xul|zcml)))(?:[\t ]+((?:[^\n\r`])+))?)(?:[\t ]*$)</string>
9260 <key>beginCaptures</key>
9261 <dict>
9262 <key>1</key>
9263 <dict>
9264 <key>name</key>
9265 <string>string.other.begin.code.fenced.mdx</string>
9266 </dict>
9267 <key>2</key>
9268 <dict>
9269 <key>name</key>
9270 <string>entity.name.function.mdx</string>
9271 <key>patterns</key>
9272 <array>
9273 <dict>
9274 <key>include</key>
9275 <string>#markdown-string</string>
9276 </dict>
9277 </array>
9278 </dict>
9279 <key>3</key>
9280 <dict>
9281 <key>patterns</key>
9282 <array>
9283 <dict>
9284 <key>include</key>
9285 <string>#markdown-string</string>
9286 </dict>
9287 </array>
9288 </dict>
9289 </dict>
9290 <key>end</key>
9291 <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string>
9292 <key>endCaptures</key>
9293 <dict>
9294 <key>1</key>
9295 <dict>
9296 <key>name</key>
9297 <string>string.other.end.code.fenced.mdx</string>
9298 </dict>
9299 </dict>
9300 <key>name</key>
9301 <string>markup.code.xml.mdx</string>
9302 <key>patterns</key>
9303 <array>
9304 <dict>
9305 <key>begin</key>
9306 <string>(^|\G)(\s*)(.*)</string>
9307 <key>while</key>
9308 <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string>
9309 <key>contentName</key>
9310 <string>meta.embedded.xml</string>
9311 <key>patterns</key>
9312 <array>
9313 <dict>
9314 <key>include</key>
9315 <string>text.xml</string>
9316 </dict>
9317 </array>
9318 </dict>
9319 </array>
9320 </dict>
9321 <dict>
9322 <key>begin</key>
9323 <string>(?:^|\G)[\t ]*(~{3,})(?:[\t ]*((?i:collada|eagle|labview|web\x2dontology\x2dlanguage|xpages|(?:.*\.)?(?:adml|admx|ant|axaml|axml|brd|builds|ccproj|ccxml|clixml|cproject|cscfg|csdef|csproj|ct|dae|depproj|dita|ditamap|ditaval|dll\.config|dotsettings|filters|fsproj|fxml|glade|gmx|grxml|hzp|iml|ivy|jelly|jsproj|kml|launch|lvclass|lvlib|lvproj|mdpolicy|mjml|mxml|natvis|ndproj|nproj|nuspec|odd|osm|owl|pkgproj|proj|props|ps1xml|psc1|pt|qhelp|rdf|resx|rss|sch|sch|scxml|sfproj|shproj|srdf|storyboard|sublime\x2dsnippet|targets|tml|ui|urdf|ux|vbproj|vcxproj|vsixmanifest|vssettings|vstemplate|vxml|wixproj|wsdl|wsf|wxi|wxl|wxs|x3d|xacro|xaml|xib|xlf|xliff|xmi|xml|xml\.dist|xmp|xpl|xproc|xproj|xsd|xsp\x2dconfig|xsp\.metadata|xspec|xul|zcml)))(?:[\t ]+((?:[^\n\r])+))?)(?:[\t ]*$)</string>
9324 <key>beginCaptures</key>
9325 <dict>
9326 <key>1</key>
9327 <dict>
9328 <key>name</key>
9329 <string>string.other.begin.code.fenced.mdx</string>
9330 </dict>
9331 <key>2</key>
9332 <dict>
9333 <key>name</key>
9334 <string>entity.name.function.mdx</string>
9335 <key>patterns</key>
9336 <array>
9337 <dict>
9338 <key>include</key>
9339 <string>#markdown-string</string>
9340 </dict>
9341 </array>
9342 </dict>
9343 <key>3</key>
9344 <dict>
9345 <key>patterns</key>
9346 <array>
9347 <dict>
9348 <key>include</key>
9349 <string>#markdown-string</string>
9350 </dict>
9351 </array>
9352 </dict>
9353 </dict>
9354 <key>end</key>
9355 <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string>
9356 <key>endCaptures</key>
9357 <dict>
9358 <key>1</key>
9359 <dict>
9360 <key>name</key>
9361 <string>string.other.end.code.fenced.mdx</string>
9362 </dict>
9363 </dict>
9364 <key>name</key>
9365 <string>markup.code.xml.mdx</string>
9366 <key>patterns</key>
9367 <array>
9368 <dict>
9369 <key>begin</key>
9370 <string>(^|\G)(\s*)(.*)</string>
9371 <key>while</key>
9372 <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string>
9373 <key>contentName</key>
9374 <string>meta.embedded.xml</string>
9375 <key>patterns</key>
9376 <array>
9377 <dict>
9378 <key>include</key>
9379 <string>text.xml</string>
9380 </dict>
9381 </array>
9382 </dict>
9383 </array>
9384 </dict>
9385 </array>
9386 </dict>
9387 <key>commonmark-code-fenced-yaml</key>
9388 <dict>
9389 <key>patterns</key>
9390 <array>
9391 <dict>
9392 <key>begin</key>
9393 <string>(?:^|\G)[\t ]*(`{3,})(?:[\t ]*((?i:jar\x2dmanifest|kaitai\x2dstruct|oasv2\x2dyaml|oasv3\x2dyaml|unity3d\x2dasset|yaml|yml|(?:.*\.)?(?:anim|asset|ksy|lkml|lookml|mat|meta|mir|prefab|raml|reek|rviz|sublime\x2dsyntax|syntax|unity|yaml\x2dtmlanguage|yaml\.sed|yml\.mysql)))(?:[\t ]+((?:[^\n\r`])+))?)(?:[\t ]*$)</string>
9394 <key>beginCaptures</key>
9395 <dict>
9396 <key>1</key>
9397 <dict>
9398 <key>name</key>
9399 <string>string.other.begin.code.fenced.mdx</string>
9400 </dict>
9401 <key>2</key>
9402 <dict>
9403 <key>name</key>
9404 <string>entity.name.function.mdx</string>
9405 <key>patterns</key>
9406 <array>
9407 <dict>
9408 <key>include</key>
9409 <string>#markdown-string</string>
9410 </dict>
9411 </array>
9412 </dict>
9413 <key>3</key>
9414 <dict>
9415 <key>patterns</key>
9416 <array>
9417 <dict>
9418 <key>include</key>
9419 <string>#markdown-string</string>
9420 </dict>
9421 </array>
9422 </dict>
9423 </dict>
9424 <key>end</key>
9425 <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string>
9426 <key>endCaptures</key>
9427 <dict>
9428 <key>1</key>
9429 <dict>
9430 <key>name</key>
9431 <string>string.other.end.code.fenced.mdx</string>
9432 </dict>
9433 </dict>
9434 <key>name</key>
9435 <string>markup.code.yaml.mdx</string>
9436 <key>patterns</key>
9437 <array>
9438 <dict>
9439 <key>begin</key>
9440 <string>(^|\G)(\s*)(.*)</string>
9441 <key>while</key>
9442 <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string>
9443 <key>contentName</key>
9444 <string>meta.embedded.yaml</string>
9445 <key>patterns</key>
9446 <array>
9447 <dict>
9448 <key>include</key>
9449 <string>source.yaml</string>
9450 </dict>
9451 </array>
9452 </dict>
9453 </array>
9454 </dict>
9455 <dict>
9456 <key>begin</key>
9457 <string>(?:^|\G)[\t ]*(~{3,})(?:[\t ]*((?i:jar\x2dmanifest|kaitai\x2dstruct|oasv2\x2dyaml|oasv3\x2dyaml|unity3d\x2dasset|yaml|yml|(?:.*\.)?(?:anim|asset|ksy|lkml|lookml|mat|meta|mir|prefab|raml|reek|rviz|sublime\x2dsyntax|syntax|unity|yaml\x2dtmlanguage|yaml\.sed|yml\.mysql)))(?:[\t ]+((?:[^\n\r])+))?)(?:[\t ]*$)</string>
9458 <key>beginCaptures</key>
9459 <dict>
9460 <key>1</key>
9461 <dict>
9462 <key>name</key>
9463 <string>string.other.begin.code.fenced.mdx</string>
9464 </dict>
9465 <key>2</key>
9466 <dict>
9467 <key>name</key>
9468 <string>entity.name.function.mdx</string>
9469 <key>patterns</key>
9470 <array>
9471 <dict>
9472 <key>include</key>
9473 <string>#markdown-string</string>
9474 </dict>
9475 </array>
9476 </dict>
9477 <key>3</key>
9478 <dict>
9479 <key>patterns</key>
9480 <array>
9481 <dict>
9482 <key>include</key>
9483 <string>#markdown-string</string>
9484 </dict>
9485 </array>
9486 </dict>
9487 </dict>
9488 <key>end</key>
9489 <string>(?:^|\G)[\t ]*(\1)(?:[\t ]*$)</string>
9490 <key>endCaptures</key>
9491 <dict>
9492 <key>1</key>
9493 <dict>
9494 <key>name</key>
9495 <string>string.other.end.code.fenced.mdx</string>
9496 </dict>
9497 </dict>
9498 <key>name</key>
9499 <string>markup.code.yaml.mdx</string>
9500 <key>patterns</key>
9501 <array>
9502 <dict>
9503 <key>begin</key>
9504 <string>(^|\G)(\s*)(.*)</string>
9505 <key>while</key>
9506 <string>(^|\G)(?![\t ]*([`~]{3,})[\t ]*$)</string>
9507 <key>contentName</key>
9508 <string>meta.embedded.yaml</string>
9509 <key>patterns</key>
9510 <array>
9511 <dict>
9512 <key>include</key>
9513 <string>source.yaml</string>
9514 </dict>
9515 </array>
9516 </dict>
9517 </array>
9518 </dict>
9519 </array>
9520 </dict>
9521 </dict>
9522 <key>scopeName</key>
9523 <string>source.mdx</string>
9524 <key>uuid</key>
9525 <string>fe65e2cd-7c73-4a27-8b5e-5902893626aa</string>
9526 </dict>
9527</plist>
\ No newline at end of file
src/pages/admin/index.mdo created+14
......@@ -0,0 +1,14 @@
1---
2meta:
3 title: paper clover admin
4---
5<main>
6
7# hi chloe
8
9remember that you're loved.
10
11- [question admin view](/admin/q+a)
12- [question inbox](/admin/q+a/inbox)
13
14</main>
src/pages/admin/index.mdx deleted-9
......@@ -1,9 +0,0 @@
1export default ({children}) => <main>{children}</main>
2export const meta = { title: 'paper clover admin' };
3
4# hi chloe
5
6remember that you're loved.
7
8- [question admin view](/admin/q+a)
9- [question inbox](/admin/q+a/inbox)
src/pages/license.mdo created+33
......@@ -0,0 +1,33 @@
1---
2meta:
3 title: paper clover's media license
4theme:
5 fg: "#ffe"
6 bg: "#434"
7 h1: "#f5f"
8 primary: "#f5f"
9---
10<main>
11
12# paper clover's media license
13
14copyright 2018-${new Date().getFullYear()} paper clover
15
16all rights reserved
17
18<br/>
19
20this license applies to all "media files" on paperclover.net, which specifically
21refers to downloadable files available within the [paperclover.net/file](/file) directory.
22
23permission is given to download, view, or otherwise store a copy of any media
24file. however, media files must not be uploaded, published, or otherwise
25distributed over the internet or other electronical communication devices.
26all uses of media files must be used for non-commercial purposes.
27
28paperclover.net provides media files as-is, without any warranty or liability.
29
30the above rules are in place to preserve my sanity.<br/>
31thank you for respecting them.
32
33</main>
src/pages/license.mdx deleted-31
......@@ -1,31 +0,0 @@
1export const CurrentYear = () => new Date().getFullYear();
2export default ({ content }) => <main>{content}</main>;
3export const theme = {
4 fg: '#ffe',
5 bg: '#434',
6 h1: '#f5f',
7};
8export const meta = {
9 title: "paper clover's media license"
10};
11
12# paper clover's media license
13
14copyright 2018-<CurrentYear/> paper clover
15
16all rights reserved
17
18<br/>
19
20this license applies to all "media files" on paperclover.net, which specifically
21refers to downloadable files available within the [paperclover.net/file](/file) directory.
22
23permission is given to download, view, or otherwise store a copy of any media
24file. however, media files must not be uploaded, published, or otherwise
25distributed over the internet or other electronical communication devices.
26all uses of media files must be used for non-commercial purposes.
27
28paperclover.net provides media files as-is, without any warranty or liability.
29
30the above rules are in place to preserve my sanity.<br/>
31thank you for respecting them.
src/pages/log.mdo created+413
......@@ -0,0 +1,413 @@
1---
2meta:
3 title: clover's log
4---
5import './log.css';
6static const repo = "${repo}";
7
8[go back to the home page](/)
9
10[git]: https://git.paperclover.net/
11[home infra]: https://git.paperclover.net/clo/home-infra
12[name paint bot]: https://git.paperclover.net/clo/discord-name-painter
13[next.js blog post]: /blog/webdev/one-year-next-app-router
14[progress.ts]: https://jsr.io/@clo/lib/doc/progress.ts
15[this site]: https://paperclover.net/
16[todo tracker]: https://git.paperclover.net/clo/todo-tracker
17[ts lie detector]: https://git.paperclover.net/clo/ts-lie-detector
18[evil inc]: https://evil.inc/
19[history of japan reanimated]: /history-of-japan-reanimated
20[react mutation]: /history-of-japan-reanimated
21
22<main>
23
24# clover's log
25
26these are like mini blog posts, but more in generally just what the heck i'm up to.
27
28## 2026-02-06
29
30spent yesterday and today cathing up with this page and preparing to get
31writing heavy on the blog. i also fixed a bug on the video player where it
32wouldnt work on latest chrome on non-av1 accelerated hardware.
33
34there were two bugs:
35
36- chrome without av1 and version >=142 would try and play the hls stream
37 natively. they made `video.canPlayType("application/x-mpegURL")` return
38 "maybe", which is total propaganda because chrome does not actually support
39 this type of stream. they support some insane subset that isn't real world
40 use cases.
41- hls polyfill was never being used because of a bundling mistake, so those
42 users would get the unoptimized original file.
43
44since i was testing windows 7, decided to also fix some IE9 bugs. the page
45works besides the video player and the fonts, which is all things i believe i
46could easily fix, so i opened new issues for those tasks.
47
48## 2026-02-04
49
50tags: [this site]
51
52i did a ton of random stuff to [this site], including the RSS feed and random
53bug fixes on the domain. trying to reduce the issue count on my forgejo.
54
55## 2026-02-03
56
57tags: [history of japan reanimated]
58
59immediately after the youtube premiere, i worked on my webpage of it. the live
60credits was a really fun touch that really completes things. i'm very happy
61with the final result.
62
63i also had to do a ton of work fixing the video player to properly show
64thumbnails on the video player. the biggest one was adding even more hell to
65the dash-av1 player. a issue that `dash.js` has is providing the first frame of
66media before the user presses "play." this doesn't happen with the hls or
67native players. so my workaround... delay attachment of the view.
68
69```
70player.initialize(null, dashFile, false);
71player.updateSettings({
72 streaming: { cacheInitSegments: true },
73});
74player.preload();
75
76video.addEventListener("play", function play() {
77 video.removeEventListener("play", play);
78
79 enableSafariAirplay(); // <-- another important hack
80 player.attachView(video);
81 onCloverVideoInit?.(id, video);
82});
83video.controls = true;
84```
85
86but on chrome you get cooked because a video without a source is not playable.
87so a second hack used to attach a fake source.
88
89```
90const duration = new Promise<number>((resolve) => {
91 player.on(dashjs.MediaPlayer.events.MANIFEST_LOADED, (e) => {
92 const duration = e.data.mediaPresentationDuration;
93 resolve(duration); // seconds
94 });
95});
96mediaSource = new MediaSource();
97mediaSource.addEventListener("sourceopen", () => {
98 duration.then((duration) => {
99 mediaSource = mediaSource!; // typescript assertion
100 mediaSource.duration = duration;
101 const sb = mediaSource.addSourceBuffer('video/webm; codecs="vp8"');
102 const oneFrame = Uint8Array.from(
103 atob("GkXfo59ChoEBQveBAULygQRC84EIQoKEd2VibUKHgQJChYECGFOAZwEAAAAAAAITEU2bdLpNu4tTq4QVSalmU6yBoU27i1OrhBZUrmtTrIHWTbuMU6uEElTDZ1OsggEjTbuMU6uEHFO7a1OsggH97AEAAAAAAABZ" + "A".repeat(119) + "VSalmsCrXsYMPQkBNgIxMYXZmNjIuMy4xMDBXQYxMYXZmNjIuMy4xMDBEiYhAXgAAAAAAABZUrmvIrgEAAAAAAAA/14EBc8WIDV7YG1KxA/CcgQAitZyDdW5kiIEAhoVWX1ZQOIOBASPjg4QCYloA4JCwgQG6gQGagQJVsIRVuYEBElTDZ/tzc59jwIBnyJlFo4dFTkNPREVSRIeMTGF2ZjYyLjMuMTAwc3PWY8CLY8WIDV7YG1KxA/BnyKFFo4dFTkNPREVSRIeUTGF2YzYyLjExLjEwMCBsaWJ2cHhnyKFFo4hEVVJBVElPTkSHkzAwOjAwOjAwLjEyMDAwMDAwMAAfQ7Z11eeBAKOigQAAgBACAJ0BKgEAAQALxwiFhYiFhIg/ggAMDWAA/ua1AKOVgQAoALEBAC8R/AAYABhYL/QAJAAAo5WBAFAAsQEALxH8ABgAGFgv9AAkAAAcU7trkbuPs4EAt4r3gQHxggGj8IED"),
104 (x) => x.charCodeAt(0),
105 );
106 sb.appendBuffer(oneFrame);
107 });
108});
109objectUrl = URL.createObjectURL(mediaSource);
110
111mainSource = document.createElement("source");
112mainSource.src = objectUrl;
113video.appendChild(mainSource);
114```
115
116the payload within contains a single webm vp8 frame, one pixel by one pixel.
117this is enough to get chrome to shut up. now the experience is nearly perfect,
118with browser support all the way to the ancient firefox version on my old
119laptop, to modern Apple Silicon Macs.
120
121## 2026-01-31
122
123tags: [history of japan reanimated]
124
125main section of animation for the project was already done, but i wanted to
126make some needed adjustments to my scene. so i had been doing those for a few
127days, as well as animating an extra 25 seconds for the outro section. it was
128very fun doing that process, since i actually grabbed an old macbook i had in
129storage to take the textedit screenshot. more details on this entire process
130will be in the project page.
131
132## 2026-01-30
133
134tags: [react mutation]
135
136at work, one of my coworkers was complaining about my helper functions for
137TanStack Query's mutation system. the conclusion was that the mutation system
138we were building on was flawed and not enjoyable to use. so i spent a few days
139making an alternative library. it's on the JSR: [@clo/react-mutation](https://jsr.io/@clo/react-mutation).
140
141this project was really fun because of how much the API surface changed as i
142started staging the library into our actual code. the experience has shaped how
143i want to go about my upcoming blog post for the better.
144
145## 2026-01-25
146
147tags: [ts lie detector], [todo tracker]
148
149added a trivial binary to the lie detector, `tsld-node`, which is like `ts-node` or `tsx` but it runs with the lie detector.
150
151for todo tracker, it's been vibe coded to a point where the sitegen repo gets
152through all commits, but there are still some issues with missing TODOs. i haven't really had time to prioritize this, even with an ai agent writing most of it, since even then i have to review the progress of it, so it's just not worth my time until other projects pull through.
153
154## 2026-01-24
155
156tags: [git], [home infra]
157
158i moved forgejo off of sqlite and onto postgres. the only motiviation behind this was to easily backdate the repositories i had imported: [name paint bot] and a scripting language compiler i wrote. i was more comfortable doing this on a real database server than just editing the file.
159
160the migration took a bit for me to figure out, but last year someone named Sven [did the same thing](https://sven-seeberg.de/wp/?p=1213), and found this `pgloader` command with the critical `data only` clause.
161
162```
163echo "LOAD DATABASE
164FROM sqlite:///root/forgejo.db
165INTO postgresql://forgejo:$POSTGRES_PASSWORD_FORGEJO@postgres/forgejo
166WITH data only, reset sequences, prefetch rows = 10000
167SET work_mem TO '16MB', maintenance_work_mem TO '512MB';" > ./pgloader-command
168
169pgloader ./pgloader-command
170```
171
172## 2026-01-18
173
174tags: [git], [home infra]
175
176i finally setup system integrated ssh push, but with a twist.
177
178the first problem is having two ssh servers on the same machine, one for the
179host, and another for Forgejo. this means that one of them had to live on
180another port, so i chose to move the host. but it kind of just sucks to use
181this. the proper solution is to use a custom config to direct the `git` user to
182the right place.
183
184what made this harder is i actually had two git instances; the second one is
185for a temporary infrastructure i'm running for [evil inc] until the
186organization gets dedicated hardware (more about this in a future post). so
187even if i routed `git` specially, it still wouldnt know which git server to go
188to.
189
190**the solution**: write a custom "router" script that generates an
191`authorized_keys` file based on which git instances actually have a key, then
192the line within the `authorized_keys` forces a special wrapper which intercepts
193the targetted git repository, routing it to the git instance with it.
194
195the sshd config looks like
196
197```
198Match User git
199 AuthorizedKeysCommand /bin/python /mnt/storage1/apps/home-infra/config/forgejo/ssh/keys.py %u %t %k
200 AuthorizedKeysCommandUser git
201```
202
203it is convenient because `AuthorizedKeysCommand` is run on every connection. it produces a file with zero or one valid keys:
204
205```
206command="/mnt/storage1/apps/home-infra/config/forgejo/ssh/route.sh --clover 1 --evil 2",no-port-forwarding,...,restrict ssh-ed25519 AAAAC3NzaC...
207```
208
209and then the route script does this shit:
210
211```
212if [ -d "/mnt/storage1/apps/forgejo/git/repositories/${repo}" ]; then
213 [ -z "$clover_id" ] && echo "ssh key is not configured for repository on git.paperclover.net" >&2 && exit 1
214 exec sudo docker exec -i -u git forgejo /usr/bin/env SSH_ORIGINAL_COMMAND="$SSH_ORIGINAL_COMMAND" /usr/local/bin/forgejo --config=/custom/conf/app.ini serv key-"$clover_id"
215elif [ -d "/mnt/storage1/apps/evil-infra/forgejo/git/repositories/${repo}" ]; then
216 [ -z "$evil_id" ] && echo "ssh key is not configured for repository on git.evil.inc" >&2 && exit 1
217 exec sudo docker exec -i -u git evil-forgejo /usr/bin/env SSH_ORIGINAL_COMMAND="$SSH_ORIGINAL_COMMAND" /usr/local/bin/forgejo --config=/custom/conf/app.ini serv key-"$evil_id"
218else
219 echo "repo not found" >&2
220 exit 1
221fi
222```
223
224it works beautifully. [see the whole patch for more info](https://git.paperclover.net/clo/home-infra/commit/e402d6ef71dfc310caeeb4d3579bddd9d0710594)
225
226## 2026-01-14
227
228tags: [next.js blog post], [this site]
229
230general housekeeping. getting this activity page up. getting the spanish
231translation by my good friend trubiso up. things are looking really cozy.
232
233## 2026-01-12
234
235tags: [album]
236
237i started more music work. i've gotten better at lyric writing, phrasing this
238new song as a sort of "adventure". felt for one of the first times that i was
239doing worldbuilding in a song. the imagery is that good.
240
241## 2026-01-11
242
243tags: [progress.ts], [todo tracker]
244
245i finished streaming io on the @clo/lib/[progress.ts]. very proud of it. my git
246commits describe the tech better than me reiterating.
247
248> ## feat(lib/progress): implement streaming wire protocol
249> resolves `#47`
250>
251> `encodeByteStream` converts these events into a `ReadableStream`. by
252> batching events together, the stream contents remain small, that way the
253> code that constructs progress nodes do not have to worry about calling
254> many setters at once, it gets debounced be the serializer. stream
255> backpressure causes larger time-gaps to be batched (smaller). this
256> enables servers to respond with rich progress.
257>
258> ```
259> const root = new progress.Root();
260> doActionWithProgress(root).then(root.end, root.error);
261> // streaming clients indicate a header
262> if (req.headers.get("Accept")?.includes(progress.contentType))
263> return new Response(progress.encodeByteStream(root), {
264> headers: { 'Content-Type': progress.contentType },
265> });
266> // to support non-streaming clients
267> return Response.json(await root.asPromise());
268> ```
269>
270> and `decodeByteStream` on the client:
271>
272> ```
273> const output = document.getElementById("output");
274> const res = await fetch(...);
275> if (!res.ok) throw ...;
276> const root = new progress.Root();
277> root.on("change", (active) => {
278> output.innerText = ansi.strip(progress.formatAnsi(
279> performance.now(),
280> active,
281> ));
282> });
283> const result = await progress.decodeByteStream(res.body, root);
284> output.innerText = JSON.stringify(result);
285> ```
286>
287> there is currently no document bindings, but i plan to. additionally, a
288> React hook is very trivial to implement for this -- but that is
289> unplanned for this repository. for transports that require JSON or
290> UTF-8, there is `encodeEventStream` which returns a `ReadableStream` of
291> JSON objects which can be compressed at the developer's discretion.
292
293> ## feat(lib/progress): headless rendering + time estimation
294> node signaling is done by providing a `progress.Root` to every node,
295> dispatching events to it when the node changes. the root is connected to
296> an observer to construct a UI out of it. there are two apis planned:
297>
298> - `attachToScreen` binds a root to a TTY screen (via the log.Widget API).
299> the primary use of this is to implement the top level `progress.start`.
300>
301> - a serialization system that allows transmitting a `Root` over a wire.
302> this commit was going to include this but it is an unexpectedly large
303> component.
304>
305> - potentially a browser binding like `attachToDocument`. this will not
306> be added in this patch.
307>
308> additionally, resolves #33 by implementing `estimatedTime`
309
310i also did a large part of the work to create a "code todo tracking" tool. i
311would say it's about half done, since the second half is simply fixing all of
312the little bugs there are. most of this code is currently ai-generated, but
313with me manually coming in to write interfaces and the modular program
314architecture. then i synthesize the code and the tests. this was basically just
315going on ambiently while [progress.ts] was in progress.
316
317## 2026-01-09
318
319tags: [home infra]
320
321finished SSO sub-project. im happy with the setup i used to protect internal
322services, such as pgadmin and qbittorrent. it's a caddy snippet that i can
323re-use very easily.
324
325```
326(reverse_proxy_auth) {
327 handle /snow.oauth2/* {
328 reverse_proxy "http://forward-auth" {
329 header_up X-Real-IP {remote_host}
330 header_up X-Forwarded-Uri {uri}
331 }
332 }
333 handle {
334 forward_auth "http://forward-auth" {
335 uri /snow.oauth2/auth
336 header_up X-Real-IP {remote_host}
337 @error status 401
338 handle_response @error {
339 redir * /snow.oauth2/sign_in?rd={scheme}://{host}{uri}
340 }
341 @valid_group header X-Auth-Request-Groups *role:{args[1]}*
342 handle_response @valid_group {
343 method {method}
344 rewrite {uri}
345 reverse_proxy {args[0]} {
346 header_up Cookie ([^;]*?)\s*_oauth2_proxy_\d=[^;]*(;?.*) "$1$2"
347 {block}
348 }
349 }
350 handle_response {
351 rewrite /403.html
352 file_server {
353 status 403
354 root /etc/caddy
355 }
356 }
357 }
358 }
359}
360
361# usage
362pg.{$HOME_DOMAIN} {
363 import reverse_proxy_auth "http://pgadmin" admin
364}
365qbt.{$HOME_DOMAIN} {
366 import reverse_proxy_auth "http://qbittorrent" media-manage
367}
368```
369
370
371## 2026-01-04
372
373tags: [home infra]
374
375working on SSO for my internal services. for context, i have about 12
376self-hosted services running, half of which i allow my friends to access.
377currently, this is done through manually creating an account on such service
378(jellyfin, forgejo), but many are done through a caddy rule. in the interest of
379making my password manager less confused (ip vs domain, subdomain etc), i'm
380slowly reducing this setup to a single sign in page.
381
382to do this, i am using https://keycloak.org, which supports openid connect
383(how i will configure forgejo and jellyfin), as well as a separate service to
384provide forward auth proxying (how i protect services like copyparty,
385syncthing, pgadmin, and many more). i tried authelia beforehand, but i really
386do not recommend them due to how hard it is to configure, passkeys being
387annoying to setup, and limited themes. i also dont recommend authentik, but i
388couldnt figure out how to even start using it after i installed it.
389
390keycloak is a bit stupid on config. as all the config lies in the postgres
391database, i can't use a config file to setup the primary realm. so instead, i
392have this huge python script to use the API to upsert the configuration in.
393this works pretty well, and means that for locally running the infrastructure
394for testing, i can get the config to be the same (useful if you brick
395keycloak, which is pretty easy to do).
396
397## 2026-01-02
398
399tags: [home infra], [git], [name paint bot] show
400
401i deleted all my github repositories except four: my "readme", a bug
402reproduction repo, the mirror for [ts lie detector], and a shared private repo
403with someone that is load bearing. in this process, i've moved all the projects
404to my [forgejo instance][git].
405
406with this, [name paint bot], one of my few remaining projects that is still
407active, moves to that forgejo instance using their github migrator. some of my
408private projects, like my pet scripting language, were migrated as well. it
409feels more alive on my site because of the theming and per-repo icons.
410
411after a year of forgejo, i am really happy with how it treats me.
412
413</main>
src/pages/log.mdx deleted-407
......@@ -1,407 +0,0 @@
1import './log.css';
2export default ({ content }) => <main>{content}</main>
3export const meta = { title: 'clover\'s log' };
4
5[go back to the home page](/)
6
7[git]: https://git.paperclover.net/
8[home infra]: https://git.paperclover.net/clo/home-infra
9[name paint bot]: https://git.paperclover.net/clo/discord-name-painter
10[next.js blog post]: /blog/webdev/one-year-next-app-router
11[progress.ts]: https://jsr.io/@clo/lib/doc/progress.ts
12[this site]: https://paperclover.net/
13[todo tracker]: https://git.paperclover.net/clo/todo-tracker
14[ts lie detector]: https://git.paperclover.net/clo/ts-lie-detector
15[evil inc]: https://evil.inc/
16[history of japan reanimated]: /history-of-japan-reanimated
17[react mutation]: /history-of-japan-reanimated
18
19# clover's log
20
21these are like mini blog posts, but more in generally just what the heck i'm up to.
22
23## 2026-02-06
24
25spent yesterday and today cathing up with this page and preparing to get
26writing heavy on the blog. i also fixed a bug on the video player where it
27wouldnt work on latest chrome on non-av1 accelerated hardware.
28
29there were two bugs:
30
31- chrome without av1 and version >=142 would try and play the hls stream
32 natively. they made `video.canPlayType("application/x-mpegURL")` return
33 "maybe", which is total propaganda because chrome does not actually support
34 this type of stream. they support some insane subset that isn't real world
35 use cases.
36- hls polyfill was never being used because of a bundling mistake, so those
37 users would get the unoptimized original file.
38
39since i was testing windows 7, decided to also fix some IE9 bugs. the page
40works besides the video player and the fonts, which is all things i believe i
41could easily fix, so i opened new issues for those tasks.
42
43## 2026-02-04
44
45tags: [this site]
46
47i did a ton of random stuff to [this site], including the RSS feed and random
48bug fixes on the domain. trying to reduce the issue count on my forgejo.
49
50## 2026-02-03
51
52tags: [history of japan reanimated]
53
54immediately after the youtube premiere, i worked on my webpage of it. the live
55credits was a really fun touch that really completes things. i'm very happy
56with the final result.
57
58i also had to do a ton of work fixing the video player to properly show
59thumbnails on the video player. the biggest one was adding even more hell to
60the dash-av1 player. a issue that `dash.js` has is providing the first frame of
61media before the user presses "play." this doesn't happen with the hls or
62native players. so my workaround... delay attachment of the view.
63
64```
65player.initialize(null, dashFile, false);
66player.updateSettings({
67 streaming: { cacheInitSegments: true },
68});
69player.preload();
70
71video.addEventListener("play", function play() {
72 video.removeEventListener("play", play);
73
74 enableSafariAirplay(); // <-- another important hack
75 player.attachView(video);
76 onCloverVideoInit?.(id, video);
77});
78video.controls = true;
79```
80
81but on chrome you get cooked because a video without a source is not playable.
82so a second hack used to attach a fake source.
83
84```
85const duration = new Promise<number>((resolve) => {
86 player.on(dashjs.MediaPlayer.events.MANIFEST_LOADED, (e) => {
87 const duration = e.data.mediaPresentationDuration;
88 resolve(duration); // seconds
89 });
90});
91mediaSource = new MediaSource();
92mediaSource.addEventListener("sourceopen", () => {
93 duration.then((duration) => {
94 mediaSource = mediaSource!; // typescript assertion
95 mediaSource.duration = duration;
96 const sb = mediaSource.addSourceBuffer('video/webm; codecs="vp8"');
97 const oneFrame = Uint8Array.from(
98 atob("GkXfo59ChoEBQveBAULygQRC84EIQoKEd2VibUKHgQJChYECGFOAZwEAAAAAAAITEU2bdLpNu4tTq4QVSalmU6yBoU27i1OrhBZUrmtTrIHWTbuMU6uEElTDZ1OsggEjTbuMU6uEHFO7a1OsggH97AEAAAAAAABZ" + "A".repeat(119) + "VSalmsCrXsYMPQkBNgIxMYXZmNjIuMy4xMDBXQYxMYXZmNjIuMy4xMDBEiYhAXgAAAAAAABZUrmvIrgEAAAAAAAA/14EBc8WIDV7YG1KxA/CcgQAitZyDdW5kiIEAhoVWX1ZQOIOBASPjg4QCYloA4JCwgQG6gQGagQJVsIRVuYEBElTDZ/tzc59jwIBnyJlFo4dFTkNPREVSRIeMTGF2ZjYyLjMuMTAwc3PWY8CLY8WIDV7YG1KxA/BnyKFFo4dFTkNPREVSRIeUTGF2YzYyLjExLjEwMCBsaWJ2cHhnyKFFo4hEVVJBVElPTkSHkzAwOjAwOjAwLjEyMDAwMDAwMAAfQ7Z11eeBAKOigQAAgBACAJ0BKgEAAQALxwiFhYiFhIg/ggAMDWAA/ua1AKOVgQAoALEBAC8R/AAYABhYL/QAJAAAo5WBAFAAsQEALxH8ABgAGFgv9AAkAAAcU7trkbuPs4EAt4r3gQHxggGj8IED"),
99 (x) => x.charCodeAt(0),
100 );
101 sb.appendBuffer(oneFrame);
102 });
103});
104objectUrl = URL.createObjectURL(mediaSource);
105
106mainSource = document.createElement("source");
107mainSource.src = objectUrl;
108video.appendChild(mainSource);
109```
110
111the payload within contains a single webm vp8 frame, one pixel by one pixel.
112this is enough to get chrome to shut up. now the experience is nearly perfect,
113with browser support all the way to the ancient firefox version on my old
114laptop, to modern Apple Silicon Macs.
115
116## 2026-01-31
117
118tags: [history of japan reanimated]
119
120main section of animation for the project was already done, but i wanted to
121make some needed adjustments to my scene. so i had been doing those for a few
122days, as well as animating an extra 25 seconds for the outro section. it was
123very fun doing that process, since i actually grabbed an old macbook i had in
124storage to take the textedit screenshot. more details on this entire process
125will be in the project page.
126
127## 2026-01-30
128
129tags: [react mutation]
130
131at work, one of my coworkers was complaining about my helper functions for
132TanStack Query's mutation system. the conclusion was that the mutation system
133we were building on was flawed and not enjoyable to use. so i spent a few days
134making an alternative library. it's on the JSR: [@clo/react-mutation](https://jsr.io/@clo/react-mutation).
135
136this project was really fun because of how much the API surface changed as i
137started staging the library into our actual code. the experience has shaped how
138i want to go about my upcoming blog post for the better.
139
140## 2026-01-25
141
142tags: [ts lie detector], [todo tracker]
143
144added a trivial binary to the lie detector, `tsld-node`, which is like `ts-node` or `tsx` but it runs with the lie detector.
145
146for todo tracker, it's been vibe coded to a point where the sitegen repo gets
147through all commits, but there are still some issues with missing TODOs. i haven't really had time to prioritize this, even with an ai agent writing most of it, since even then i have to review the progress of it, so it's just not worth my time until other projects pull through.
148
149## 2026-01-24
150
151tags: [git], [home infra]
152
153i moved forgejo off of sqlite and onto postgres. the only motiviation behind this was to easily backdate the repositories i had imported: [name paint bot] and a scripting language compiler i wrote. i was more comfortable doing this on a real database server than just editing the file.
154
155the migration took a bit for me to figure out, but last year someone named Sven [did the same thing](https://sven-seeberg.de/wp/?p=1213), and found this `pgloader` command with the critical `data only` clause.
156
157```
158echo "LOAD DATABASE
159FROM sqlite:///root/forgejo.db
160INTO postgresql://forgejo:$POSTGRES_PASSWORD_FORGEJO@postgres/forgejo
161WITH data only, reset sequences, prefetch rows = 10000
162SET work_mem TO '16MB', maintenance_work_mem TO '512MB';" > ./pgloader-command
163
164pgloader ./pgloader-command
165```
166
167## 2026-01-18
168
169tags: [git], [home infra]
170
171i finally setup system integrated ssh push, but with a twist.
172
173the first problem is having two ssh servers on the same machine, one for the
174host, and another for Forgejo. this means that one of them had to live on
175another port, so i chose to move the host. but it kind of just sucks to use
176this. the proper solution is to use a custom config to direct the `git` user to
177the right place.
178
179what made this harder is i actually had two git instances; the second one is
180for a temporary infrastructure i'm running for [evil inc] until the
181organization gets dedicated hardware (more about this in a future post). so
182even if i routed `git` specially, it still wouldnt know which git server to go
183to.
184
185**the solution**: write a custom "router" script that generates an
186`authorized_keys` file based on which git instances actually have a key, then
187the line within the `authorized_keys` forces a special wrapper which intercepts
188the targetted git repository, routing it to the git instance with it.
189
190the sshd config looks like
191
192```
193Match User git
194 AuthorizedKeysCommand /bin/python /mnt/storage1/apps/home-infra/config/forgejo/ssh/keys.py %u %t %k
195 AuthorizedKeysCommandUser git
196```
197
198it is convenient because `AuthorizedKeysCommand` is run on every connection. it produces a file with zero or one valid keys:
199
200```
201command="/mnt/storage1/apps/home-infra/config/forgejo/ssh/route.sh --clover 1 --evil 2",no-port-forwarding,...,restrict ssh-ed25519 AAAAC3NzaC...
202```
203
204and then the route script does this shit:
205
206```
207if [ -d "/mnt/storage1/apps/forgejo/git/repositories/${repo}" ]; then
208 [ -z "$clover_id" ] && echo "ssh key is not configured for repository on git.paperclover.net" >&2 && exit 1
209 exec sudo docker exec -i -u git forgejo /usr/bin/env SSH_ORIGINAL_COMMAND="$SSH_ORIGINAL_COMMAND" /usr/local/bin/forgejo --config=/custom/conf/app.ini serv key-"$clover_id"
210elif [ -d "/mnt/storage1/apps/evil-infra/forgejo/git/repositories/${repo}" ]; then
211 [ -z "$evil_id" ] && echo "ssh key is not configured for repository on git.evil.inc" >&2 && exit 1
212 exec sudo docker exec -i -u git evil-forgejo /usr/bin/env SSH_ORIGINAL_COMMAND="$SSH_ORIGINAL_COMMAND" /usr/local/bin/forgejo --config=/custom/conf/app.ini serv key-"$evil_id"
213else
214 echo "repo not found" >&2
215 exit 1
216fi
217```
218
219it works beautifully. [see the whole patch for more info](https://git.paperclover.net/clo/home-infra/commit/e402d6ef71dfc310caeeb4d3579bddd9d0710594)
220
221## 2026-01-14
222
223tags: [next.js blog post], [this site]
224
225general housekeeping. getting this activity page up. getting the spanish
226translation by my good friend trubiso up. things are looking really cozy.
227
228## 2026-01-12
229
230tags: [album]
231
232i started more music work. i've gotten better at lyric writing, phrasing this
233new song as a sort of "adventure". felt for one of the first times that i was
234doing worldbuilding in a song. the imagery is that good.
235
236## 2026-01-11
237
238tags: [progress.ts], [todo tracker]
239
240i finished streaming io on the @clo/lib/[progress.ts]. very proud of it. my git
241commits describe the tech better than me reiterating.
242
243> ## feat(lib/progress): implement streaming wire protocol
244> resolves `#47`
245>
246> `encodeByteStream` converts these events into a `ReadableStream`. by
247> batching events together, the stream contents remain small, that way the
248> code that constructs progress nodes do not have to worry about calling
249> many setters at once, it gets debounced be the serializer. stream
250> backpressure causes larger time-gaps to be batched (smaller). this
251> enables servers to respond with rich progress.
252>
253> ```
254> const root = new progress.Root();
255> doActionWithProgress(root).then(root.end, root.error);
256> // streaming clients indicate a header
257> if (req.headers.get("Accept")?.includes(progress.contentType))
258> return new Response(progress.encodeByteStream(root), {
259> headers: { 'Content-Type': progress.contentType },
260> });
261> // to support non-streaming clients
262> return Response.json(await root.asPromise());
263> ```
264>
265> and `decodeByteStream` on the client:
266>
267> ```
268> const output = document.getElementById("output");
269> const res = await fetch(...);
270> if (!res.ok) throw ...;
271> const root = new progress.Root();
272> root.on("change", (active) => {
273> output.innerText = ansi.strip(progress.formatAnsi(
274> performance.now(),
275> active,
276> ));
277> });
278> const result = await progress.decodeByteStream(res.body, root);
279> output.innerText = JSON.stringify(result);
280> ```
281>
282> there is currently no document bindings, but i plan to. additionally, a
283> React hook is very trivial to implement for this -- but that is
284> unplanned for this repository. for transports that require JSON or
285> UTF-8, there is `encodeEventStream` which returns a `ReadableStream` of
286> JSON objects which can be compressed at the developer's discretion.
287
288> ## feat(lib/progress): headless rendering + time estimation
289> node signaling is done by providing a `progress.Root` to every node,
290> dispatching events to it when the node changes. the root is connected to
291> an observer to construct a UI out of it. there are two apis planned:
292>
293> - `attachToScreen` binds a root to a TTY screen (via the log.Widget API).
294> the primary use of this is to implement the top level `progress.start`.
295>
296> - a serialization system that allows transmitting a `Root` over a wire.
297> this commit was going to include this but it is an unexpectedly large
298> component.
299>
300> - potentially a browser binding like `attachToDocument`. this will not
301> be added in this patch.
302>
303> additionally, resolves #33 by implementing `estimatedTime`
304
305i also did a large part of the work to create a "code todo tracking" tool. i
306would say it's about half done, since the second half is simply fixing all of
307the little bugs there are. most of this code is currently ai-generated, but
308with me manually coming in to write interfaces and the modular program
309architecture. then i synthesize the code and the tests. this was basically just
310going on ambiently while [progress.ts] was in progress.
311
312## 2026-01-09
313
314tags: [home infra]
315
316finished SSO sub-project. im happy with the setup i used to protect internal
317services, such as pgadmin and qbittorrent. it's a caddy snippet that i can
318re-use very easily.
319
320```
321(reverse_proxy_auth) {
322 handle /snow.oauth2/* {
323 reverse_proxy "http://forward-auth" {
324 header_up X-Real-IP {remote_host}
325 header_up X-Forwarded-Uri {uri}
326 }
327 }
328 handle {
329 forward_auth "http://forward-auth" {
330 uri /snow.oauth2/auth
331 header_up X-Real-IP {remote_host}
332 @error status 401
333 handle_response @error {
334 redir * /snow.oauth2/sign_in?rd={scheme}://{host}{uri}
335 }
336 @valid_group header X-Auth-Request-Groups *role:{args[1]}*
337 handle_response @valid_group {
338 method {method}
339 rewrite {uri}
340 reverse_proxy {args[0]} {
341 header_up Cookie ([^;]*?)\s*_oauth2_proxy_\d=[^;]*(;?.*) "$1$2"
342 {block}
343 }
344 }
345 handle_response {
346 rewrite /403.html
347 file_server {
348 status 403
349 root /etc/caddy
350 }
351 }
352 }
353 }
354}
355
356# usage
357pg.{$HOME_DOMAIN} {
358 import reverse_proxy_auth "http://pgadmin" admin
359}
360qbt.{$HOME_DOMAIN} {
361 import reverse_proxy_auth "http://qbittorrent" media-manage
362}
363```
364
365
366## 2026-01-04
367
368tags: [home infra]
369
370working on SSO for my internal services. for context, i have about 12
371self-hosted services running, half of which i allow my friends to access.
372currently, this is done through manually creating an account on such service
373(jellyfin, forgejo), but many are done through a caddy rule. in the interest of
374making my password manager less confused (ip vs domain, subdomain etc), i'm
375slowly reducing this setup to a single sign in page.
376
377to do this, i am using https://keycloak.org, which supports openid connect
378(how i will configure forgejo and jellyfin), as well as a separate service to
379provide forward auth proxying (how i protect services like copyparty,
380syncthing, pgadmin, and many more). i tried authelia beforehand, but i really
381do not recommend them due to how hard it is to configure, passkeys being
382annoying to setup, and limited themes. i also dont recommend authentik, but i
383couldnt figure out how to even start using it after i installed it.
384
385keycloak is a bit stupid on config. as all the config lies in the postgres
386database, i can't use a config file to setup the primary realm. so instead, i
387have this huge python script to use the API to upsert the configuration in.
388this works pretty well, and means that for locally running the infrastructure
389for testing, i can get the config to be the same (useful if you brick
390keycloak, which is pretty easy to do).
391
392## 2026-01-02
393
394tags: [home infra], [git], [name paint bot] show
395
396i deleted all my github repositories except four: my "readme", a bug
397reproduction repo, the mirror for [ts lie detector], and a shared private repo
398with someone that is load bearing. in this process, i've moved all the projects
399to my [forgejo instance][git].
400
401with this, [name paint bot], one of my few remaining projects that is still
402active, moves to that forgejo instance using their github migrator. some of my
403private projects, like my pet scripting language, were migrated as well. it
404feels more alive on my site because of the theming and per-repo icons.
405
406after a year of forgejo, i am really happy with how it treats me.
407
src/q+a/clover-markdown.tsx deleted-271
......@@ -1,271 +0,0 @@
1function assert(condition: any, message?: string): asserts condition {
2 if (!condition) {
3 throw new Error(message || "Assertion failed");
4 }
5}
6
7export const customRules = defaultRules.clone();
8
9/** A question mention, formatted like `#112233445566` */
10customRules.insertBefore("em", {
11 name: "mentionQuestion",
12 match: inlineRegex(/^#([0-9]{10})(?:[0-9]{2})?/),
13 parse(capture) {
14 return {
15 id: capture[1],
16 };
17 },
18});
19
20/** A question mention, formatted like `@pheonix-write` */
21customRules.insertBefore("em", {
22 name: "mentionArtifact",
23 match: inlineRegex(/^@([a-z0-9-]+)/i),
24 parse(capture) {
25 return {
26 id: capture[1],
27 };
28 },
29});
30
31/** The censor bar used for past deadnaming: #name# and #username# */
32customRules.insertBefore("em", {
33 name: "deadname",
34 match: inlineRegex(/^#(name|username)#/i),
35 parse(capture) {
36 return {
37 kind: capture[1],
38 };
39 },
40});
41
42/** html block. allows arbitrary html, but rn only as an answer. formatted like `@html content here` */
43customRules.insertBefore("paragraph", {
44 name: "html",
45 match: blockRegex(/^@html ((?:[^\n]|\n(?! *\n))+)(?:\n *)+\n/),
46 parse(capture) {
47 return {
48 data: capture[1],
49 };
50 },
51});
52
53/** for server-rendered search, mark can highlight the seen word */
54customRules.insertBefore("paragraph", {
55 name: "mark",
56 match: inlineRegex(/^<!!mark!!>(.*?)<!!mark!!>/),
57 parse: parseCaptureInline,
58});
59
60/** Adjust autolinking to use my own components if possible */
61customRules.insertBefore("url", {
62 name: "autolink_mention",
63 match: inlineRegex(
64 /^https?:\/\/(?:paperclover\.net|localhost:\d+)\/([^\s<]+[^<.,:;"')\]\s])/,
65 ),
66 parse(capture) {
67 const qaMatch = capture[1].match(/^q\+a\/([0-9]{10}(?:[0-9]{2})?)/);
68 if (qaMatch) {
69 return {
70 type: "mentionQuestion",
71 id: qaMatch[1],
72 };
73 }
74
75 const fileMatch = capture[1].match(/^file\/([^#?]*)/);
76 if (fileMatch) {
77 // const file = MediaFile.getByPath(decodeURIComponent(fileMatch[1]));
78 // if (file) {
79 // return {
80 // type: "file",
81 // file,
82 // };
83 // }
84 }
85
86 return {
87 type: "link",
88 content: [
89 {
90 type: "text",
91 content: capture[1],
92 },
93 ],
94 target: capture[1],
95 title: undefined,
96 };
97 },
98});
99
100export const questionRules = customRules.clone();
101
102questionRules.remove("heading");
103
104/** Input paragraphs. any paragraph prefixed with `i:` */
105questionRules.insertBefore("paragraph", {
106 name: "question",
107 match: blockRegex(/^(?:q: [^\n]+(?:\n|$))+\s*/),
108 parse(capture, parse, state) {
109 const lines = capture[0]
110 .trim()
111 .split("\n")
112 .map((line) => line.trim())
113 .filter((x) => x.length > 0)
114 .map((x) => {
115 assert(x.startsWith("q: "));
116 return x.slice(3);
117 });
118 const content = lines
119 .map((line, i) => {
120 const parsed = parseInline(parse, line, state);
121 if (i < lines.length - 1) {
122 parsed.push({ type: "br" });
123 }
124 return parsed;
125 })
126 .flat();
127 return {
128 content,
129 };
130 },
131});
132
133const cloverParser = createParser(questionRules);
134
135const basicNodeTagNames: Record<string, string> = {
136 mark: "mark",
137 question: "q-",
138 paragraph: "p",
139 hr: "hr",
140 blockQuote: "blockquote",
141 em: "em",
142 strong: "strong",
143 u: "u",
144 del: "del",
145 inlineCode: "code",
146 br: "br",
147};
148
149function BasicNode(node: ASTNode, children: any) {
150 const E = basicNodeTagNames[node.type];
151 return <E>{children}</E>;
152}
153
154function ListRenderer(node: ASTNode, children: any[]) {
155 const T = node.ordered ? "ol" : "ul";
156 return (
157 <T>
158 {children.map((child) => <li>{child}</li>)}
159 </T>
160 );
161}
162
163function MarkdownLink(node: ASTNode, children: any[]) {
164 return <a href={node.target}>{children}</a>;
165}
166
167function Deadname(node: ASTNode) {
168 return <span class="dead">old {node.kind}</span>;
169}
170
171function QuestionRef(node: ASTNode) {
172 if (node.id.length === 12) node.id = node.id.slice(0, 10);
173 const timestamp = questionIdToTimestamp(node.id);
174 if (!timestamp) {
175 console.error(`Invalid question ref: ${node.id}`);
176 return <span class="question-ref">[Invalid Question Ref: #{node.id}]</span>;
177 }
178 return (
179 <a class="custom question-ref" href={`/q+a/${node.id}`}>
180 {formatQuestionTimestamp(timestamp)}
181 </a>
182 );
183}
184
185function ArtifactRef(node: ASTNode) {
186 const artifact = artifactMap[node.id];
187 if (!artifact) {
188 return <span class="artifact">[Invalid Artifact Ref: @{node.id}]</span>;
189 }
190 const [title, url, type] = artifact;
191 if (!url) {
192 return <span>{title}</span>;
193 }
194 return (
195 <a class={`custom artifact artifact-${type}`} href={url}>
196 {title}
197 </a>
198 );
199}
200
201const br = <br />;
202const ssrFunctions: any = {
203 // Default Renders
204 // heading: HeadingRenderer,
205 hr: BasicNode,
206 mark: BasicNode,
207 blockQuote: BasicNode,
208 // codeBlock: MarkdownCodeBlock,
209 list: ListRenderer,
210 // table: TableRenderer,
211 paragraph: BasicNode,
212 // tableSeparator: ???,
213 link: MarkdownLink,
214 // image: MarkdownImage,
215 em: BasicNode,
216 strong: BasicNode,
217 u: BasicNode,
218 del: BasicNode,
219 inlineCode: BasicNode,
220 br: () => br,
221 newline: () => br,
222 deadname: Deadname,
223
224 // Custom Elements
225 question: BasicNode,
226 html: (node: any) => render.raw(node.data),
227 mentionQuestion: QuestionRef,
228 mentionArtifact: ArtifactRef,
229};
230
231function renderNode(node: string | ASTNode | ASTNode[]): any {
232 if (typeof node === "string") {
233 return node;
234 } else if (Array.isArray(node)) {
235 return node.map((item) => renderNode(item));
236 } else if (node.type === "text") {
237 return node.content;
238 } else {
239 const render = ssrFunctions[node.type];
240 if (!render) {
241 throw new Error(`No renderer for node type "${node.type}"`);
242 }
243 const content = node.content as any;
244 return render(node, content ? renderNode(content) : undefined);
245 }
246}
247
248export namespace CloverMarkdown {
249 export interface Props {
250 text: string;
251 inline?: boolean;
252 }
253}
254
255export function CloverMarkdown(props: CloverMarkdown.Props) {
256 const ast = cloverParser(props.text, { inline: !!props.inline });
257 return renderNode(ast);
258}
259
260import { artifactMap } from "./artifacts.ts";
261import { formatQuestionTimestamp, questionIdToTimestamp } from "./format.ts";
262import {
263 type ASTNode,
264 blockRegex,
265 createParser,
266 defaultRules,
267 inlineRegex,
268 parseCaptureInline,
269 parseInline,
270} from "./simple-markdown.ts";
271import * as render from "lib/render.ts";
src/q+a/header.client.ts deleted-12
......@@ -1,12 +0,0 @@
1const h1 = document.querySelector("h1")!;
2const key = "net.paperclover.q+a.header";
3const state = localStorage?.getItem(key);
4if (state === "detrevni ton") h1.classList.toggle("invert");
5h1.addEventListener("click", () => {
6 h1.classList.toggle("invert");
7 localStorage?.setItem(
8 key,
9 (localStorage.getItem?.(key) ?? "not inverted")
10 .split("").reverse().join(""),
11 );
12});
src/q+a/layout.tsx deleted-29
......@@ -1,29 +0,0 @@
1import "./questions.css";
2
3export default function QuestionLayout({ content }: render.Node) {
4 addScript("./header.client.ts");
5 return (
6 <main class="qa">
7 <h1>
8 <span class="a" style="color:var(--a);--s:#19607d">answers</span>
9 <span class="n" style="--s:#999;margin:0 5px">&</span>
10 <span class="q" style="color:var(--b);--s:#994253">questions</span>
11 </h1>
12 {content}
13 </main>
14 );
15}
16
17export const theme = {
18 fg: "#f0f0f0",
19 bg: "#1a1b1c",
20 primary: "#f5a9b8",
21};
22
23export const minimal = {
24 default: ({ content }: { content: any }) => <main class="qa">{content}</main>,
25 theme,
26};
27
28import { addScript } from "#sitegen";
29import * as render from "lib/render.ts";
src/q+a/markdown.tsx created+81
......@@ -0,0 +1,81 @@
1function labelledRedaction(contents: string) {
2 return (
3 <span class="dead">
4 {contents === "name"
5 ? "old name"
6 : contents === "username"
7 ? "old username"
8 : contents === "domain"
9 ? "old domain"
10 : null}
11 </span>
12 );
13}
14
15function questionRef(id: string) {
16 if (id.length === 12) id = id.slice(0, 10);
17 const timestamp = questionIdToTimestamp(id);
18 if (!timestamp) {
19 console.error(`Invalid question ref: ${id}`);
20 return <span class="question-ref">[Invalid Question Ref: #{id}]</span>;
21 }
22 return (
23 <a class="custom question-ref" href={`/q+a/${id}`}>
24 {formatQuestionTimestamp(timestamp)}
25 </a>
26 );
27}
28
29function artifactRef(id: string) {
30 const artifact = artifactMap[id];
31 if (!artifact) {
32 return <span class="artifact">[Invalid Artifact Ref: @{id}]</span>;
33 }
34 const [title, url, type] = artifact;
35 if (!url) {
36 return <span>{title}</span>;
37 }
38 return (
39 <a class={`custom artifact artifact-${type}`} href={url}>
40 {title}
41 </a>
42 );
43}
44
45/** classic esbuild trick: use an impossible to guess key for string magic */
46const uniqueKey = crypto.randomUUID().replaceAll("-", "");
47
48const renderers = {
49 A: artifactRef,
50 Q: questionRef,
51 R: labelledRedaction,
52} as const;
53
54export function renderMarkdown(source: string) {
55 const tagNames: markodown.CloverQuestionExtensions = {
56 question: "q-",
57 artifactRef: uniqueKey + "_A",
58 questionRef: uniqueKey + "_Q",
59 labelledRedaction: uniqueKey + "_R",
60 };
61 const result = markodown.transform({
62 source,
63 markdownOnly: true,
64 cloverExtensions: tagNames,
65 });
66 if (!result.success) {
67 throw new Error(JSON.stringify(result.errors));
68 }
69 return result.text
70 .replace(
71 new RegExp(`<${uniqueKey}_([AQR])>([^<]*)<\\/${uniqueKey}_[AQR]>`, "g"),
72 (_, type: "A" | "Q" | "R", content: string) => {
73 return render.sync(renderers[type](content)).text;
74 },
75 );
76}
77
78import * as markodown from "@clo/markodown";
79import * as render from "lib/render.ts";
80import { artifactMap } from "./artifacts.ts";
81import { formatQuestionTimestamp, questionIdToTimestamp } from "./format.ts";
src/q+a/pages/admin.q+a.marko+1-1
......@@ -1,4 +1,4 @@
1export * as layout from "../layout.tsx";
1export { theme } from "../tags/question-layout.marko";
22export interface Input {
33 admin?: boolean;
44}
src/q+a/pages/q+a.404.marko+3-1
......@@ -1,10 +1,12 @@
1export * as layout from "../layout.tsx";
1export * as layout from "../tags/question-layout.marko";
22export const meta: Metadata = {
33 title: "paper clover q+a",
44};
55
6<question-layout>
67<h2>sound the alarms</h2>
78<p>this page doesn't exist</p>
89<p>
910 <a href="/q+a">return to the questions list</a>
1011</p>
12</question-layout>
src/q+a/pages/q+a.marko+3-1
......@@ -2,7 +2,7 @@ export interface Input {
22 admin?: boolean;
33}
44
5export * as layout from "../layout.tsx";
5export { theme } from "../tags/question-layout.marko";
66export const regenerate = { tags: ["q+a"] };
77export const meta: Metadata = {
88 title: "paper clover q+a",
......@@ -12,6 +12,7 @@ export const meta: Metadata = {
1212<const/{ admin = false } = input />
1313<const/questions = [...Question.getAll()] />
1414
15<question-layout>
1516<if=!admin>
1617 <question-form />
1718</>
......@@ -21,5 +22,6 @@ export const meta: Metadata = {
2122<footer>
2223 fun fact: clover has answered ${questions.length} questions
2324</footer>
25</question-layout>
2426
2527import { Question } from "@/q+a/models/Question.ts";
src/q+a/scripts/editor.client.tsx+11-14
......@@ -1,6 +1,5 @@
11import { EditorState } from "@codemirror/state";
22import { basicSetup, EditorView } from "codemirror";
3import * as render from "lib/render.ts";
43import type { ScriptPayload } from "@/q+a/views/editor.marko";
54import QuestionRender from "@/q+a/tags/question.marko";
65
......@@ -10,19 +9,17 @@ const date = new Date(payload.date);
109const main = document.getElementById("edit-grid")! as HTMLDivElement;
1110const preview = document.getElementById("preview")! as HTMLDivElement;
1211
13function updatePreview(text: string) {
14 preview.innerHTML = render.sync(
15 <QuestionRender
16 question={{
17 id: payload.id,
18 qmid: payload.qmid,
19 text: text,
20 type: payload.type,
21 date,
22 }}
23 editor
24 />,
25 ).text;
12async function updatePreview(text: string) {
13 preview.innerHTML = await QuestionRender.render({
14 question: {
15 id: payload.id,
16 qmid: payload.qmid,
17 text: text,
18 type: payload.type,
19 date,
20 },
21 editor: true,
22 });
2623}
2724updatePreview(payload.text);
2825
src/q+a/simple-markdown.ts deleted-1090
......@@ -1,1090 +0,0 @@
1// @ts-nocheck
2// # Simple-Markdown Core
3//
4// This is a fork of Khan-academy's Simple-Markdown[1], initially forked in 2022
5// to add Svelte support[2], and used for paper clover's q+a markdown flavor.
6//
7// 1: https://github.com/Khan/perseus/tree/main/packages/simple-markdown/src
8// 2: https://github.com/paperclover/svelte-simple-markdown
9export type Rules = Record<string, ParserRule>;
10export interface ParserRule {
11 name: string;
12 match: MatchFunction;
13 parse: ParseFunction;
14 quality?: QualityFunction;
15}
16
17export class RuleList extends Array<ParserRule> {
18 constructor(input?: ArrayLike<ParserRule>) {
19 super();
20 if (input) {
21 this.push(...Array.from(input));
22 }
23 }
24
25 insertBefore(rule: string, newRule: ParserRule): void {
26 const index = this.findIndex((r) => r.name === rule);
27 if (index === -1) {
28 throw new Error(`Rule ${rule} not found`);
29 }
30 this.splice(index, 0, newRule);
31 }
32
33 insertAfter(rule: string, newRule: ParserRule): void {
34 const index = this.findIndex((r) => r.name === rule);
35 if (index === -1) {
36 throw new Error(`Rule ${rule} not found`);
37 }
38 this.splice(index + 1, 0, newRule);
39 }
40
41 toRuleObject(): Record<string, ParserRule> {
42 const result: Record<string, ParserRule> = {};
43 this.forEach((rule) => {
44 result[rule.name] = rule;
45 });
46 return result;
47 }
48
49 add(rule: ParserRule): void {
50 this.push(rule);
51 }
52
53 get(rule: string): ParserRule | undefined {
54 return this.find((r) => r.name === rule);
55 }
56
57 remove(rule: string): void {
58 const index = this.findIndex((r) => r.name === rule);
59 if (index === -1) {
60 throw new Error(`Rule ${rule} not found`);
61 }
62 this.splice(index, 1);
63 }
64
65 clone() {
66 return new RuleList(this);
67 }
68}
69
70/**
71 * Creates a parser for a given set of rules, with the precedence
72 * specified as a list of rules.
73 *
74 * @param rules
75 * an object containing
76 * rule type -> {match, order, parse} objects
77 * (lower order is higher precedence)
78 * @param [defaultState]
79 *
80 * @returns
81 * The resulting parse function, with the following parameters:
82 * @source: the input source string to be parsed
83 * @state: an optional object to be threaded through parse
84 * calls. Allows clients to add stateful operations to
85 * parsing, such as keeping track of how many levels deep
86 * some nesting is. For an example use-case, see passage-ref
87 * parsing in src/widgets/passage/passage-markdown.jsx
88 */
89export function createParser(
90 ruleListInput: RuleList,
91 defaultState: Partial<ParserState> = {},
92) {
93 let rules = ruleListInput.toRuleObject();
94 let ruleList = Object.keys(rules);
95
96 let latestState: ParserState;
97
98 let nestedParse = function (source: string, state?: ParserState) {
99 let result: ASTNode[] = [];
100 state = state || latestState;
101 latestState = state;
102
103 while (source) {
104 // store the best match, it's rule, and quality:
105 let ruleType = null;
106 let rule = null;
107 let capture = null;
108 let quality = NaN; // loop control variables:
109
110 let i = 0;
111 let currRuleType = ruleList[0];
112
113 let currRule = rules[currRuleType];
114
115 do {
116 let currCapture = currRule.match(source, state);
117
118 if (currCapture) {
119 let currQuality = currRule.quality
120 ? currRule.quality(currCapture, state)
121 : 0;
122
123 // This should always be true the first time because
124 // the initial quality is NaN (that's why there's the
125 // condition negation).
126 if (!(currQuality <= quality)) {
127 ruleType = currRuleType;
128 rule = currRule;
129 capture = currCapture;
130 quality = currQuality;
131 }
132 }
133
134 // Move on to the next item.
135 // Note that this makes `currRule` be the next item
136 i++;
137 currRuleType = ruleList[i];
138 currRule = rules[currRuleType];
139 } while (
140 // keep looping while we're still within the ruleList
141 currRule &&
142 // if we don't have a match yet, continue
143 (!capture ||
144 // or if we have a match, but the next rule is
145 // at the same order, and has a quality measurement
146 // functions, then this rule must have a quality
147 // measurement function (since they are sorted before
148 // those without), and we need to check if there is
149 // a better quality match
150 currRule.quality)
151 );
152
153 if (!rule || !capture || !ruleType) {
154 throw new Error(
155 "Could not find a matching rule for the below " +
156 "content. The rule with highest `order` should " +
157 "always match content provided to it. Check " +
158 "the definition of `match` for '" +
159 ruleList[ruleList.length - 1] +
160 "'. It seems to not match the following source:\n" +
161 source,
162 );
163 }
164
165 if (capture.index) {
166 // If present and non-zero, i.e. a non-^ regexp result:
167 throw new Error(
168 "`match` must return a capture starting at index 0 " +
169 "(the current parse index). Did you forget a ^ at the " +
170 "start of the RegExp?",
171 );
172 }
173
174 let parsed = rule.parse(capture, nestedParse, state);
175
176 // We maintain the same object here so that rules can
177 // store references to the objects they return and
178 // modify them later. (oops sorry! but this adds a lot
179 // of power--see reflinks.)
180
181 // We also let rules override the default type of
182 // their parsed node if they would like to, so that
183 // there can be a single output function for all links,
184 // even if there are several rules to parse them.
185 if (!parsed.type) {
186 parsed.type = ruleType;
187 }
188
189 // Collapse text nodes
190 if (
191 parsed.type === "text" && result[result.length - 1]?.type === "text"
192 ) {
193 result[result.length - 1].content += parsed.content;
194 } else {
195 result.push(parsed as ASTNode);
196 }
197
198 state.prevCapture = capture;
199 source = source.substring(state.prevCapture[0].length);
200 }
201
202 return result;
203 };
204
205 let outerParse = function (
206 source: string,
207 state: ParserState = { inline: false },
208 ) {
209 latestState = populateInitialState(state, defaultState);
210
211 if (!latestState.inline && !latestState.disableAutoBlockNewlines) {
212 source = source + "\n\n";
213 }
214
215 // We store the previous capture so that match functions can
216 // use some limited amount of lookbehind. Lists use this to
217 // ensure they don't match arbitrary '- ' or '* ' in inline
218 // text (see the list rule for more information). This stores
219 // the full regex capture object, if there is one.
220 latestState.prevCapture = undefined;
221 return nestedParse(preprocess(source), latestState);
222 };
223
224 return outerParse;
225}
226
227type Multiple<T> = T | T[];
228type Nullable<T> = T | null | undefined;
229
230export type MatchFunction = (
231 source: string,
232 state: ParserState,
233) => Nullable<RegExpMatchArray>;
234
235export type Parser = (source: string, state?: ParserState) => ASTNode[];
236
237export type ParseFunction = (
238 source: RegExpMatchArray,
239 nestedParse: Parser,
240 state: ParserState,
241) => TypeOptionalASTNode;
242
243export type QualityFunction = (
244 capture: RegExpMatchArray,
245 state: ParserState,
246) => number;
247
248export interface ParserState {
249 inline: boolean;
250 prevCapture?: RegExpMatchArray;
251 [key: string]: any;
252}
253
254export interface ASTNode {
255 type: string;
256 content?: ASTNode[] | string;
257 [key: string]: any;
258}
259
260export type TypeOptionalASTNode = Omit<ASTNode, "type"> & { type?: string };
261
262export interface RefNode {
263 type: string;
264 content?: Multiple<ASTNode>;
265 target?: string;
266 title?: string;
267 alt?: string;
268}
269
270/** Creates a match function for an inline scoped element from a regex */
271export function inlineRegex(regex: RegExp): MatchFunction {
272 return (source, state) => {
273 if (state.inline) {
274 return regex.exec(source);
275 } else {
276 return null;
277 }
278 };
279}
280
281/** Creates a match function for a block scoped element from a regex */
282export function blockRegex(regex: RegExp): MatchFunction {
283 return (source, state) => {
284 if (state.inline) {
285 return null;
286 } else {
287 return regex.exec(source);
288 }
289 };
290}
291
292/** Creates a match function from a regex, ignoring block/inline scope */
293export function anyScopeRegex(regex: RegExp): MatchFunction {
294 return (source) => {
295 return regex.exec(source);
296 };
297}
298
299const UNESCAPE_URL_R = /\\([^0-9A-Za-z\s])/g;
300export function unescapeUrl(rawUrlString: string) {
301 return rawUrlString.replace(UNESCAPE_URL_R, "$1");
302}
303
304/**
305 * Parse some content with the parser `parse`, with state.inline
306 * set to true. Useful for block elements; not generally necessary
307 * to be used by inline elements (where state.inline is already true.
308 */
309export function parseInline(
310 parse: Parser,
311 content: string,
312 state: ParserState,
313) {
314 const isCurrentlyInline = state.inline || false;
315 state.inline = true;
316 const result = parse(content, state);
317 state.inline = isCurrentlyInline;
318 return result;
319}
320
321export function parseBlock(parse: Parser, content: string, state: ParserState) {
322 const isCurrentlyInline = state.inline || false;
323 state.inline = false;
324 const result = parse(content + "\n\n", state);
325 state.inline = isCurrentlyInline;
326 return result;
327}
328
329export function parseCaptureInline(
330 capture: RegExpMatchArray,
331 parse: Parser,
332 state: ParserState,
333) {
334 return {
335 content: parseInline(parse, capture[1], state),
336 };
337}
338
339export function ignoreCapture() {
340 return {};
341}
342
343export function sanitizeUrl(url?: string) {
344 if (url == null) {
345 return null;
346 }
347 try {
348 const prot = new URL(url, "https://localhost").protocol;
349 if (
350 prot.indexOf("javascript:") === 0 ||
351 prot.indexOf("vbscript:") === 0 ||
352 prot.indexOf("data:") === 0
353 ) {
354 return null;
355 }
356 } catch (e) {
357 // invalid URLs should throw a TypeError
358 // see for instance: `new URL("");`
359 return null;
360 }
361 return url;
362}
363
364const CR_NEWLINE_R = /\r\n?/g;
365const TAB_R = /\t/g;
366const FORMFEED_R = /\f/g;
367
368/**
369 * Turn various whitespace into easy-to-process whitespace
370 */
371function preprocess(source: string) {
372 return source.replace(CR_NEWLINE_R, "\n").replace(FORMFEED_R, "").replace(
373 TAB_R,
374 " ",
375 );
376}
377
378function populateInitialState(
379 givenState: Partial<ParserState>,
380 defaultState: Partial<ParserState>,
381) {
382 let state = givenState || {};
383
384 for (let prop in defaultState) {
385 if (Object.prototype.hasOwnProperty.call(defaultState, prop)) {
386 state[prop] = defaultState[prop];
387 }
388 }
389
390 return state as ParserState;
391}
392
393// recognize a `*` `-`, `+`, `1.`, `2.`... list bullet
394const LIST_BULLET = "(?:[*+-]|\\d+\\.)";
395
396// recognize the start of a list item:
397// leading space plus a bullet plus a space (` * `)
398const LIST_ITEM_PREFIX = "( *)(" + LIST_BULLET + ") +";
399const LIST_ITEM_PREFIX_R = new RegExp("^" + LIST_ITEM_PREFIX);
400
401// recognize an individual list item:
402// * hi
403// this is part of the same item
404//
405// as is this, which is a new paragraph in the same item
406//
407// * but this is not part of the same item
408const LIST_ITEM_R = new RegExp(
409 LIST_ITEM_PREFIX + "[^\\n]*(?:\\n" + "(?!\\1" + LIST_BULLET +
410 " )[^\\n]*)*(\n|$)",
411 "gm",
412);
413const BLOCK_END_R = /\n{2,}$/;
414const INLINE_CODE_ESCAPE_BACKTICKS_R = /^ (?= *`)|(` *) $/g;
415
416// recognize the end of a paragraph block inside a list item:
417// two or more newlines at end end of the item
418const LIST_BLOCK_END_R = BLOCK_END_R;
419const LIST_ITEM_END_R = / *\n+$/;
420
421// check whether a list item has paragraphs: if it does,
422// we leave the newlines at the end
423const LIST_R = new RegExp(
424 "^( *)(" +
425 LIST_BULLET +
426 ") " +
427 "[\\s\\S]+?(?:\n{2,}(?! )" +
428 "(?!\\1" +
429 LIST_BULLET +
430 " )\\n*" +
431 // the \\s*$ here is so that we can parse the inside of nested
432 // lists, where our content might end before we receive two `\n`s
433 "|\\s*\n*$)",
434);
435const LIST_LOOKBEHIND_R = /(?:^|\n)( *)$/;
436
437const TABLES = (function () {
438 const TABLE_ROW_SEPARATOR_TRIM = /^ *\| *| *\| *$/g;
439 const TABLE_CELL_END_TRIM = / *$/;
440 const TABLE_RIGHT_ALIGN = /^ *-+: *$/;
441 const TABLE_CENTER_ALIGN = /^ *:-+: *$/;
442 const TABLE_LEFT_ALIGN = /^ *:-+ *$/; // TODO: This needs a real type
443
444 const parseTableAlignCapture = (alignCapture: string) => {
445 if (TABLE_RIGHT_ALIGN.test(alignCapture)) {
446 return "right";
447 } else if (TABLE_CENTER_ALIGN.test(alignCapture)) {
448 return "center";
449 } else if (TABLE_LEFT_ALIGN.test(alignCapture)) {
450 return "left";
451 } else {
452 return null;
453 }
454 };
455
456 const parseTableAlign = (source: string, trimEndSeparators: boolean) => {
457 if (trimEndSeparators) {
458 source = source.replace(TABLE_ROW_SEPARATOR_TRIM, "");
459 }
460
461 const alignText = source.trim().split("|");
462 return alignText.map(parseTableAlignCapture);
463 };
464
465 const parseTableRow = (
466 source: string,
467 parse: Parser,
468 state: ParserState,
469 trimEndSeparators: boolean,
470 ) => {
471 const prevInTable = state.inTable;
472 state.inTable = true;
473 const tableRow = parse(source.trim(), state);
474 state.inTable = prevInTable;
475 const cells: ASTNode[][] = [[]];
476 tableRow.forEach(function (node, i) {
477 if (node.type === "tableSeparator") {
478 // Filter out empty table separators at the start/end:
479 if (!trimEndSeparators || (i !== 0 && i !== tableRow.length - 1)) {
480 // Split the current row:
481 cells.push([]);
482 }
483 } else {
484 if (
485 typeof node.content === "string" &&
486 (tableRow[i + 1] == null || tableRow[i + 1].type === "tableSeparator")
487 ) {
488 node.content = node.content.replace(TABLE_CELL_END_TRIM, "");
489 }
490
491 cells[cells.length - 1].push(node);
492 }
493 });
494 return cells;
495 };
496
497 /**
498 * @param {string} source
499 * @param {SimpleMarkdown.Parser} parse
500 * @param {SimpleMarkdown.State} state
501 * @param {boolean} trimEndSeparators
502 * @returns {SimpleMarkdown.ASTNode[][]}
503 */
504 const parseTableCells = function (
505 source: string,
506 parse: Parser,
507 state: ParserState,
508 trimEndSeparators: boolean,
509 ) {
510 const rowsText = source.trim().split("\n");
511 return rowsText.map(function (rowText) {
512 return parseTableRow(rowText, parse, state, trimEndSeparators);
513 });
514 };
515
516 /**
517 * @param {boolean} trimEndSeparators
518 * @returns {SimpleMarkdown.SingleNodeParseFunction}
519 */
520 const parseTable = function (trimEndSeparators: boolean) {
521 return function (
522 capture: RegExpMatchArray,
523 parse: Parser,
524 state: ParserState,
525 ) {
526 state.inline = true;
527 const header = parseTableRow(capture[1], parse, state, trimEndSeparators);
528 const align = parseTableAlign(capture[2], trimEndSeparators);
529 const cells = parseTableCells(
530 capture[3],
531 parse,
532 state,
533 trimEndSeparators,
534 );
535 state.inline = false;
536 return {
537 type: "table",
538 header: header,
539 align: align,
540 cells: cells,
541 };
542 };
543 };
544
545 return {
546 parseTable: parseTable(true),
547 parseNpTable: parseTable(false),
548 TABLE_REGEX: /^ *(\|.+)\n *\|( *[-:]+[-| :]*)\n((?: *\|.*(?:\n|$))*)\n*/,
549 NPTABLE_REGEX:
550 /^ *(\S.*\|.*)\n *([-:]+ *\|[-| :]*)\n((?:.*\|.*(?:\n|$))*)\n*/,
551 };
552})();
553
554const LINK_INSIDE = "(?:\\[[^\\]]*\\]|[^\\[\\]]|\\](?=[^\\[]*\\]))*";
555const LINK_HREF_AND_TITLE =
556 "\\s*<?((?:\\([^)]*\\)|[^\\s\\\\]|\\\\.)*?)>?(?:\\s+['\"]([\\s\\S]*?)['\"])?\\s*";
557const AUTOLINK_MAILTO_CHECK_R = /mailto:/i;
558
559function parseRef(
560 capture: RegExpMatchArray,
561 state: ParserState,
562 refNode: RefNode,
563) {
564 const ref = (capture[2] || capture[1]).replace(/\s+/g, " ").toLowerCase();
565
566 // We store information about previously seen defs on
567 // state._defs (_ to deconflict with client-defined
568 // state). If the def for this reflink/refimage has
569 // already been seen, we can use its target/source
570 // and title here:
571 if (state._defs && state._defs[ref]) {
572 const def = state._defs[ref];
573
574 // `refNode` can be a link or an image. Both use
575 // target and title properties.
576 refNode.target = def.target;
577 refNode.title = def.title;
578 }
579
580 // In case we haven't seen our def yet (or if someone
581 // overwrites that def later on), we add this node
582 // to the list of ref nodes for that def. Then, when
583 // we find the def, we can modify this link/image AST
584 // node :).
585 // I'm sorry.
586 state._refs = state._refs || {};
587 state._refs[ref] = state._refs[ref] || [];
588
589 state._refs[ref].push(refNode);
590
591 return refNode;
592}
593
594export const defaultRules = new RuleList();
595{
596 defaultRules.add({
597 name: "heading",
598 match: blockRegex(/^ *(#{1,6})([^\n]+?)#* *(?:\n *)+\n/),
599 parse: function (capture, parse, state) {
600 return {
601 level: capture[1].length,
602 content: parseInline(parse, capture[2].trim(), state),
603 };
604 },
605 });
606
607 defaultRules.add({
608 name: "nptable",
609 match: blockRegex(TABLES.NPTABLE_REGEX),
610 parse: TABLES.parseNpTable,
611 });
612
613 defaultRules.add({
614 name: "lheading",
615 match: blockRegex(/^([^\n]+)\n *(=|-){3,} *(?:\n *)+\n/),
616 parse(capture, parse, state) {
617 return {
618 type: "heading",
619 level: capture[2] === "=" ? 1 : 2,
620 content: parseInline(parse, capture[1], state),
621 };
622 },
623 });
624
625 defaultRules.add({
626 name: "hr",
627 match: blockRegex(/^( *[-*_]){3,} *(?:\n *)+\n/),
628 parse: ignoreCapture,
629 });
630
631 defaultRules.add({
632 name: "codeBlock",
633 match: blockRegex(/^(?: {4}[^\n]+\n*)+(?:\n *)+\n/),
634 parse(capture) {
635 const content = capture[0].replace(/^ {4}/gm, "").replace(/\n+$/, "");
636 return {
637 lang: undefined,
638 content: content,
639 };
640 },
641 });
642
643 defaultRules.add({
644 name: "fence",
645 match: blockRegex(
646 /^ *(`{3,}|~{3,}) *(?:(\S+) *)?\n([\s\S]+?)\n?\1 *(?:\n *)+\n/,
647 ),
648 parse(capture) {
649 return {
650 type: "codeBlock",
651 lang: capture[2] || undefined,
652 content: capture[3],
653 };
654 },
655 });
656
657 defaultRules.add({
658 name: "blockQuote",
659 match: blockRegex(/^( *>[^\n]+(\n[^\n]+)*\n*)+\n{2,}/),
660 parse(capture, parse, state) {
661 const content = capture[0].replace(/^ *> ?/gm, "");
662 return {
663 content: parse(content, state),
664 };
665 },
666 });
667
668 defaultRules.add({
669 name: "list",
670
671 match(source, state) {
672 // We only want to break into a list if we are at the start of a
673 // line. This is to avoid parsing "hi * there" with "* there"
674 // becoming a part of a list.
675 // You might wonder, "but that's inline, so of course it wouldn't
676 // start a list?". You would be correct! Except that some of our
677 // lists can be inline, because they might be inside another list,
678 // in which case we can parse with inline scope, but need to allow
679 // nested lists inside this inline scope.
680 const prevCaptureStr = state.prevCapture == null
681 ? ""
682 : state.prevCapture[0];
683 const isStartOfLineCapture = LIST_LOOKBEHIND_R.exec(prevCaptureStr);
684 const isListBlock = state._list || !state.inline;
685
686 if (isStartOfLineCapture && isListBlock) {
687 source = isStartOfLineCapture[1] + source;
688 return LIST_R.exec(source);
689 } else {
690 return null;
691 }
692 },
693 parse(capture, parse, state) {
694 const bullet = capture[2];
695 const ordered = bullet.length > 1;
696 const start = ordered ? +bullet : undefined;
697 // We know this will match here, because of how the regexes are defined
698 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
699 const items = capture[0].replace(LIST_BLOCK_END_R, "\n").match(
700 LIST_ITEM_R,
701 )!;
702
703 let lastItemWasAParagraph = false;
704 const itemContent = items.map(function (item, i) {
705 // We need to see how far indented this item is:
706 const prefixCapture = LIST_ITEM_PREFIX_R.exec(item);
707 const space = prefixCapture ? prefixCapture[0].length : 0; // And then we construct a regex to "unindent" the subsequent
708 // lines of the items by that amount:
709
710 const spaceRegex = new RegExp("^ {1," + space + "}", "gm"); // Before processing the item, we need a couple things
711
712 const content = item // remove indents on trailing lines:
713 .replace(spaceRegex, "") // remove the bullet:
714 .replace(LIST_ITEM_PREFIX_R, ""); // I'm not sur4 why this is necessary again?
715 // Handling "loose" lists, like:
716 //
717 // * this is wrapped in a paragraph
718 //
719 // * as is this
720 //
721 // * as is this
722
723 const isLastItem = i === items.length - 1;
724 const containsBlocks = content.indexOf("\n\n") !== -1; // Any element in a list is a block if it contains multiple
725 // newlines. The last element in the list can also be a block
726 // if the previous item in the list was a block (this is
727 // because non-last items in the list can end with \n\n, but
728 // the last item can't, so we just "inherit" this property
729 // from our previous element).
730
731 const thisItemIsAParagraph = containsBlocks ||
732 (isLastItem && lastItemWasAParagraph);
733 lastItemWasAParagraph = thisItemIsAParagraph; // backup our state for restoration afterwards. We're going to
734 // want to set state._list to true, and state.inline depending
735 // on our list's looseness.
736
737 const oldStateInline = state.inline;
738 const oldStateList = state._list;
739 state._list = true; // Parse inline if we're in a tight list, or block if we're in
740 // a loose list.
741
742 let adjustedContent;
743
744 if (thisItemIsAParagraph) {
745 state.inline = false;
746 adjustedContent = content.replace(LIST_ITEM_END_R, "\n\n");
747 } else {
748 state.inline = true;
749 adjustedContent = content.replace(LIST_ITEM_END_R, "");
750 }
751
752 const result = parse(adjustedContent, state); // Restore our state before returning
753
754 state.inline = oldStateInline;
755 state._list = oldStateList;
756 return result;
757 });
758
759 return {
760 ordered: ordered,
761 start: start,
762 content: itemContent,
763 };
764 },
765 });
766
767 defaultRules.add({
768 name: "def",
769 // TODO: This will match without a blank line before the next
770 // block element, which is inconsistent with most of the rest of
771 // simple-markdown.
772 match: blockRegex(
773 /^ *\[([^\]]+)\]: *<?([^\s>]*)>?(?: +["(]([^\n]+)[")])? *\n(?: *\n)*/,
774 ),
775 parse(capture, parse, state) {
776 const def = capture[1].replace(/\s+/g, " ").toLowerCase();
777 const target = capture[2];
778 const title = capture[3];
779
780 // Look for previous links/images using this def
781 // If any links/images using this def have already been declared,
782 // they will have added themselves to the state._refs[def] list
783 // (_ to deconflict with client-defined state). We look through
784 // that list of reflinks for this def, and modify those AST nodes
785 // with our newly found information now.
786 // Sorry :(.
787 if (state._refs && state._refs[def]) {
788 // `refNode` can be a link or an image
789 state._refs[def].forEach((refNode: RefNode) => {
790 refNode.target = target;
791 refNode.title = title;
792 });
793 }
794
795 // Add this def to our map of defs for any future links/images
796 // In case we haven't found any or all of the refs referring to
797 // this def yet, we add our def to the table of known defs, so
798 // that future reflinks can modify themselves appropriately with
799 // this information.
800 state._defs = state._defs || {};
801 state._defs[def] = {
802 target: target,
803 title: title,
804 };
805
806 // return the relevant parsed information
807 // for debugging only.
808 return {
809 def: def,
810 target: target,
811 title: title,
812 };
813 },
814 });
815
816 defaultRules.add({
817 name: "table",
818 match: blockRegex(TABLES.TABLE_REGEX),
819 parse: TABLES.parseTable,
820 });
821
822 defaultRules.add({
823 name: "newline",
824 match: blockRegex(/^(?:\n *)*\n/),
825 parse: ignoreCapture,
826 });
827
828 defaultRules.add({
829 name: "paragraph",
830 match: blockRegex(/^((?:[^\n]|\n(?! *\n))+)(?:\n *)+\n/),
831 parse: parseCaptureInline,
832 });
833
834 defaultRules.add({
835 name: "escape",
836 // We don't allow escaping numbers, letters, or spaces here so that
837 // backslashes used in plain text still get rendered. But allowing
838 // escaping anything else provides a very flexible escape mechanism,
839 // regardless of how this grammar is extended.
840 match: inlineRegex(/^\\([^0-9A-Za-z\s])/),
841 parse(capture) {
842 return {
843 type: "text",
844 content: capture[1],
845 };
846 },
847 });
848
849 defaultRules.add({
850 name: "tableSeparator",
851
852 match(source, state) {
853 if (!state.inTable) {
854 return null;
855 }
856
857 return /^ *\| */.exec(source);
858 },
859 parse() {
860 return {
861 type: "tableSeparator",
862 };
863 },
864 });
865
866 defaultRules.add({
867 name: "autolink",
868 match: inlineRegex(/^<([^: >]+:\/[^ >]+)>/),
869 parse(capture) {
870 return {
871 type: "link",
872 content: [
873 {
874 type: "text",
875 content: capture[1],
876 },
877 ],
878 target: capture[1],
879 };
880 },
881 });
882
883 defaultRules.add({
884 name: "mailto",
885 match: inlineRegex(/^<([^ >]+@[^ >]+)>/),
886 parse(capture) {
887 const address = capture[1];
888 let target = capture[1]; // Check for a `mailto:` already existing in the link:
889
890 if (!AUTOLINK_MAILTO_CHECK_R.test(target)) {
891 target = "mailto:" + target;
892 }
893
894 return {
895 type: "link",
896 content: [
897 {
898 type: "text",
899 content: address,
900 },
901 ],
902 target: target,
903 };
904 },
905 });
906
907 defaultRules.add({
908 name: "url",
909 match: inlineRegex(/^(https?:\/\/[^\s<]+[^<.,:;"')\]\s])/),
910 parse(capture) {
911 return {
912 type: "link",
913 content: [
914 {
915 type: "text",
916 content: capture[1],
917 },
918 ],
919 target: capture[1],
920 title: undefined,
921 };
922 },
923 });
924
925 defaultRules.add({
926 name: "link",
927 match: inlineRegex(
928 new RegExp(
929 "^\\[(" + LINK_INSIDE + ")\\]\\(" + LINK_HREF_AND_TITLE + "\\)",
930 ),
931 ),
932 parse(capture, parse, state) {
933 const link = {
934 content: parse(capture[1], state),
935 target: unescapeUrl(capture[2]),
936 title: capture[3],
937 };
938 return link;
939 },
940 });
941
942 defaultRules.add({
943 name: "image",
944 match: inlineRegex(
945 new RegExp(
946 "^!\\[(" + LINK_INSIDE + ")\\]\\(" + LINK_HREF_AND_TITLE + "\\)",
947 ),
948 ),
949 parse: function (capture) {
950 const image = {
951 alt: capture[1],
952 target: unescapeUrl(capture[2]),
953 title: capture[3],
954 };
955 return image;
956 },
957 });
958
959 defaultRules.add({
960 name: "reflink",
961 match: inlineRegex(
962 new RegExp(
963 // The first [part] of the link
964 "^\\[(" +
965 LINK_INSIDE +
966 ")\\]" + // The [ref] target of the link
967 "\\s*\\[([^\\]]*)\\]",
968 ),
969 ),
970 parse(capture, parse, state) {
971 return parseRef(capture, state, {
972 type: "link",
973 content: parse(capture[1], state),
974 });
975 },
976 });
977
978 defaultRules.add({
979 name: "refimage",
980 match: inlineRegex(
981 new RegExp(
982 // The first [part] of the link
983 "^!\\[(" +
984 LINK_INSIDE +
985 ")\\]" + // The [ref] target of the link
986 "\\s*\\[([^\\]]*)\\]",
987 ),
988 ),
989 parse(capture, parse, state) {
990 return parseRef(capture, state, {
991 type: "image",
992 alt: capture[1],
993 });
994 },
995 });
996
997 defaultRules.add({
998 name: "em",
999 /* same as strong/u */
1000 match: inlineRegex(
1001 new RegExp(
1002 // only match _s surrounding words.
1003 "^\\b_" +
1004 "((?:__|\\\\[\\s\\S]|[^\\\\_])+?)_" +
1005 "\\b" + // Or match *s:
1006 "|" + // Only match *s that are followed by a non-space:
1007 "^\\*(?=\\S)(" + // Match at least one of:
1008 "(?:" + // - `**`: so that bolds inside italics don't close the
1009 // italics
1010 "\\*\\*|" + // - escape sequence: so escaped *s don't close us
1011 "\\\\[\\s\\S]|" + // - whitespace: followed by a non-* (we don't
1012 // want ' *' to close an italics--it might
1013 // start a list)
1014 "\\s+(?:\\\\[\\s\\S]|[^\\s\\*\\\\]|\\*\\*)|" + // - non-whitespace, non-*, non-backslash characters
1015 "[^\\s\\*\\\\]" +
1016 ")+?" + // followed by a non-space, non-* then *
1017 ")\\*(?!\\*)",
1018 ),
1019 ),
1020 quality(capture) {
1021 // precedence by length, `em` wins ties:
1022 return capture[0].length + 0.2;
1023 },
1024 parse(capture, parse, state) {
1025 return {
1026 content: parse(capture[2] || capture[1], state),
1027 };
1028 },
1029 });
1030
1031 defaultRules.add({
1032 name: "strong",
1033 /* same as em */
1034 match: inlineRegex(/^\*\*((?:\\[\s\S]|[^\\])+?)\*\*(?!\*)/),
1035 quality(capture) {
1036 // precedence by length, wins ties vs `u`:
1037 return capture[0].length + 0.1;
1038 },
1039 parse: parseCaptureInline,
1040 });
1041
1042 defaultRules.add({
1043 name: "u",
1044 /* same as em&strong; increment for next rule */
1045 match: inlineRegex(/^__((?:\\[\s\S]|[^\\])+?)__(?!_)/),
1046 quality(capture) {
1047 // precedence by length, loses all ties
1048 return capture[0].length;
1049 },
1050 parse: parseCaptureInline,
1051 });
1052
1053 defaultRules.add({
1054 name: "del",
1055 match: inlineRegex(/^~~(?=\S)((?:\\[\s\S]|~(?!~)|[^\s~\\]|\s(?!~~))+?)~~/),
1056 parse: parseCaptureInline,
1057 });
1058
1059 defaultRules.add({
1060 name: "inlineCode",
1061 match: inlineRegex(/^(`+)([\s\S]*?[^`])\1(?!`)/),
1062 parse(capture) {
1063 return {
1064 content: capture[2].replace(INLINE_CODE_ESCAPE_BACKTICKS_R, "$1"),
1065 };
1066 },
1067 });
1068
1069 defaultRules.add({
1070 name: "br",
1071 match: anyScopeRegex(/^ {2,}\n/),
1072 parse: ignoreCapture,
1073 });
1074
1075 defaultRules.add({
1076 name: "text",
1077 // Here we look for anything followed by non-symbols,
1078 // double newlines, or double-space-newlines
1079 // We break on any symbol characters so that this grammar
1080 // is easy to extend without needing to modify this regex
1081 match: anyScopeRegex(
1082 /^[\s\S]+?(?=[^0-9A-Za-z\s\u00c0-\uffff]|\n\n| {2,}\n|\w+:\S|$)/,
1083 ),
1084 parse: function (capture) {
1085 return {
1086 content: capture[0],
1087 };
1088 },
1089 });
1090}
src/q+a/tags/header.client.ts created+12
......@@ -0,0 +1,12 @@
1const h1 = document.querySelector("h1")!;
2const key = "net.paperclover.q+a.header";
3const state = localStorage?.getItem(key);
4if (state === "detrevni ton") h1.classList.toggle("invert");
5h1.addEventListener("click", () => {
6 h1.classList.toggle("invert");
7 localStorage?.setItem(
8 key,
9 (localStorage.getItem?.(key) ?? "not inverted")
10 .split("").reverse().join(""),
11 );
12});
src/q+a/tags/question-layout.marko created+22
......@@ -0,0 +1,22 @@
1import "../questions.css";
2client import "./header.client.ts";
3
4<main class="qa">
5 <if=!input.minimal>
6 <h1>
7 <span class="a" style="color:var(--a);--s:#19607d">answers</span>
8 <span class="n" style="--s:#999;margin:0 5px">&</span>
9 <span class="q" style="color:var(--b);--s:#994253">questions</span>
10 </h1>
11 </>
12 <${input.content} />
13</main>
14
15export const theme = {
16 fg: "#f0f0f0",
17 bg: "#1a1b1c",
18 primary: "#f5a9b8",
19};
20
21import { addScript } from "#sitegen";
22import * as render from "lib/render.ts";
src/q+a/tags/question.marko+3-3
......@@ -8,7 +8,7 @@ export interface Input {
88}
99
1010// 2024-12-31 05:00:00 EST
11export const transitionDate = 1735639200000;
11export const transitionDate = 1735639200000;
1212
1313<const/{ question, admin = false, editor = false } = input />
1414<const/{ id, date } = question/>
......@@ -29,7 +29,7 @@ export const transitionDate = 1735639200000;
2929 >${formatQuestionTimestamp(date)}</time>
3030 </a>
3131
32 <CloverMarkdown text=question.text />
32 $!{markdown.renderMarkdown(question.text)}
3333</>
3434
3535// this singleton script will make all the '<time>' tags clickable.
......@@ -39,5 +39,5 @@ export const transitionDate = 1735639200000;
3939
4040import type { Question } from "@/q+a/models/Question.ts";
4141import { formatQuestionTimestamp, formatQuestionISOTimestamp } from "@/q+a/format.ts";
42import { CloverMarkdown } from "@/q+a/clover-markdown.tsx";
42import * as markdown from "@/q+a/markdown.tsx";
4343import { addScript as Script } from "#sitegen"
src/q+a/views/backend-inbox.marko+3-1
......@@ -1,4 +1,4 @@
1export { minimal as layout } from "../layout.tsx";
1export { theme } from "../tags/question-layout.marko";
22import { PendingQuestion } from "@/q+a/models/PendingQuestion.ts";
33import {
44 formatQuestionISOTimestamp,
......@@ -8,6 +8,7 @@ export const meta = { title: 'question answer inbox' };
88
99<const/questions = PendingQuestion.getAll() />
1010
11<question-layout minimal>
1112<h1>inbox</h1>
1213<for|q| of=questions>
1314 <div
......@@ -36,5 +37,6 @@ export const meta = { title: 'question answer inbox' };
3637 </p>
3738 </div>
3839</>
40</question-layout>
3941
4042client import "./backend-inbox.client.ts";
src/q+a/views/editor.marko+1-1
......@@ -1,5 +1,5 @@
11import "./editor.css";
2export { theme } from "@/q+a/layout.tsx";
2export { theme } from "../tags/question-layout.marko";
33
44export const meta = { title: "question editor" };
55
src/q+a/views/fail.marko+3-2
......@@ -1,5 +1,4 @@
1import * as layout from "../layout.tsx";
2export { layout };
1import * as layout from "../tags/question-layout.marko";
32export const theme = {
43 ...layout.theme,
54 primary: "#58ffee",
......@@ -8,6 +7,7 @@ export const meta = { title: 'oh no' };
87
98<const/{ error, content }=input/>
109
10<question-layout>
1111<h2 style="color: red">:(</h2>
1212<p style="color: red">
1313 ${error}
......@@ -25,3 +25,4 @@ export const meta = { title: 'oh no' };
2525<p>
2626 <a href="/q+a">return to the questions list</a>
2727</p>
28</question-layout>
src/q+a/views/permalink.marko+4-1
......@@ -1,4 +1,4 @@
1export * as layout from "@/q+a/layout.tsx";
1export { theme } from "../tags/question-layout.marko";
22export interface Input {
33 question: Question;
44}
......@@ -25,6 +25,8 @@ server export function meta({ context, question }) {
2525
2626<const/{ question }=input/>
2727<const/{ type }=question/>
28
29<question-layout>
2830<if=type==QuestionType.normal>
2931 <p>this page is a permalink to the following question:</p>
3032 <question ...{question} />
......@@ -48,6 +50,7 @@ server export function meta({ context, question }) {
4850</>
4951
5052<a href="/q+a">view more questions</a>
53</question-layout>
5154
5255import { Question, QuestionType } from '@/q+a/models/Question.ts';
5356import { transitionDate } from "@/q+a/tags/question.marko";
src/q+a/views/success.marko+3-3
......@@ -5,6 +5,7 @@ export const meta = { title: 'question submitted!!!' };
55
66<const/{ permalink }=input/>
77
8<question-layout>
89<h2 style="color: #8c78ff; margin-bottom: 0">thank you</h2>
910<p style="color: #8c78ff">
1011 your question has been submitted.
......@@ -23,11 +24,10 @@ export const meta = { title: 'question submitted!!!' };
2324<p>
2425 <a href="/q+a">return to the questions list</a>
2526</p>
27</question-layout>
2628
27import * as layout from "../layout.tsx";
28export { layout };
29import * as layout from "../tags/question-layout.marko";
2930export const theme = {
3031 ...layout.theme,
3132 primary: "#58ffee",
3233};
33
tsconfig.json+2-1
......@@ -16,7 +16,8 @@
1616 "verbatimModuleSyntax": true,
1717 "target": "esnext",
1818 "noUncheckedIndexedAccess": true,
19 "resolveJsonModule": true
19 "resolveJsonModule": true,
20 "types": ["marko"]
2021 },
2122 "include": ["lib/**/*", "src/**/*", "framework/**/*"]
2223}