diff --git a/astro.config.mjs b/astro.config.mjs
index 43537ff..fce42ac 100644
--- a/astro.config.mjs
+++ b/astro.config.mjs
@@ -1,10 +1,22 @@
import { defineConfig } from "astro/config";
import starlight from "@astrojs/starlight";
+import mdx from "@astrojs/mdx";
export default defineConfig({
site: process.env.SITE_URL ?? "https://wezel.build",
base: process.env.SITE_BASE ?? "/",
+ // /docs has no landing page of its own — send it to the introduction.
+ redirects: {
+ "/docs": "/docs/introduction",
+ },
+
+ // Code blocks in the hand-authored landing body (src/home/index.mdx).
+ // Starlight manages its own theming for docs separately.
+ markdown: {
+ shikiConfig: { theme: "vitesse-dark", wrap: false },
+ },
+
integrations: [
starlight({
title: "Wezel Docs",
@@ -40,8 +52,15 @@ export default defineConfig({
autogenerate: { directory: "docs/developing" },
},
],
- customCss: [],
+ customCss: ["./src/styles/starlight.css"],
head: [
+ // Light-only: pin the theme so expressive-code and everything else
+ // render light, regardless of OS preference or any stored value.
+ {
+ tag: 'script',
+ content:
+ "try{localStorage.setItem('starlight-theme','light')}catch(e){}document.documentElement.dataset.theme='light';",
+ },
{
tag: 'script',
attrs: {
@@ -52,5 +71,8 @@ export default defineConfig({
},
],
}),
+ // mdx() must come after starlight() — Starlight registers
+ // expressive-code, which has to be set up before MDX.
+ mdx(),
],
});
diff --git a/package-lock.json b/package-lock.json
index 68dafbe..21a3be5 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -8,11 +8,13 @@
"name": "wezel-www",
"version": "0.0.1",
"dependencies": {
+ "@astrojs/mdx": "^4.3.14",
"@astrojs/starlight": "^0.37.6",
"@fontsource/ibm-plex-sans": "^5.2.8",
"@fontsource/iosevka": "^5.2.5",
"@fontsource/iosevka-aile": "^5.2.5",
- "astro": "^5.10.0"
+ "astro": "^5.10.0",
+ "ci-info": "^4.4.0"
}
},
"node_modules/@astrojs/compiler": {
@@ -57,12 +59,12 @@
}
},
"node_modules/@astrojs/mdx": {
- "version": "4.3.13",
- "resolved": "https://registry.npmjs.org/@astrojs/mdx/-/mdx-4.3.13.tgz",
- "integrity": "sha512-IHDHVKz0JfKBy3//52JSiyWv089b7GVSChIXLrlUOoTLWowG3wr2/8hkaEgEyd/vysvNQvGk+QhysXpJW5ve6Q==",
+ "version": "4.3.14",
+ "resolved": "https://registry.npmjs.org/@astrojs/mdx/-/mdx-4.3.14.tgz",
+ "integrity": "sha512-FBrqJQORVm+rkRa2TS5CjU9PBA6hkhrwLVBSS9A77gN2+iehvjq1w6yya/d0YKC7osiVorKkr3Qd9wNbl0ZkGA==",
"license": "MIT",
"dependencies": {
- "@astrojs/markdown-remark": "6.3.10",
+ "@astrojs/markdown-remark": "6.3.11",
"@mdx-js/mdx": "^3.1.1",
"acorn": "^8.15.0",
"es-module-lexer": "^1.7.0",
@@ -83,6 +85,41 @@
"astro": "^5.0.0"
}
},
+ "node_modules/@astrojs/mdx/node_modules/@astrojs/internal-helpers": {
+ "version": "0.7.6",
+ "resolved": "https://registry.npmjs.org/@astrojs/internal-helpers/-/internal-helpers-0.7.6.tgz",
+ "integrity": "sha512-GOle7smBWKfMSP8osUIGOlB5kaHdQLV3foCsf+5Q9Wsuu+C6Fs3Ez/ttXmhjZ1HkSgsogcM1RXSjjOVieHq16Q==",
+ "license": "MIT"
+ },
+ "node_modules/@astrojs/mdx/node_modules/@astrojs/markdown-remark": {
+ "version": "6.3.11",
+ "resolved": "https://registry.npmjs.org/@astrojs/markdown-remark/-/markdown-remark-6.3.11.tgz",
+ "integrity": "sha512-hcaxX/5aC6lQgHeGh1i+aauvSwIT6cfyFjKWvExYSxUhZZBBdvCliOtu06gbQyhbe0pGJNoNmqNlQZ5zYUuIyQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@astrojs/internal-helpers": "0.7.6",
+ "@astrojs/prism": "3.3.0",
+ "github-slugger": "^2.0.0",
+ "hast-util-from-html": "^2.0.3",
+ "hast-util-to-text": "^4.0.2",
+ "import-meta-resolve": "^4.2.0",
+ "js-yaml": "^4.1.1",
+ "mdast-util-definitions": "^6.0.0",
+ "rehype-raw": "^7.0.0",
+ "rehype-stringify": "^10.0.1",
+ "remark-gfm": "^4.0.1",
+ "remark-parse": "^11.0.0",
+ "remark-rehype": "^11.1.2",
+ "remark-smartypants": "^3.0.2",
+ "shiki": "^3.21.0",
+ "smol-toml": "^1.6.0",
+ "unified": "^11.0.5",
+ "unist-util-remove-position": "^5.0.0",
+ "unist-util-visit": "^5.0.0",
+ "unist-util-visit-parents": "^6.0.2",
+ "vfile": "^6.0.3"
+ }
+ },
"node_modules/@astrojs/prism": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/@astrojs/prism/-/prism-3.3.0.tgz",
diff --git a/package.json b/package.json
index 41965d8..bdfc06b 100644
--- a/package.json
+++ b/package.json
@@ -8,10 +8,12 @@
"preview": "astro preview"
},
"dependencies": {
+ "@astrojs/mdx": "^4.3.14",
"@astrojs/starlight": "^0.37.6",
"@fontsource/ibm-plex-sans": "^5.2.8",
"@fontsource/iosevka": "^5.2.5",
"@fontsource/iosevka-aile": "^5.2.5",
- "astro": "^5.10.0"
+ "astro": "^5.10.0",
+ "ci-info": "^4.4.0"
}
}
diff --git a/public/favicon.svg b/public/favicon.svg
new file mode 100644
index 0000000..cdcd74e
--- /dev/null
+++ b/public/favicon.svg
@@ -0,0 +1,4 @@
+
diff --git a/src/content.config.ts b/src/content.config.ts
index e32fee2..43fb7a6 100644
--- a/src/content.config.ts
+++ b/src/content.config.ts
@@ -1,23 +1,10 @@
-import { defineCollection, z } from "astro:content";
+import { defineCollection } from "astro:content";
import { docsLoader } from "@astrojs/starlight/loaders";
import { docsSchema } from "@astrojs/starlight/schema";
-import { glob } from "astro/loaders";
export const collections = {
docs: defineCollection({
loader: docsLoader(),
schema: docsSchema(),
}),
- blog: defineCollection({
- loader: glob({ pattern: "**/*.md", base: "./src/content/blog" }),
- schema: z.object({
- title: z.string(),
- description: z.string(),
- date: z.coerce.date(),
- draft: z.boolean().default(false),
- author: z
- .union([z.string(), z.array(z.string()).min(1)])
- .transform((v) => (Array.isArray(v) ? v : [v])),
- }),
- }),
-};
\ No newline at end of file
+};
diff --git a/src/content/docs/docs/index.md b/src/content/docs/docs/index.md
deleted file mode 100644
index 696d534..0000000
--- a/src/content/docs/docs/index.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Hello
----
-
-Hello world.
\ No newline at end of file
diff --git a/src/home/index.mdx b/src/home/index.mdx
new file mode 100644
index 0000000..65990dc
--- /dev/null
+++ b/src/home/index.mdx
@@ -0,0 +1,65 @@
+## Why?
+
+I feel like build times are an afterthought. Yes, people spend countless hours complaining about them. Yes, the pain is real. Yet the trend is for them to get worse, not better. There's never enough time in a day to get them into shape.
+
+We do not talk about our issues enough, because nobody wishes to *care* about their build. It's a boring topic. Rightly so. You want to spend your sweet time on tackling problems you're paid to solve.
+
+### Sufficiently swift compiler
+It's not like we can expect a sufficiently swift compiler to show up and wave all of your issues away either. We cannot expect toolchain maintainers to:
+1. know what is painful about your usage of the toolchain.
+2. have access to your codebase in order to poke, prod and profile a compiler.
+
+Even if such compiler existed, chances are that [Jevons paradox](https://en.wikipedia.org/wiki/Jevons_paradox) would do you dirty.
+
+### Optimizing builds requires obscure knowledge
+
+Material on optimizing builds is few and far between, stowed away on internet forums or blog post entries. In a way, you have to have quite intricate understanding of the compiler internals in order to really squeeze your build.
+Knowing how big of an impact that using a given dependency will have on your builds is nigh impossible. So is structuring code around monomorphizations or dynamic dispatch. Yet, down the line, these decisions can have collateral effect on your build performance.
+
+### Speed is a cultural challenge
+It's quite tricky to keep code written a particular way. In my experience, code that's quicker to compile is "uglier", as you have to limit how much a compiler has to do. Sure, you can stick a comment on it, but there's no way to be *certain* that code you've made quick to compile will stay that way - especially in the era where the human might not necessarily be in the loop.
+
+### Putting it all together
+We're left in a situation where optimizing builds is a skill on its own, whose fruits are hard to hold onto. Nobody will do that for you and while you may be tempted to just skip this relatively boring chore, you'll feel the pain of that negligence yourself. That's why I've built Wezel - I want people to know when (and eventually - why) their build regressed
+
+## What
+
+Wezel lets you define build scenarios you want to track across the lifetime of your project.
+It all starts with an `experiment.toml`, committed next to your code, that declares what to measure.
+Take the following example The `cargo` tool builds your target and emits timing outcomes, and a
+single outcome can feed several summaries.
+
+```toml
+description = "Tracks compile time of the wezel workspace"
+
+# A single experiment consists of multiple steps, executed in order
+[step.cargo.release-build]
+command = "build"
+build_target = ["burrow"]
+profile = "release"
+# Summaries are outputs of a step of an experiment.
+# They're always scalar (to drive the bisection).
+# Summaries use outcomes to get their values.
+# Outcomes are results of your experiments - they are not necessarily scalar!
+# (think of e.g. compiler profiling files or cargo --timings output)
+#
+# In this case, `cargo` tool emits `time_ms` for a total time for a build.
+# In order to produce a summary,
+# we'll run 5 builds and take their mean build time as a value of `build-time` summary.
+summary.build-time = { outcome = "time_ms", aggregation = "mean", samples = 5 }
+```
+
+Most commits do not meaningfully regress your builds though. That's why we resort to sparse sampling: instead of running these experiments on every single commit you make, we run these measurements once a day or so.
+Only when there's a meaningful regression between the last measurement and a current one do we resort to running experiments on all unmeasured commits - until all sources of regressions are found.
+
+The results of experiment runs are surfaced in a dashboard over at [app.wezel.build](app.wezel.build).
+
+### Not just Rust
+
+The whole thing is extensible - tool implementations live in separate binaries. Nothing prevents you from adding support for your own toolchain of choice - as long as you follow the protocol.
+
+## Where
+
+Wezel is open source and under active development. Follow along on
+[GitHub](https://github.com/wezel-build/wezel), read the
+[quickstart](/docs/quickstart) to wire it into your own builds. There's also [Discord server](https://discord.gg/NE6HAz7H) you can join to chat and follow along.
diff --git a/src/layouts/Base.astro b/src/layouts/Base.astro
index 5c9a0d2..c7eab0a 100644
--- a/src/layouts/Base.astro
+++ b/src/layouts/Base.astro
@@ -9,33 +9,24 @@ interface Props {
const { title = "Wezel — Your build, always at its best." } = Astro.props;
const navLinks = [
- { label: "Blog", href: "/blog" },
{ label: "Docs", href: "/docs" },
{ label: "Status", href: "https://status.wezel.build" },
];
-const themes = ["dark", "light"] as const;
---
-
+
-
-
-
-
diff --git a/src/pages/index.astro b/src/pages/index.astro
index 3b096d3..2a9926f 100644
--- a/src/pages/index.astro
+++ b/src/pages/index.astro
@@ -1,1358 +1,122 @@
---
-import Base from '../layouts/Base.astro';
-import { Code } from 'astro:components';
-
-const installUrl = new URL('install.sh', Astro.site ?? 'https://wezel.build/').toString();
+import Base from "../layouts/Base.astro";
+import "../styles/home.css";
+import { Content as Body } from "../home/index.mdx";
+
+const installUrl = new URL(
+ "install.sh",
+ Astro.site ?? "https://wezel.build/",
+).toString();
const installCmd = `curl -fsSL ${installUrl} | sh`;
-
-const experimentToml = `description = "Tracks compile time of the wezel workspace"
-
-# Steps are keyed [step..].
-# The cargo tool builds your target and emits per-crate timing outcomes.
-[step.cargo.build]
-build_target = "workspace"
-
-# A single outcome can feed multiple summaries — Wezel tracks each independently.
-summary.workspace-build-time = { outcome = "duration.total" }
-summary.slowest-crate-time = { outcome = "duration.max" }
-`;
-
-// Chart data: Wezel samples build durations across recent commits, then bisects
-// when a sample drifts. Most commits are unmeasured (small ticks); a handful are
-// sampled bars; two get measured via bisection to pinpoint the offender.
-const N = 32;
-const BASELINE = 47.3;
-const W = 480;
-const H = 210;
-const PAD_X = 4;
-const slot = (W - PAD_X * 2) / N;
-const barW = slot * 0.62;
-const baseY = 165;
-const scale = 2.1;
-
-// Samples land at irregular intervals (the schedule isn't uniform in real use).
-const sampleIdxs = [4, 11, 13, 17]; // gaps of 7, 2, 4 commits
-const detectIdx = N - 1; // detect is the latest commit (rightmost slot)
-// Binary search on [18..30] with 31 known bad and 17 known good. The
-// terminating condition is a measured-bad commit adjacent to a measured-good
-// commit — so the offender itself has to be measured, not inferred.
-// 1. mid of [18,30] = 24 → measure (BAD). Narrow LEFT to [18,23].
-// 2. mid of [18,23] = 20 → measure (good). Narrow RIGHT to [21,23].
-// 3. mid of [21,23] = 22 → measure (good). Narrow RIGHT to [23,23].
-// 4. last candidate: 23 → measure (BAD). 22-good and 23-bad adjacent: 23.
-const bisectStep1Idx = 24; // bad
-const bisectStep2Idx = 20; // good — jump back-left after step 1 was bad
-const bisectStep3Idx = 22; // good — narrow further right
-const bisectStep4Idx = 23; // bad — confirms 23 is the offender (adjacent to 22-good)
-const regressionIdx = bisectStep4Idx;
-
-// Timing: the cursor sweeps left-to-right across the chart at SWEEP_MOVING_MS
-// of pure motion, but PAUSES for PAUSE_MS at each sample commit (so the viewer
-// sees the cursor visibly stop on every measurement). Total sweep duration
-// extends by the cumulative pause time.
-// Bisection bars appear AFTER the sweep finishes, with the cursor jumping back
-// to track each binary-search step.
-const SWEEP_MOVING_MS = 4000;
-const STEP_MS = SWEEP_MOVING_MS / N;
-const PAUSE_MS = 150;
-const TOTAL_PAUSE_MS = sampleIdxs.length * PAUSE_MS;
-const SWEEP_END_MS = SWEEP_MOVING_MS + TOTAL_PAUSE_MS; // 4600ms
-
-// pausesBefore(i): total ms of cursor-pause accumulated by the time the cursor
-// reaches commit i's center (count of sample indices strictly less than i).
-function pausesBefore(i) {
- let n = 0;
- for (const s of sampleIdxs) if (s < i) n++;
- return n * PAUSE_MS;
-}
-
-const BISECT_SPAN_DELAY = SWEEP_END_MS + 200; // 4800 — bracket starts drawing right-to-left here
-const BISECT_STEP_1_DELAY = SWEEP_END_MS + 1300; // 5900 — wait for bracket+label to fully settle (~5700ms), small buffer
-const BISECT_STEP_2_DELAY = SWEEP_END_MS + 1725; // 6325
-const BISECT_STEP_3_DELAY = SWEEP_END_MS + 2150; // 6750
-const BISECT_STEP_4_DELAY = SWEEP_END_MS + 2575; // 7175
-const MARKER_LINE_DELAY = SWEEP_END_MS + 2950; // 7550
-const MARKER_LABEL_DELAY = SWEEP_END_MS + 3100; // 7700
-
-const commits = Array.from({ length: N }, (_, i) => {
- const tBase = Math.round(i * STEP_MS + pausesBefore(i));
- if (i === bisectStep1Idx)
- return { i, type: 'bisect-bad', value: 59.5, delay: BISECT_STEP_1_DELAY };
- if (i === bisectStep2Idx)
- return { i, type: 'bisect-good', value: 47.1, delay: BISECT_STEP_2_DELAY };
- if (i === bisectStep3Idx)
- return { i, type: 'bisect-good', value: 47.0, delay: BISECT_STEP_3_DELAY };
- if (i === bisectStep4Idx)
- return { i, type: 'bisect-bad', value: 59.6, delay: BISECT_STEP_4_DELAY };
- if (i === detectIdx)
- return { i, type: 'detect', value: 59.3, delay: tBase + 80 };
- if (sampleIdxs.includes(i))
- return {
- i,
- type: 'sample',
- value: BASELINE + Math.sin(i * 1.7) * 0.5,
- delay: tBase + 80,
- };
- return { i, type: 'unmeasured', value: null, delay: tBase };
-});
---
-
-
-
-
-
-
-
-
- Build-time observability · open source
-
-
- Your build,
- always at its best.
-
-
- Wezel benchmarks the build scenarios your team runs most, on every commit.
- When something gets slower, the offending change, the delta, and the affected scope
- surface immediately.
-