Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .changeset/ark-rewrites.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
"beatmapper": minor
---

## Technical Changes

- All hotkeys have been moved to Ark UI's command registration model, which offers better handling for operating system behaviors and future customizability.
- The `DocsTableOfContents` component has been rewritten to an Ark-based implementation, which should fix the janky hacks and inconsistencies around scroll containers.
10 changes: 10 additions & 0 deletions .changeset/content-collections.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
"beatmapper": minor
---

## Technical Changes

- Migrated from Velite to [Content Collections](https://www.content-collections.dev) and a [Sätteri](https://satteri.bruits.org)-based processing pipeline. This new pipeline offers faster compilation of MDX content via a Rust-based parser and more flexibility for plugins that extend the syntax.
- Sharp is no longer bundled as a transitive dependency, since the package recently introduced a broken shim that can break the install step on certain environments. It also wasn't really that useful for our original content processing workflow.
- All collections now use Valibot for schema validation as opposed to Zod, as Content Collections is fully Standard Schema compliant and allows us to align all schemas with one validation library as opposed to two.
- All custom syntax tree mutations and transformations were ported to local MDAST/HAST plugins, so we can own the supply chain for functionality and align generated data structures (like table of contents) more closely with the frontend.
11 changes: 11 additions & 0 deletions .changeset/rewrite-controls.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
"beatmapper": minor
---

## Technical Changes

- The camera logic has been rewritten as a component-based controller model for better composability and customizability.

## Bugfixes

- The camera now enforces pointer-lock behavior when rotation and movement controls are active.
9 changes: 9 additions & 0 deletions .changeset/upgrade-dependencies.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
"beatmapper": minor
---

## Technical Changes

- Upgraded project dependencies and workspace binaries to latest supported targets (including majors).
- Updated ECMAScript language targets to `ES2024` according to latest baselines.
- The React Compiler (oxc-based) is now enabled by default for both dev and preview environments.
9 changes: 9 additions & 0 deletions .changeset/vitest-browser.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
"beatmapper": minor
---

## Technical Changes

- Added new capabilities to the testing environment for running integration tests via Vitest's [Browser Mode](https://vitest.dev/guide/browser) and [Playwright](https://playwright.dev/docs/api/class-playwright).
- The install step for Playwright is now bundled directly into the workspace's setup task for both local installs and CI workflows. Any browsers that are not supported on the runner's operating system will be automatically skipped when running integration tests.
- Some helper methods were added to compose new render functions for integration tests, which provides a nicer syntax when working with providers and exposing their contexts for verification.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,14 @@
node_modules

# autogenerated
.velite
.content-collections
public/static
src/routeTree.gen.ts
styled-system
styled-system-studio

# testing
.vitest
coverage

# production
Expand Down
2 changes: 1 addition & 1 deletion biome.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"$schema": "https://biomejs.dev/schemas/2.4.8/schema.json",
"$schema": "https://biomejs.dev/schemas/2.5.13/schema.json",
"files": {
"ignoreUnknown": true
},
Expand Down
179 changes: 179 additions & 0 deletions content-collections.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
import { createHash } from "node:crypto";
import { mkdir, readFile, writeFile } from "node:fs/promises";
import { basename, dirname, extname, join, resolve } from "node:path";
import { cwd } from "node:process";
import { fileURLToPath, pathToFileURL } from "node:url";

import type { Assign } from "@ark-ui/react";
import type { TocItemData } from "@ark-ui/react/toc";
import { defineCollection, defineConfig } from "@content-collections/core";
import { slugify } from "@std/text/unstable-slugify";
import { defineHastPlugin, type Features, mdxToJs } from "satteri";
import { default as expressiveCode, type SatteriExpressiveCodeOptions } from "satteri-expressive-code";
import { nullable, number, object, optional, string } from "valibot";

declare module "satteri" {
export interface DataMap {
getToc: () => Assign<TocItemData, { label: string }>[];
}
}

function toc() {
const toc: Assign<TocItemData, { label: string }>[] = [];

return defineHastPlugin({
name: "satteri-toc",
element: {
filter: ["h1", "h2", "h3", "h4", "h5", "h6"],
visit(node, ctx) {
const depth = parseInt(node.tagName.charAt(1), 10);
const label = ctx.textContent(node);
const value = slugify(label);

if (!node.properties.id) {
ctx.setProperty(node, "id", value);
}
if (depth === 2) {
toc.push({ value: node.properties.id ?? value, depth, label } as Assign<TocItemData, { label: string }>);
}

ctx.data.getToc = () => toc;
},
},
});
}

function callouts() {
const ALERT_REGEX = /^\s*\[!(NOTE|TIP|IMPORTANT|WARNING|CAUTION)\]\s*/i;

return defineHastPlugin({
name: "satteri-alerts",
element: {
filter: ["blockquote"],
visit(node, ctx) {
const pNode = node.children.find((c) => c.type === "element" && c.tagName === "p");
const text = pNode && "children" in pNode ? pNode.children.find((c) => c.type === "text") : null;
const match = text?.value.match(ALERT_REGEX);

if (match && text) {
const classes = Array.from(node.properties.className ?? []);
ctx.setProperty(node, "className", [...classes, `alert-${match[1].toLowerCase()}`]);
ctx.setProperty(text, "value", text.value.replace(ALERT_REGEX, ""));
}
},
},
});
}

function inlineMedia() {
return defineHastPlugin({
name: "satteri-inline-media",
element: {
filter: ["img"],
visit(node, ctx) {
if ([".mp4", ".webm", ".ogv", ".mov", ".m4v"].some((ext) => node.properties.src?.toLowerCase().endsWith(ext))) {
ctx.replaceNode(node, { ...node, tagName: "video", properties: { ...node.properties, controls: true, alt: undefined } });
}
},
},
});
}

function staticAssets(options = { staticDir: join(cwd(), "public/static") }) {
const INVALID_LOCAL_ASSET = /^(?:[a-zA-Z]+:|\/\/|#|\?)/;
const SANITIZE_ASSET = /^([^?#]*)(.*)$/;

return defineHastPlugin({
name: "satteri-static-assets",
element: {
filter: ["img", "video"],
async visit(node, ctx) {
if (!ctx.fileURL || !node.properties.src) return;
if (INVALID_LOCAL_ASSET.test(node.properties.src)) return;
const match = node.properties.src.match(SANITIZE_ASSET);
if (!match) return;
const [_, cleanUrl, suffix] = match;
const filePath = fileURLToPath(ctx.fileURL);

try {
const sourcePath = cleanUrl.startsWith("/") ? resolve(cwd(), cleanUrl.slice(1)) : resolve(dirname(filePath), cleanUrl);

const buffer = await readFile(sourcePath);
const ext = extname(sourcePath);
const hash = createHash("md5").update(buffer).digest("hex").slice(0, 8);
const filename = `${basename(sourcePath, ext)}-${hash}${ext}`;

const destPath = join(options.staticDir, filename);
await mkdir(options.staticDir, { recursive: true });
await writeFile(destPath, buffer);

const publicUrl = join(`/${basename(options.staticDir)}/`, filename + suffix);
ctx.setProperty(node, "src", publicUrl);
} catch (err) {
console.warn(`Failed to copy asset "${cleanUrl}" referenced in "${filePath}":`, err);
}
},
},
});
}

const features: Features = {
gfm: true,
frontmatter: true,
};
const code: SatteriExpressiveCodeOptions = {
themes: ["github-light-default", "github-dark-default"],
styleOverrides: { frames: { shadowColor: "transparent" } },
themeCssSelector: (theme) => `.${theme.type}`,
};

function resolveId(path: string, collection: string) {
const [...splat] = path.replace(join(cwd(), `src/content/${collection}`), "").split("\\");
return splat.join("/").replace("/index", "").replace(".local", "").replace(".mdx", "");
}

const docs = defineCollection({
name: "Doc",
directory: "src/content/docs",
include: "**/*.mdx",
schema: object({
content: string(),
title: string(),
subtitle: optional(string()),
category: optional(nullable(string()), null),
order: optional(number(), 0),
prev: optional(string()),
next: optional(string()),
}),
transform: async ({ content, ...document }, ctx) => {
const result = await mdxToJs(content, {
features: features,
outputFormat: "function-body",
fileURL: pathToFileURL(join(ctx.collection.directory, document._meta.filePath)),
hastPlugins: [toc(), callouts(), inlineMedia(), staticAssets(), expressiveCode(code)],
});
return { ...document, id: resolveId(document._meta.filePath, "docs"), code: result.code, tableOfContents: result.data.getToc?.() ?? [] };
},
});

const prompts = defineCollection({
name: "Prompt",
directory: "src/content/prompts",
include: "**/*.mdx",
schema: object({
content: string(),
title: string(),
}),
transform: async ({ content, ...document }, ctx) => {
const result = mdxToJs(content, {
features: features,
outputFormat: "function-body",
fileURL: pathToFileURL(join(ctx.collection.directory, document._meta.filePath)),
});
return { ...document, id: resolveId(document._meta.filePath, "prompts"), code: result.code };
},
});

export default defineConfig({
content: [docs, prompts],
});
9 changes: 5 additions & 4 deletions mise.toml
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
[tools]
biome = "2.4.8"
lefthook = "2.1.4"
biome = "2.5.13"
lefthook = "2.1.12"
node = "24.16.0"
pnpm = "11.5.2"
pnpm = "12.4.1"

[env]
_.path = ['{{config_root}}/node_modules/.bin']

[tasks.setup]
run = [
"pnpm install",
"panda codegen --clean"
"panda codegen --clean",
"pnpm exec playwright install"
]
Loading
Loading