diff --git a/CLAUDE.md b/CLAUDE.md index e9a0782..5fd294c 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -7,10 +7,15 @@ Static site generator for Vortex RFC proposals built with Bun. ``` index.ts - Main build script and dev server styles.css - Site styling (light/dark themes) -proposals/ - RFC markdown files (format: NNNN-slug.md) +proposed/ - RFC markdown files in proposed state +accepted/ - RFC markdown files in accepted state +completed/ - RFC markdown files in completed state dist/ - Build output (gitignored) ``` +RFC filenames follow the format `NNNN-slug.md` (e.g., `0001-galp-patches.md`). +Numbering is global across all states - no duplicates allowed. + ## Commands ```sh @@ -21,19 +26,30 @@ bun run clean # Remove dist/ ## How the Build Works -1. Scans `proposals/*.md` for RFC files +1. Scans `proposed/`, `accepted/`, `completed/` for RFC files 2. Parses RFC number from filename (e.g., `0002-foo.md` → RFC 0002) -3. Extracts title from first `# ` heading -4. Converts markdown to HTML using `Bun.markdown.html()` -5. Generates `dist/index.html` (table of contents) -6. Generates `dist/rfc/{number}.html` for each RFC +3. Determines state from containing folder +4. Extracts title from first `# ` heading +5. Converts markdown to HTML using `Bun.markdown.html()` +6. Generates `dist/index.html` (table of contents with filter UI) +7. Generates `dist/rfc/{number}.html` for each RFC ## Dev Server - Uses `Bun.serve()` to serve static files from `dist/` -- Watches `proposals/` and `styles.css` for changes +- Watches `proposed/`, `accepted/`, `completed/`, and `styles.css` for changes - SSE endpoint at `/__reload` for live reload +## RFC States + +RFCs progress through three states by moving files between folders: + +- **proposed**: New RFCs under discussion +- **accepted**: Approved RFCs ready for implementation +- **completed**: Fully implemented RFCs + +The index page shows a state pill for each RFC and supports filtering by state. + ## Styling - CSS custom properties for theming (`--bg`, `--fg`, `--link`, etc.) diff --git a/accepted/.keep b/accepted/.keep new file mode 100644 index 0000000..e69de29 diff --git a/proposals/0000-template.md b/accepted/0000-template.md similarity index 93% rename from proposals/0000-template.md rename to accepted/0000-template.md index 5e9c030..9eda702 100644 --- a/proposals/0000-template.md +++ b/accepted/0000-template.md @@ -1,5 +1,4 @@ - Start Date: (today's date, YYYY-MM-DD) -- RFC PR: [vortex-data/rfcs#0000](https://github.com/vortex-data/rfcs/pull/0000) - Tracking Issue: [vortex-data/vortex#0000](https://github.com/vortex-data/vortex/issues/0000) ## Summary @@ -23,7 +22,14 @@ Describe the proposed design in enough detail that someone familiar with Vortex - Why is this the best approach in the space of possible designs? - Which crates are affected and how the dependency graph changes, if at all. -Use code examples and diagrams where they might help. +Use code examples and diagrams where they might help, like this: + +```rust +pub fn main() { + let x = f32::to_bits(100.0f32); + dbg!(x); +} +``` ## Compatibility diff --git a/bun.lock b/bun.lock index f87bb78..0820453 100644 --- a/bun.lock +++ b/bun.lock @@ -1,8 +1,12 @@ { "lockfileVersion": 1, + "configVersion": 0, "workspaces": { "": { "name": "vortexrfc", + "dependencies": { + "shiki": "^4.0.0", + }, "devDependencies": { "@types/bun": "latest", "prettier": "^3.8.1", @@ -13,16 +17,106 @@ }, }, "packages": { + "@shikijs/core": ["@shikijs/core@4.0.0", "", { "dependencies": { "@shikijs/primitive": "4.0.0", "@shikijs/types": "4.0.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-tvV94Dwyz4qFZ8R0MUaFx5Yptgy8yrloa4dwynEJDGjKz+8vqO8Q6FmPZL9W1gSzFHOUMOGQzIHK62aGourFxA=="], + + "@shikijs/engine-javascript": ["@shikijs/engine-javascript@4.0.0", "", { "dependencies": { "@shikijs/types": "4.0.0", "@shikijs/vscode-textmate": "^10.0.2", "oniguruma-to-es": "^4.3.4" } }, "sha512-+PEyTS+JTz2lLy2C1Dwwx6hzoehIzqxQYh5MEjv9V4JtSabx+bIkRHfQT+6DnBmPAplGH0exBknWeiJSXC7w1w=="], + + "@shikijs/engine-oniguruma": ["@shikijs/engine-oniguruma@4.0.0", "", { "dependencies": { "@shikijs/types": "4.0.0", "@shikijs/vscode-textmate": "^10.0.2" } }, "sha512-KXmq4b6Xw16+4+rz5M4NZMoe/tzs5kTOMSJz8+LCyxSrwmxwTBAM/ab85iSO2Gw79E47HkW4B9HPHUXhrNOivw=="], + + "@shikijs/langs": ["@shikijs/langs@4.0.0", "", { "dependencies": { "@shikijs/types": "4.0.0" } }, "sha512-dSAT6fBcnOcYZQMWZO8+OmzUKKm+OO0As/qZ3TXLiSy0JsCTEYz1TaX7TDupnYLz7dr0oF2DOTEgPocx1D3aFw=="], + + "@shikijs/primitive": ["@shikijs/primitive@4.0.0", "", { "dependencies": { "@shikijs/types": "4.0.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-6K2zD7JTgsyFc2vM1rqy8eRGC8D5Hius3qzVONjq2lHMrqfTSn1HcGeJZiFPYSV9m3DQuBHncBbA5xe0hKSOkQ=="], + + "@shikijs/themes": ["@shikijs/themes@4.0.0", "", { "dependencies": { "@shikijs/types": "4.0.0" } }, "sha512-xe42kvxOXan5ouXxULez6qwDNUJkoP6kicfg0wKuJBkeIaHLxZBZa2gEGYutL1q27DQZ5+XoR6caVX+E/aNR5A=="], + + "@shikijs/types": ["@shikijs/types@4.0.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-LCnfBTtQKNtJyc1qMShZr2dJt1uxNI6pI0/YTc2DSNET91aUvnMGHUHsucVCC5AJVcv5XyBqk2NgYRwd20EjbA=="], + + "@shikijs/vscode-textmate": ["@shikijs/vscode-textmate@10.0.2", "", {}, "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg=="], + "@types/bun": ["@types/bun@1.3.9", "", { "dependencies": { "bun-types": "1.3.9" } }, "sha512-KQ571yULOdWJiMH+RIWIOZ7B2RXQGpL1YQrBtLIV3FqDcCu6FsbFUBwhdKUlCKUpS3PJDsHlJ1QKlpxoVR+xtw=="], + "@types/hast": ["@types/hast@3.0.4", "", { "dependencies": { "@types/unist": "*" } }, "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ=="], + + "@types/mdast": ["@types/mdast@4.0.4", "", { "dependencies": { "@types/unist": "*" } }, "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA=="], + "@types/node": ["@types/node@25.3.0", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-4K3bqJpXpqfg2XKGK9bpDTc6xO/xoUP/RBWS7AtRMug6zZFaRekiLzjVtAoZMquxoAbzBvy5nxQ7veS5eYzf8A=="], + "@types/unist": ["@types/unist@3.0.3", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="], + + "@ungap/structured-clone": ["@ungap/structured-clone@1.3.0", "", {}, "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g=="], + "bun-types": ["bun-types@1.3.9", "", { "dependencies": { "@types/node": "*" } }, "sha512-+UBWWOakIP4Tswh0Bt0QD0alpTY8cb5hvgiYeWCMet9YukHbzuruIEeXC2D7nMJPB12kbh8C7XJykSexEqGKJg=="], + "ccount": ["ccount@2.0.1", "", {}, "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg=="], + + "character-entities-html4": ["character-entities-html4@2.1.0", "", {}, "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA=="], + + "character-entities-legacy": ["character-entities-legacy@3.0.0", "", {}, "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ=="], + + "comma-separated-tokens": ["comma-separated-tokens@2.0.3", "", {}, "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg=="], + + "dequal": ["dequal@2.0.3", "", {}, "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA=="], + + "devlop": ["devlop@1.1.0", "", { "dependencies": { "dequal": "^2.0.0" } }, "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA=="], + + "hast-util-to-html": ["hast-util-to-html@9.0.5", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", "ccount": "^2.0.0", "comma-separated-tokens": "^2.0.0", "hast-util-whitespace": "^3.0.0", "html-void-elements": "^3.0.0", "mdast-util-to-hast": "^13.0.0", "property-information": "^7.0.0", "space-separated-tokens": "^2.0.0", "stringify-entities": "^4.0.0", "zwitch": "^2.0.4" } }, "sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw=="], + + "hast-util-whitespace": ["hast-util-whitespace@3.0.0", "", { "dependencies": { "@types/hast": "^3.0.0" } }, "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw=="], + + "html-void-elements": ["html-void-elements@3.0.0", "", {}, "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg=="], + + "mdast-util-to-hast": ["mdast-util-to-hast@13.2.1", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "@ungap/structured-clone": "^1.0.0", "devlop": "^1.0.0", "micromark-util-sanitize-uri": "^2.0.0", "trim-lines": "^3.0.0", "unist-util-position": "^5.0.0", "unist-util-visit": "^5.0.0", "vfile": "^6.0.0" } }, "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA=="], + + "micromark-util-character": ["micromark-util-character@2.1.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q=="], + + "micromark-util-encode": ["micromark-util-encode@2.0.1", "", {}, "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw=="], + + "micromark-util-sanitize-uri": ["micromark-util-sanitize-uri@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-encode": "^2.0.0", "micromark-util-symbol": "^2.0.0" } }, "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ=="], + + "micromark-util-symbol": ["micromark-util-symbol@2.0.1", "", {}, "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q=="], + + "micromark-util-types": ["micromark-util-types@2.0.2", "", {}, "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA=="], + + "oniguruma-parser": ["oniguruma-parser@0.12.1", "", {}, "sha512-8Unqkvk1RYc6yq2WBYRj4hdnsAxVze8i7iPfQr8e4uSP3tRv0rpZcbGUDvxfQQcdwHt/e9PrMvGCsa8OqG9X3w=="], + + "oniguruma-to-es": ["oniguruma-to-es@4.3.4", "", { "dependencies": { "oniguruma-parser": "^0.12.1", "regex": "^6.0.1", "regex-recursion": "^6.0.2" } }, "sha512-3VhUGN3w2eYxnTzHn+ikMI+fp/96KoRSVK9/kMTcFqj1NRDh2IhQCKvYxDnWePKRXY/AqH+Fuiyb7VHSzBjHfA=="], + "prettier": ["prettier@3.8.1", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg=="], + "property-information": ["property-information@7.1.0", "", {}, "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ=="], + + "regex": ["regex@6.1.0", "", { "dependencies": { "regex-utilities": "^2.3.0" } }, "sha512-6VwtthbV4o/7+OaAF9I5L5V3llLEsoPyq9P1JVXkedTP33c7MfCG0/5NOPcSJn0TzXcG9YUrR0gQSWioew3LDg=="], + + "regex-recursion": ["regex-recursion@6.0.2", "", { "dependencies": { "regex-utilities": "^2.3.0" } }, "sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg=="], + + "regex-utilities": ["regex-utilities@2.3.0", "", {}, "sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng=="], + + "shiki": ["shiki@4.0.0", "", { "dependencies": { "@shikijs/core": "4.0.0", "@shikijs/engine-javascript": "4.0.0", "@shikijs/engine-oniguruma": "4.0.0", "@shikijs/langs": "4.0.0", "@shikijs/themes": "4.0.0", "@shikijs/types": "4.0.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-rjKoiw30ZaFsM0xnPPwxco/Jftz/XXqZkcQZBTX4LGheDw8gCDEH87jdgaKDEG3FZO2bFOK27+sR/sDHhbBXfg=="], + + "space-separated-tokens": ["space-separated-tokens@2.0.2", "", {}, "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q=="], + + "stringify-entities": ["stringify-entities@4.0.4", "", { "dependencies": { "character-entities-html4": "^2.0.0", "character-entities-legacy": "^3.0.0" } }, "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg=="], + + "trim-lines": ["trim-lines@3.0.1", "", {}, "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg=="], + "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], "undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="], + + "unist-util-is": ["unist-util-is@6.0.1", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g=="], + + "unist-util-position": ["unist-util-position@5.0.0", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA=="], + + "unist-util-stringify-position": ["unist-util-stringify-position@4.0.0", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ=="], + + "unist-util-visit": ["unist-util-visit@5.1.0", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0", "unist-util-visit-parents": "^6.0.0" } }, "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg=="], + + "unist-util-visit-parents": ["unist-util-visit-parents@6.0.2", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0" } }, "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ=="], + + "vfile": ["vfile@6.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "vfile-message": "^4.0.0" } }, "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q=="], + + "vfile-message": ["vfile-message@4.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw=="], + + "zwitch": ["zwitch@2.0.4", "", {}, "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A=="], } } diff --git a/completed/.keep b/completed/.keep new file mode 100644 index 0000000..e69de29 diff --git a/index.ts b/index.ts index 9d6b2a8..291dc87 100644 --- a/index.ts +++ b/index.ts @@ -1,5 +1,6 @@ import { $, type Server } from "bun"; import { watch } from "fs"; +import { createHighlighter, type Highlighter } from "shiki"; const isDev = process.argv.includes("--dev"); const PORT = 3000; @@ -21,12 +22,17 @@ interface RFCGitInfo { author: GitHubAuthor | null; } +type RFCState = "proposed" | "accepted" | "completed"; + +const RFC_STATES: RFCState[] = ["proposed", "accepted", "completed"]; + interface RFC { number: string; title: string; filename: string; html: string; git: RFCGitInfo; + state: RFCState; } const THEME_SCRIPT = ` @@ -65,6 +71,33 @@ function updateToggleIcon() { document.addEventListener('DOMContentLoaded', updateToggleIcon); `; +const FILTER_SCRIPT = ` +function filterRFCs(state) { + const items = document.querySelectorAll('.rfc-list li'); + const buttons = document.querySelectorAll('.filter-btn'); + + buttons.forEach(btn => { + btn.classList.toggle('active', btn.dataset.state === state); + }); + + items.forEach(item => { + if (state === 'all' || item.dataset.state === state) { + item.style.display = ''; + } else { + item.style.display = 'none'; + } + }); + + // Save filter preference + localStorage.setItem('rfc-filter', state); +} + +document.addEventListener('DOMContentLoaded', function() { + const saved = localStorage.getItem('rfc-filter') || 'all'; + filterRFCs(saved); +}); +`; + const LIVE_RELOAD_SCRIPT = ` (function() { const evtSource = new EventSource('/__reload'); @@ -127,6 +160,10 @@ function escapeHTML(str: string): string { .replace(/"/g, """); } +function stateLabel(state: RFCState): string { + return state.charAt(0).toUpperCase() + state.slice(1); +} + function indexPage( rfcs: RFC[], repoUrl: string | null, @@ -152,9 +189,10 @@ function indexPage( } return ` -
Technical proposals for the Vortex file format.
+${filterButtons}... blocks
+ const codeBlockRegex =
+ /([\s\S]*?)<\/code><\/pre>/g;
+
+ const matches = [...html.matchAll(codeBlockRegex)];
+ let result = html;
+
+ for (const match of matches) {
+ const [fullMatch, lang, code] = match;
+ if (!lang || !code) continue;
+
+ // Decode HTML entities back to raw code
+ const rawCode = code
+ .replace(/</g, "<")
+ .replace(/>/g, ">")
+ .replace(/&/g, "&")
+ .replace(/"/g, '"')
+ .replace(/'/g, "'");
+
+ // Check if this language is supported
+ const loadedLangs = hl.getLoadedLanguages();
+ if (!loadedLangs.includes(lang)) {
+ // Skip unsupported languages, leave original markup
+ continue;
+ }
+
+ // Generate highlighted HTML with both themes using CSS variables
+ const highlighted = hl.codeToHtml(rawCode.trim(), {
+ lang,
+ themes: {
+ light: "github-light",
+ dark: "github-dark",
+ },
+ defaultColor: false,
+ });
+
+ result = result.replace(fullMatch, highlighted);
+ }
+
+ return result;
+}
+
async function build(liveReload: boolean = false): Promise {
console.log("Building Vortex RFC site...\n");
@@ -402,22 +515,29 @@ async function build(liveReload: boolean = false): Promise {
const glob = new Bun.Glob("*.md");
const rfcs: RFC[] = [];
- // Parse all RFC markdown files
- for await (const filename of glob.scan("./proposals")) {
- console.log(`Processing ${filename}...`);
+ // Parse all RFC markdown files from each state folder
+ for (const state of RFC_STATES) {
+ const folder = `./${state}`;
+
+ for await (const filename of glob.scan(folder)) {
+ console.log(`Processing ${state}/${filename}...`);
- const path = `./proposals/${filename}`;
- const content = await Bun.file(path).text();
- const html = Bun.markdown.html(content, { autolinks: true });
- const number = parseRFCNumber(filename);
- const title = parseTitle(content, filename);
- const git = await getGitHistory(path, repoPath);
+ const path = `${folder}/${filename}`;
+ const content = await Bun.file(path).text();
+ const rawHtml = Bun.markdown.html(content, { autolinks: true });
+ const html = await highlightCodeBlocks(rawHtml);
+ const number = parseRFCNumber(filename);
+ const title = parseTitle(content, filename);
+ const git = await getGitHistory(path, repoPath);
- rfcs.push({ number, title, filename, html, git });
+ rfcs.push({ number, title, filename, html, git, state });
+ }
}
if (rfcs.length === 0) {
- console.log("No RFC files found in ./proposals/");
+ console.log(
+ "No RFC files found in ./proposed/, ./accepted/, or ./completed/",
+ );
return 0;
}
@@ -468,7 +588,9 @@ async function startDevServer() {
// Initial build with live reload enabled
await build(true);
console.log(`\nStarting dev server at http://localhost:${PORT}`);
- console.log("Watching for changes in ./proposals/ and ./styles.css\n");
+ console.log(
+ "Watching for changes in ./proposed/, ./accepted/, ./completed/, and ./styles.css\n",
+ );
// Debounce rebuilds
let rebuildTimeout: Timer | null = null;
@@ -481,12 +603,14 @@ async function startDevServer() {
}, 100);
};
- // Watch proposals directory
- watch("./proposals", { recursive: true }, (_event, filename) => {
- if (filename?.endsWith(".md")) {
- scheduleRebuild();
- }
- });
+ // Watch all state directories
+ for (const state of RFC_STATES) {
+ watch(`./${state}`, { recursive: true }, (_event, filename) => {
+ if (filename?.endsWith(".md")) {
+ scheduleRebuild();
+ }
+ });
+ }
// Watch styles.css
watch("./styles.css", () => {
diff --git a/package.json b/package.json
index b539045..3cf61b3 100644
--- a/package.json
+++ b/package.json
@@ -14,5 +14,8 @@
},
"peerDependencies": {
"typescript": "^5"
+ },
+ "dependencies": {
+ "shiki": "^4.0.0"
}
}
diff --git a/proposed/.keep b/proposed/.keep
new file mode 100644
index 0000000..e69de29
diff --git a/styles.css b/styles.css
index 4f65a45..60c5f0b 100644
--- a/styles.css
+++ b/styles.css
@@ -493,3 +493,126 @@ footer {
color: var(--fg-muted);
font-size: 0.875rem;
}
+
+/* State filter bar */
+.filter-bar {
+ display: flex;
+ gap: 0.5rem;
+ margin-bottom: 1.5rem;
+ flex-wrap: wrap;
+}
+
+.filter-btn {
+ font-family: inherit;
+ font-size: 0.875rem;
+ padding: 0.375rem 0.75rem;
+ border: 1px solid var(--border);
+ border-radius: 4px;
+ background: var(--bg);
+ color: var(--fg-muted);
+ cursor: pointer;
+ transition:
+ background 0.15s ease,
+ color 0.15s ease,
+ border-color 0.15s ease;
+}
+
+.filter-btn:hover {
+ background: var(--bg-alt);
+ color: var(--fg);
+}
+
+.filter-btn.active {
+ background: var(--fg);
+ color: var(--bg);
+ border-color: var(--fg);
+}
+
+/* State pills */
+.rfc-state-pill {
+ display: inline-block;
+ font-size: 0.75rem;
+ font-weight: 500;
+ padding: 0.125rem 0.5rem;
+ border-radius: 9999px;
+ text-transform: capitalize;
+ flex-shrink: 0;
+}
+
+.state-proposed {
+ background: #fef3c7;
+ color: #92400e;
+}
+
+.state-accepted {
+ background: #dbeafe;
+ color: #1e40af;
+}
+
+.state-completed {
+ background: #d1fae5;
+ color: #065f46;
+}
+
+:root[data-theme="dark"] .state-proposed {
+ background: #78350f;
+ color: #fef3c7;
+}
+
+:root[data-theme="dark"] .state-accepted {
+ background: #1e3a8a;
+ color: #dbeafe;
+}
+
+:root[data-theme="dark"] .state-completed {
+ background: #064e3b;
+ color: #d1fae5;
+}
+
+/* Shiki syntax highlighting - dual theme support */
+pre.shiki {
+ font-family:
+ "IBM Plex Mono", "SF Mono", "Menlo", "Monaco", "Consolas", monospace;
+ border: 1px solid var(--border);
+ border-radius: 4px;
+ padding: 1rem;
+ overflow-x: auto;
+ margin-bottom: 1rem;
+ font-size: 0.875rem;
+ line-height: 1.5;
+}
+
+pre.shiki code {
+ background: none;
+ padding: 0;
+ border-radius: 0;
+}
+
+/* Light theme (default) */
+:root pre.shiki,
+:root pre.shiki span {
+ color: var(--shiki-light);
+ background-color: var(--shiki-light-bg);
+}
+
+:root[data-theme="light"] pre.shiki,
+:root[data-theme="light"] pre.shiki span {
+ color: var(--shiki-light);
+ background-color: var(--shiki-light-bg);
+}
+
+/* Dark theme */
+:root[data-theme="dark"] pre.shiki,
+:root[data-theme="dark"] pre.shiki span {
+ color: var(--shiki-dark);
+ background-color: var(--shiki-dark-bg);
+}
+
+/* System preference dark mode */
+@media (prefers-color-scheme: dark) {
+ :root:not([data-theme="light"]) pre.shiki,
+ :root:not([data-theme="light"]) pre.shiki span {
+ color: var(--shiki-dark);
+ background-color: var(--shiki-dark-bg);
+ }
+}