From b2a26d8e45f2c77b013e6fb1dd41320a6096aca0 Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Wed, 17 Jun 2026 19:13:57 +0200 Subject: [PATCH 1/3] Redo the landing page: make the voice more mine, if I can say so. --- astro.config.mjs | 10 + package-lock.json | 47 +- package.json | 4 +- src/home/index.mdx | 41 ++ src/layouts/Base.astro | 9 +- src/pages/index.astro | 1464 ++++------------------------------------ src/styles/global.css | 32 - src/styles/home.css | 266 ++++++++ 8 files changed, 483 insertions(+), 1390 deletions(-) create mode 100644 src/home/index.mdx create mode 100644 src/styles/home.css diff --git a/astro.config.mjs b/astro.config.mjs index 43537ff..c77788b 100644 --- a/astro.config.mjs +++ b/astro.config.mjs @@ -1,10 +1,17 @@ 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 ?? "/", + // 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", @@ -52,5 +59,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/src/home/index.mdx b/src/home/index.mdx new file mode 100644 index 0000000..016c1c7 --- /dev/null +++ b/src/home/index.mdx @@ -0,0 +1,41 @@ +## Why? + +Builds get slower one commit at a time, and nobody notices until the whole +team is waiting. Wezel watches the build scenarios you actually run and tells +you the moment one of them regresses — while the change is still fresh enough +to fix. + +## What + +An `experiment.toml`, committed next to your code, declares what to measure. +The `cargo` tool builds your target and emits per-crate timing outcomes, and a +single outcome can feed several summaries. + +```toml +description = "Tracks compile time of the wezel workspace" + +[step.cargo.build] +build_target = "workspace" + +summary.workspace-build-time = { outcome = "duration.total" } +summary.slowest-crate-time = { outcome = "duration.max" } +``` + +On every commit, the CLI walks the steps, captures the outcomes, and compares +each summary against the trailing baseline. When a summary drifts past +tolerance, Wezel reports the offending commit, the delta, and the affected +scope. + +```console +$ wezel experiment run workspace-build +→ step.cargo.build ........... 59.7s +→ summary.workspace-build-time 59.7s (baseline 47.3s · +12.4s · regression) +→ summary.slowest-crate-time 8.4s (baseline 8.1s · +0.3s) +done in 59.9s +``` + +## Stay tuned + +Wezel is open source and under active development. Follow along on +[GitHub](https://github.com/wezel-build/wezel), or read the +[quickstart](/docs/quickstart) to wire it into your own builds. diff --git a/src/layouts/Base.astro b/src/layouts/Base.astro index 5c9a0d2..c2a632a 100644 --- a/src/layouts/Base.astro +++ b/src/layouts/Base.astro @@ -31,8 +31,11 @@ const themes = ["dark", "light"] as const; @@ -60,7 +63,9 @@ const themes = ["dark", "light"] as const; (function() { var order = ['dark', 'light']; var btn = document.getElementById('theme-toggle'); - var current = localStorage.getItem('theme') || 'dark'; + // Reflect whatever was actually applied (which may be the + // landing's light default rather than a stored value). + var current = document.documentElement.getAttribute('data-theme') || 'dark'; btn.textContent = current; btn.addEventListener('click', function() { var i = order.indexOf(current); 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 }; -}); --- - -
-
-
- -
-
- -

- 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. -

- - -
-
- shell - -
-
$ curl -fsSL {installUrl} | sh
-
-
- -
-
- build duration · seconds - - live -
- - - - - - - - - - - - - - - {[20, 40, 60].map((s) => ( - - ))} - - - - - - {commits.map((c) => { - const x = PAD_X + c.i * slot + (slot - barW) / 2; - const cx = x + barW / 2; - // ghost height = baseline ± small per-commit noise; this is "what - // we'd guess, but haven't measured" — the question mark says so. - const ghostV = BASELINE + Math.sin(c.i * 1.3) * 0.4; - const h = ghostV * scale; - const y = baseY - h; - // Synced to the cursor's center crossing this commit, accounting - // for any sample-pauses that have already happened earlier in the sweep. - const ghostDelay = Math.round(c.i * STEP_MS + STEP_MS / 2 + pausesBefore(c.i)); - return ( - - - ? - - ); - })} - - - {commits.filter((c) => c.type !== 'unmeasured').map((c) => { - const x = PAD_X + c.i * slot + (slot - barW) / 2; - const h = c.value * scale; - const y = baseY - h; - const isWarn = c.type === 'detect' || c.type === 'bisect-bad'; - const isBisect = c.type.startsWith('bisect-'); - const cls = `bar bar-${c.type}${isWarn ? ' bar-warn' : ''}${isBisect ? ' bar-bisect' : ''}`; - return ( - - ); - })} - - - - - - - - - - - - - a3f8c1d - +12.4s · +26% - - - - - 32 commits ago - now - - - - - - - - - - bisection range - - -
-
- -
-
- - -
-
- -

From TOML to alert.

- -
    -
  1. -
    - 01 -
    -

    Define a scenario.

    -

    An experiment.toml committed alongside your code declares what Wezel measures.

    -
    -
    -
    -
    - .wezel/experiments/workspace-build/experiment.toml - toml -
    - -
    -
  2. - -
  3. -
    - 02 -
    -

    Observe a regression.

    -

    The CLI walks the steps, captures outcomes, and compares each summary against the trailing baseline.

    + +
    +
    + + + {/* copy slot: wordmark (usually the product name) */} +

    wezel

    + + {/* copy slot: tagline */} +

    Build-time observability

    + + {/* copy slot: subtitle */} +

    + Wezel benchmarks the build scenarios your team runs most, on + every commit, and surfaces the offending change the moment + something gets slower. +

    + + {/* copy slot: status banner — edit or delete */} +

    + Early development · not yet ready for production +

    + + -
    -
    -
    - shell -
    -
    $ wezel experiment run workspace-build
    -→ step.cargo.build  ...........  59.7s
    -→ summary.workspace-build-time   59.7s   (baseline 47.3s · +12.4s · regression)
    -→ summary.slowest-crate-time      8.4s   (baseline  8.1s · +0.3s)
    -done in 59.9s
    -
    -
  4. - -
  5. -
    - 03 -
    -

    Track it in CI.

    -

    Wired into anything that runs per commit — CI, a build server, a cron — Wezel emits the alert the moment a summary drifts past tolerance.

    -
    -
    -
    -
    - ⚠ regression detected - 2026-05-15 -
    -
    -
    experiment
    workspace-build
    -
    summary
    workspace-build-time
    -
    commit
    a3f8c1d
    -
    baseline
    47.3s · trailing 30 commits
    -
    current
    59.7s · +12.4s · +26%
    -
    -
    -
  6. -
-
-
- - -
-
- -
- - + - - diff --git a/src/styles/global.css b/src/styles/global.css index 83e4b35..20eb888 100644 --- a/src/styles/global.css +++ b/src/styles/global.css @@ -167,18 +167,11 @@ samp { background: var(--accent); color: var(--bg); border-color: var(--accent); - box-shadow: - 0 0 20px var(--accent-glow), - 0 1px 3px rgba(0, 0, 0, 0.3); } .btn-primary:hover { background: #c09878; border-color: #c09878; - box-shadow: - 0 0 32px var(--accent-glow-strong), - 0 2px 8px rgba(0, 0, 0, 0.4); - transform: translateY(-1px); } .btn-secondary { @@ -277,40 +270,15 @@ samp { } /* ===== Subtle noise texture on body ===== */ -[data-theme="light"] .btn-primary { - box-shadow: - 0 0 16px var(--accent-glow), - 0 1px 3px rgba(0, 0, 0, 0.1); -} - [data-theme="light"] .btn-primary:hover { background: #7a5028; border-color: #7a5028; - box-shadow: - 0 0 24px var(--accent-glow-strong), - 0 2px 6px rgba(0, 0, 0, 0.12); } [data-theme="light"] .card:hover { box-shadow: 0 4px 24px rgba(0, 0, 0, 0.06); } -body::before { - content: ""; - position: fixed; - inset: 0; - pointer-events: none; - z-index: 9999; - opacity: 0.015; - background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E"); - background-repeat: repeat; - background-size: 256px 256px; -} - -[data-theme="light"] body::before { - opacity: 0; -} - /* ===== Responsive ===== */ @media (max-width: 768px) { .container { diff --git a/src/styles/home.css b/src/styles/home.css new file mode 100644 index 0000000..4415ef0 --- /dev/null +++ b/src/styles/home.css @@ -0,0 +1,266 @@ +/* Landing page — light warm-paper, prose-forward, README-like. + Centered wordmark hero; body reads as a styled document with accent-bar + section headings. Styling only; words live in index.astro (hero) and + src/home/index.mdx (body). */ + +/* ── Hero (centered wordmark) ─────────────────────────── */ +.hero { + text-align: center; + padding: 5.5rem 1.5rem 3.5rem; + border-bottom: 1px solid var(--border); +} +.hero-inner { + max-width: 44rem; + margin: 0 auto; +} +.hero-mark { + display: flex; + align-items: center; + justify-content: center; + width: 48px; + height: 48px; + margin: 0 auto 1.5rem; + border-radius: 12px; + background: var(--accent); + color: var(--bg); + font-family: var(--font-mono); + font-weight: 700; + font-size: 28px; + line-height: 1; +} +.hero-word { + font-family: var(--font-display); + font-weight: 700; + font-size: clamp(2.6rem, 6vw, 3.6rem); + letter-spacing: -0.04em; + line-height: 1; + color: var(--text); + margin: 0; +} +.hero-tagline { + font-family: var(--font-mono); + font-size: 0.95rem; + letter-spacing: 0.01em; + color: var(--accent); + margin: 0.6rem 0 0; +} +.hero-sub { + font-size: 1.02rem; + line-height: 1.65; + color: var(--text-mid); + max-width: 48ch; + margin: 1.5rem auto 0; +} +.hero-banner { + display: inline-block; + margin: 1.75rem auto 0; + padding: 0.4rem 0.9rem; + border: 1px solid var(--border-light); + border-radius: 999px; + font-family: var(--font-mono); + font-size: 0.7rem; + letter-spacing: 0.1em; + text-transform: uppercase; + color: var(--text-dim); +} +.hero-ctas { + display: flex; + gap: 0.75rem; + justify-content: center; + flex-wrap: wrap; + margin-top: 2rem; +} + +/* ── Body (hand-authored MDX prose) ───────────────────── */ +.body { + border-bottom: 1px solid var(--border); +} +.prose { + max-width: 44rem; + margin: 0 auto; + padding: 4rem 1.5rem 4.5rem; +} +.prose h2 { + position: relative; + font-family: var(--font-display); + font-weight: 700; + font-size: 1.5rem; + letter-spacing: -0.03em; + color: var(--text); + padding-left: 0.85rem; + margin: 3rem 0 1rem; +} +.prose h2:first-child { + margin-top: 0; +} +/* the little accent bar that gives the dodeca/vixen heading feel */ +.prose h2::before { + content: ""; + position: absolute; + left: 0; + top: 0.2em; + bottom: 0.15em; + width: 4px; + border-radius: 2px; + background: var(--accent); +} +.prose h3 { + font-family: var(--font-display); + font-weight: 600; + font-size: 1.15rem; + letter-spacing: -0.02em; + color: var(--text); + margin: 2rem 0 0.75rem; +} +.prose p { + font-size: 1.02rem; + line-height: 1.78; + color: var(--text-mid); + margin: 0 0 1.15rem; +} +.prose ul, +.prose ol { + margin: 0 0 1.15rem 1.25rem; + list-style: revert; + color: var(--text-mid); + line-height: 1.78; +} +.prose li { + margin: 0.3rem 0; +} +.prose a { + color: var(--accent); + border-bottom: 1px solid color-mix(in srgb, var(--accent) 35%, transparent); + transition: border-color 0.15s; +} +.prose a:hover { + border-bottom-color: var(--accent); +} +/* inline code — subtle tint, the way vixen styles it */ +.prose :not(pre) > code { + font-family: var(--font-mono); + font-size: 0.88em; + color: var(--accent); + background: var(--accent-glow); + padding: 0.1em 0.36em; + border-radius: 4px; +} + +/* ── Code blocks (expressive-code), flattened ─────────── */ +/* Strip the window chrome so blocks read as flat documents, not a + terminal app. Token colors still come from expressive-code's themes, + which track the light/dark toggle. */ +.prose .expressive-code { + margin: 1.5rem 0; +} +.prose .expressive-code .frame { + box-shadow: none !important; +} +.prose .expressive-code .frame .header { + display: none !important; +} +.prose .expressive-code pre { + border: 1px solid var(--border); + border-radius: var(--radius); + padding: 1rem 1.15rem; +} + +/* ── Install snippet (flat, centered in closing) ──────── */ +.install-snippet { + width: 100%; + max-width: 540px; + margin: 0 auto; + text-align: left; + background: var(--surface); + border: 1px solid var(--border); + border-radius: var(--radius); + overflow: hidden; +} +.install-snippet figcaption { + display: flex; + align-items: center; + gap: 0.5rem; + padding: 0.5rem 0.9rem; + background: var(--bg-deep); + border-bottom: 1px solid var(--border); + font-family: var(--font-mono); + font-size: 0.66rem; + letter-spacing: 0.12em; + text-transform: uppercase; + color: var(--text-dim); +} +.install-shell { + color: var(--text-dim); +} +.install-copy { + margin-left: auto; + background: transparent; + border: 1px solid var(--border); + border-radius: 4px; + padding: 0.15rem 0.55rem; + font-family: var(--font-mono); + font-size: 0.64rem; + letter-spacing: 0.12em; + text-transform: uppercase; + color: var(--text-mid); + cursor: pointer; + transition: border-color 0.15s, color 0.15s, background 0.15s; +} +.install-copy:hover { + color: var(--text); + border-color: var(--text-dim); +} +.install-copy-done { + color: var(--green) !important; + border-color: var(--green) !important; + background: var(--green-dim) !important; +} +.install-snippet pre { + margin: 0; + padding: 0.95rem 1.1rem; + font-family: var(--font-mono); + font-size: 0.84rem; + line-height: 1.6; + color: var(--text); + background: transparent; + overflow-x: auto; + white-space: pre; +} +.install-snippet .t-prompt { color: var(--green); font-weight: 600; margin-right: 0.5em; } +.install-snippet .t-cmd { color: var(--accent); font-weight: 500; } +.install-snippet .t-flag { color: var(--text-mid); } +.install-snippet .t-url { color: var(--cyan); } +.install-snippet .t-pipe { color: var(--text-mid); margin: 0 0.15em; } + +/* ── Closing (quiet, centered) ────────────────────────── */ +.closing { + text-align: center; + padding: 4rem 1.5rem 5rem; +} +.closing-inner { + max-width: 44rem; + margin: 0 auto; + display: flex; + flex-direction: column; + align-items: center; + gap: 1.5rem; +} +.closing-lead { + font-size: 1.05rem; + color: var(--text-mid); + margin: 0; +} +.closing-links { + font-family: var(--font-mono); + font-size: 0.85rem; + color: var(--text-dim); +} +.closing-links a { color: var(--accent); } +.closing-links a:hover { color: var(--text); } + +/* ── Responsive ───────────────────────────────────────── */ +@media (max-width: 700px) { + .hero { padding: 3.5rem 1.25rem 2.5rem; } + .prose { padding: 3rem 1.25rem 3.5rem; } + .closing { padding: 3rem 1.25rem 4rem; } +} From 2e1d5dd9d607abf8881ad7e0888aece7edc8d9c1 Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Wed, 17 Jun 2026 21:48:37 +0200 Subject: [PATCH 2/3] Initial massages --- src/home/index.mdx | 57 +++++++++++++++++++++++++++++++++------------- 1 file changed, 41 insertions(+), 16 deletions(-) diff --git a/src/home/index.mdx b/src/home/index.mdx index 016c1c7..c6c26a0 100644 --- a/src/home/index.mdx +++ b/src/home/index.mdx @@ -1,30 +1,55 @@ ## Why? -Builds get slower one commit at a time, and nobody notices until the whole -team is waiting. Wezel watches the build scenarios you actually run and tells -you the moment one of them regresses — while the change is still fresh enough -to fix. +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 -An `experiment.toml`, committed next to your code, declares what to measure. -The `cargo` tool builds your target and emits per-crate timing outcomes, and a +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" -[step.cargo.build] -build_target = "workspace" - -summary.workspace-build-time = { outcome = "duration.total" } -summary.slowest-crate-time = { outcome = "duration.max" } +[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 } ``` -On every commit, the CLI walks the steps, captures the outcomes, and compares -each summary against the trailing baseline. When a summary drifts past -tolerance, Wezel reports the offending commit, the delta, and the affected -scope. +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. ```console $ wezel experiment run workspace-build @@ -34,7 +59,7 @@ $ wezel experiment run workspace-build done in 59.9s ``` -## Stay tuned +## Where Wezel is open source and under active development. Follow along on [GitHub](https://github.com/wezel-build/wezel), or read the From b471ab3ecc2cb84995f7a277a8b7d581b0494445 Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Thu, 18 Jun 2026 10:19:02 +0200 Subject: [PATCH 3/3] Land simplified page, v0 --- astro.config.mjs | 14 +- public/favicon.svg | 4 + src/content.config.ts | 17 +-- src/content/docs/docs/index.md | 5 - src/home/index.mdx | 17 +-- src/layouts/Base.astro | 55 +------ src/lib/authors.ts | 31 ---- src/pages/blog/[id].astro | 259 --------------------------------- src/pages/blog/index.astro | 201 ------------------------- src/styles/global.css | 45 +----- src/styles/starlight.css | 83 +++++++++++ 11 files changed, 117 insertions(+), 614 deletions(-) create mode 100644 public/favicon.svg delete mode 100644 src/content/docs/docs/index.md delete mode 100644 src/lib/authors.ts delete mode 100644 src/pages/blog/[id].astro delete mode 100644 src/pages/blog/index.astro create mode 100644 src/styles/starlight.css diff --git a/astro.config.mjs b/astro.config.mjs index c77788b..fce42ac 100644 --- a/astro.config.mjs +++ b/astro.config.mjs @@ -6,6 +6,11 @@ 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: { @@ -47,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: { 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 @@ + + + w + 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 index c6c26a0..65990dc 100644 --- a/src/home/index.mdx +++ b/src/home/index.mdx @@ -32,6 +32,7 @@ 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"] @@ -51,16 +52,14 @@ 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. -```console -$ wezel experiment run workspace-build -→ step.cargo.build ........... 59.7s -→ summary.workspace-build-time 59.7s (baseline 47.3s · +12.4s · regression) -→ summary.slowest-crate-time 8.4s (baseline 8.1s · +0.3s) -done in 59.9s -``` +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), or read the -[quickstart](/docs/quickstart) to wire it into your own builds. +[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 c2a632a..c7eab0a 100644 --- a/src/layouts/Base.astro +++ b/src/layouts/Base.astro @@ -9,36 +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; --- - + - + {title} - -
@@ -170,25 +138,6 @@ const themes = ["dark", "light"] as const; color: var(--text); } - .theme-btn { - background: var(--surface2); - border: 1px solid var(--border); - border-radius: 4px; - padding: 2px 10px; - cursor: pointer; - color: var(--text-mid); - font-size: 0.72rem; - font-family: var(--font-mono); - font-weight: 500; - letter-spacing: 0.02em; - transition: border-color 0.15s, color 0.15s; - } - - .theme-btn:hover { - border-color: var(--text-dim); - color: var(--text); - } - /* ── Footer ──────────────────────────────── */ .footer { border-top: 1px solid var(--border); diff --git a/src/lib/authors.ts b/src/lib/authors.ts deleted file mode 100644 index efe480a..0000000 --- a/src/lib/authors.ts +++ /dev/null @@ -1,31 +0,0 @@ -export type Author = { - login: string; - name: string; - avatar: string; - htmlUrl: string; -}; - -const cache = new Map>(); - -function fetchOne(login: string): Promise { - let cached = cache.get(login); - if (cached) return cached; - cached = fetch(`https://api.github.com/users/${login}`, { - headers: { 'User-Agent': 'wezel-blog-build' }, - }).then(async (res) => { - if (!res.ok) throw new Error(`GitHub API ${res.status} for @${login}`); - const data = await res.json(); - return { - login: data.login, - name: data.name ?? data.login, - avatar: data.avatar_url, - htmlUrl: data.html_url, - }; - }); - cache.set(login, cached); - return cached; -} - -export function fetchAuthors(logins: readonly string[]): Promise { - return Promise.all(logins.map(fetchOne)); -} diff --git a/src/pages/blog/[id].astro b/src/pages/blog/[id].astro deleted file mode 100644 index 1bcb840..0000000 --- a/src/pages/blog/[id].astro +++ /dev/null @@ -1,259 +0,0 @@ ---- -import Base from '../../layouts/Base.astro'; -import { getCollection, render } from 'astro:content'; -import { fetchAuthors } from '../../lib/authors'; - -export async function getStaticPaths() { - const posts = await getCollection('blog', ({ data }) => !data.draft); - return Promise.all( - posts.map(async (post) => ({ - params: { id: post.id }, - props: { post, authors: await fetchAuthors(post.data.author) }, - })), - ); -} - -const { post, authors } = Astro.props; -const { Content } = await render(post); ---- - - -
-
- ← All posts - -

{post.data.title}

-

{post.data.description}

- -
-
- -
-
- - - diff --git a/src/pages/blog/index.astro b/src/pages/blog/index.astro deleted file mode 100644 index 4100e92..0000000 --- a/src/pages/blog/index.astro +++ /dev/null @@ -1,201 +0,0 @@ ---- -import Base from '../../layouts/Base.astro'; -import { getCollection } from 'astro:content'; -import { fetchAuthors, type Author } from '../../lib/authors'; - -const posts = (await getCollection('blog', ({ data }) => !data.draft)) - .sort((a, b) => b.data.date.getTime() - a.data.date.getTime()); - -const authorsByPost = new Map( - await Promise.all( - posts.map( - async (post) => [post.id, await fetchAuthors(post.data.author)] as const, - ), - ), -); ---- - - -
-
- -

From the hive

-

- Thoughts on build performance, developer tooling, and building Wezel. -

-
-
- -
- -
- - - diff --git a/src/styles/global.css b/src/styles/global.css index 20eb888..4460129 100644 --- a/src/styles/global.css +++ b/src/styles/global.css @@ -76,33 +76,8 @@ ol { --radius-lg: 12px; } -/* ── Warm (default) ────────────────────────────────── */ -:root, -[data-theme="dark"] { - --bg: #141210; - --bg-deep: #0e0d0b; - --surface: #1c1a17; - --surface2: #242220; - --surface3: #2e2c28; - --text: #d0ccc4; - --text-mid: #9a9488; - --text-dim: #8b8075; - --accent: #b08868; - --accent-glow: rgba(176, 136, 104, 0.12); - --accent-glow-strong: rgba(176, 136, 104, 0.25); - --green: #7a9870; - --green-dim: rgba(122, 152, 112, 0.15); - --cyan: #7a9ca0; - --cyan-dim: rgba(122, 156, 160, 0.15); - --red: #c27458; - --amber: #b89860; - --border: #2a2724; - --border-light: #38342e; - --nav-bg: rgba(20, 18, 16, 0.82); -} - -/* ── Light ─────────────────────────────────────────── */ -[data-theme="light"] { +/* ── Warm paper (light — the only theme) ───────────────── */ +:root { --bg: #f5f2ee; --bg-deep: #ece8e3; --surface: #faf8f5; @@ -170,8 +145,8 @@ samp { } .btn-primary:hover { - background: #c09878; - border-color: #c09878; + background: #7a5028; + border-color: #7a5028; } .btn-secondary { @@ -220,7 +195,7 @@ samp { .card:hover { border-color: var(--border-light); - box-shadow: 0 4px 24px rgba(0, 0, 0, 0.2); + box-shadow: 0 4px 24px rgba(0, 0, 0, 0.06); } /* ===== Labels / Tags ===== */ @@ -269,16 +244,6 @@ samp { padding-inline: 2rem; } -/* ===== Subtle noise texture on body ===== */ -[data-theme="light"] .btn-primary:hover { - background: #7a5028; - border-color: #7a5028; -} - -[data-theme="light"] .card:hover { - box-shadow: 0 4px 24px rgba(0, 0, 0, 0.06); -} - /* ===== Responsive ===== */ @media (max-width: 768px) { .container { diff --git a/src/styles/starlight.css b/src/styles/starlight.css new file mode 100644 index 0000000..b60c5e8 --- /dev/null +++ b/src/styles/starlight.css @@ -0,0 +1,83 @@ +/* Align Starlight's docs with the landing's warm-paper look. + The site is light-only, so we force every theme state to the same + light values and hide the theme switcher. */ + +@import '@fontsource/ibm-plex-sans/latin-400.css'; +@import '@fontsource/ibm-plex-sans/latin-500.css'; +@import '@fontsource/ibm-plex-sans/latin-600.css'; +@import '@fontsource/ibm-plex-sans/latin-700.css'; +@import '@fontsource/iosevka/latin-400.css'; +@import '@fontsource/iosevka/latin-500.css'; +@import '@fontsource/iosevka-aile/latin-600.css'; +@import '@fontsource/iosevka-aile/latin-700.css'; + +/* Force the warm-paper palette regardless of the data-theme attribute. */ +:root, +:root[data-theme='light'], +:root[data-theme='dark'] { + /* Accent (matches --accent #8a5e2e on the landing) */ + --sl-color-accent-low: #e7dccb; + --sl-color-accent: #8a5e2e; + --sl-color-accent-high: #5e3f1f; + + /* Text → background ramp, warm and light. + In Starlight's light theme, white = darkest text, black = page bg. */ + --sl-color-white: #28241f; + --sl-color-gray-1: #2f2b25; + --sl-color-gray-2: #46413a; + --sl-color-gray-3: #56504a; + --sl-color-gray-4: #746e68; + --sl-color-gray-5: #c8c2ba; + --sl-color-gray-6: #d6d0c8; + --sl-color-gray-7: #ece8e3; + --sl-color-black: #f5f2ee; + + /* Semantic surfaces */ + --sl-color-bg: #f5f2ee; + --sl-color-bg-nav: rgba(245, 242, 238, 0.88); + --sl-color-bg-sidebar: #faf8f5; + --sl-color-bg-inline-code: var(--sl-color-accent-low); + --sl-color-hairline: #d6d0c8; + --sl-color-hairline-light: #e2ddd7; + --sl-color-hairline-shade: #e2ddd7; + --sl-color-text: #56504a; + --sl-color-text-accent: #8a5e2e; + --sl-color-text-invert: #f5f2ee; + + /* Fonts (match the landing) */ + --sl-font: "IBM Plex Sans", ui-sans-serif, system-ui, -apple-system, sans-serif; + --sl-font-mono: "Iosevka", "JetBrains Mono", ui-monospace, "SFMono-Regular", monospace; +} + +/* Light-only: drop the theme switcher entirely. */ +starlight-theme-select { + display: none !important; +} + +/* Headings in the Iosevka Aile display face, like the landing. */ +.sl-markdown-content :is(h1, h2, h3, h4, h5, h6), +.site-title { + font-family: "Iosevka Aile", "Iosevka", ui-sans-serif, system-ui, sans-serif; + letter-spacing: -0.02em; +} + +/* The little accent bar on section headings — the dodeca/vixen tell. */ +.sl-markdown-content h2 { + position: relative; + padding-left: 0.85rem; +} +.sl-markdown-content h2::before { + content: ""; + position: absolute; + left: 0; + top: 0.18em; + bottom: 0.12em; + width: 4px; + border-radius: 2px; + background: var(--sl-color-accent); +} + +/* Site title wordmark in the header, to echo the nav logo. */ +.site-title { + font-weight: 700; +}