Skip to content

Commit fa0252a

Browse files
committed
Default the theme to the OS setting, and cut the README back
The switcher started dark regardless of what the visitor's machine was set to. A stored choice still wins; failing that the OS decides. The test is for light rather than dark so no-preference, an old browser and a blocked localStorage all still land on dark. site.js also follows the OS live while nobody has chosen, for anyone on a day/night schedule. The README had grown into a tour of the engine's quirks. What is left is how to run it, how to send a PR, and how deploys work; the two CFML rules that actually catch people out are kept, in the contributing section where they are read. It also no longer points at a tools/ directory that is not in the repository.
1 parent 14bdd98 commit fa0252a

3 files changed

Lines changed: 69 additions & 208 deletions

File tree

‎README.md‎

Lines changed: 41 additions & 200 deletions
Original file line numberDiff line numberDiff line change
@@ -1,227 +1,68 @@
1-
# RustCFML project website
1+
# rustcfml.org
22

3-
A small brochure site for [RustCFML](https://github.com/RustCFML/RustCFML), written in
4-
CFML and served by RustCFML itself. No build step, no framework, no generator: templates,
5-
one stylesheet, one script, and a rewrite file.
3+
The website for [RustCFML](https://github.com/RustCFML/RustCFML), written in CFML and
4+
served by RustCFML itself — on a Cloudflare Worker, compiled to WebAssembly.
65

76
## Run it
87

98
```bash
10-
rustcfml --serve . --port 8517 # development, picks up edits as you save
11-
rustcfml --serve . --production # warm caches, restart to reload
9+
rustcfml --serve . --port 8517
1210
```
1311

14-
Then open <http://localhost:8517>. Add `?reload` to any URL to rebuild the application
15-
scope without restarting the server.
12+
Then <http://localhost:8517>. Edits are picked up as you save; add `?reload` to any URL
13+
after changing `Application.cfc`, which is cached in the application scope.
1614

17-
```bash
18-
./tools/lint.py # hash/interpolation lint, must say "clean"
19-
./tools/check.sh 8517 # fetches every page and asserts it rendered
20-
./tools/layout.sh 8517 # headless Chrome: overflow, reveals, menu, theme
21-
./tools/import-examples.py # re-port /try's examples from the engine repo
22-
./tools/set-version.py # bump to GitHub's latest release
23-
./tools/set-version.py --check # exit 1 if the site is a release behind
24-
```
25-
26-
All three should pass before committing. `lint.py` catches the mistakes specific to this
27-
engine; the other two need a server running on the port you pass them. `layout.sh` needs
28-
Chrome and needs to sit in the web root for the duration, so it copies itself in and out
29-
again. It is the only reason `zz-render-probe.html` ever appears.
30-
31-
They have earned their place. Between them they found: a code sample interpolating its own
32-
variable, an `href="#anchor"` that swallowed the rest of its page, inline SVG icons with no
33-
intrinsic size filling a phone screen with orange, `1fr` grid tracks letting one long
34-
`curl` URL make the whole page horizontally scrollable, and a reveal animation that left
35-
content invisible with JavaScript switched off.
36-
37-
`layout.sh` also reports `CLIPPED`: a panel narrower than the content inside it. Page-level
38-
overflow is only the loudest symptom of a fixed minimum in a flexible track. The quiet
39-
version hides the end of a line and leaves the document measuring clean. That check found
40-
seventeen page/width combinations at once, including a `--serve` command showing 213 of the
41-
687 pixels it needed. Anything that is *meant* to scroll is listed in `MAY_CLIP` at the top
42-
of `tools/layout.html`; everything else that clips is a bug.
43-
44-
## Deploying to Cloudflare Workers
45-
46-
The site runs at <https://rustcfml.org> as a Worker: the engine compiled to WebAssembly,
47-
with the templates embedded in the binary and the stylesheet, scripts and images served
48-
as static assets. The wiring follows the reference host,
49-
[RustCFML-Cloudflare-worker](https://github.com/RustCFML/RustCFML-Cloudflare-worker).
15+
To run the deployed shape instead — the real Worker, in Cloudflare's runtime:
5016

5117
```bash
52-
npm install # postgres/mysql2 for the JSPI snippet, esbuild
53-
rustup target add wasm32-unknown-unknown
54-
wrangler deploy # runs scripts/stage-assets.sh, then worker-build
18+
npm install
19+
npx wrangler dev --port 8788 --local
5520
```
5621

57-
`wrangler dev` previews the same build locally. Neither needs a `rustcfml` binary: the
58-
engine arrives as the `cfml-worker` crate, pinned by tag in `Cargo.toml`.
22+
That one recompiles the engine on every change, so it is for checking deployment
23+
behaviour (clean URLs, static assets, redirects), not for writing content.
5924

60-
**Two halves, split deliberately.** `cfml_worker::handle_fetch` runs every file it
61-
resolves through the CFML VM, so a stylesheet embedded alongside the templates would be
62-
*executed* rather than served, and `#` in the CSS would be read as interpolation. So
63-
`build.rs` embeds only `.cfm` and `.cfc`, and `scripts/stage-assets.sh` copies `assets/`,
64-
`robots.txt` and `sitemap.xml` into a generated `public/` that Workers Static Assets
65-
serves. A request matching a static file never invokes the Worker; everything else falls
66-
through to it.
25+
## Contributing
6726

68-
**`urlrewrite.xml` is not read by the Worker host.** Its rules are ported into `resolve`
69-
in `src/lib.rs`: `/features` and `/features/` map to `features.cfm`, `Application.cfc`
70-
and the `includes/`, `lib/`, `logs/` and `tools/` directories are refused, and anything
71-
unresolvable renders `404.cfm` (which sets its own 404 status). Keep the two in step —
72-
the native server reads the XML, the Worker reads the Rust.
27+
Pull requests are welcome. Fork, branch, open a PR against `main`.
7328

74-
**For a local deploy,** put the engine's browser build in `assets/wasm/` first. It is
75-
16 MB, it is gitignored, and `/try` is a dead page without it:
29+
Worth knowing before you write CFML here, because the engine is stricter than a JVM
30+
one and both rules bite silently:
7631

77-
```bash
78-
# in the engine repo
79-
wasm-pack build crates/wasm --target web
80-
cp -R crates/wasm/pkg/* /path/to/rustcfml-site/assets/wasm/
81-
```
82-
83-
A single asset may be up to 25 MiB, so it is served as a static file like anything else.
84-
CI builds this for you, so copying by hand is only for deploying from a laptop.
85-
86-
### Following the engine
32+
- **Nothing interpolates outside `<cfoutput>`.** `#variable#` in plain markup renders as
33+
literal text.
34+
- **Inside `<cfoutput>`, `#` always starts something.** `href="#pricing"` is read as a
35+
variable. Literal hashes must be doubled: `##pricing`.
8736

88-
One number drives the whole build: the `cfml-worker` tag in `Cargo.toml`. The Worker
89-
compiles against it, `/try`'s browser engine is built from it, and `Application.cfc`
90-
quotes it in the footer and every download link. They are kept equal deliberately —
91-
they drifted 30 releases apart once, and the site told visitors it was a v0.653.14
92-
application while the Worker serving that page ran v0.685.5.
37+
House style is to interpolate only as `#( … )#`, and to keep code samples containing
38+
hashes outside output blocks.
9339

94-
| Workflow | When | What it does |
95-
|---|---|---|
96-
| `.github/workflows/update-rustcfml.yml` | hourly, or by hand | Bumps `Cargo.toml` and `Application.cfc` to the engine's current **stable** release, proves wasm32 still compiles, commits to `main`, then calls the deploy |
97-
| `.github/workflows/deploy.yml` | push to `main`, by hand, or called | Builds `/try`'s engine from the pinned tag, builds the Worker, deploys |
40+
Pages are flat: each one sets `request.section`, `request.title` and `request.desc`,
41+
includes `_head` and `_header`, writes its markup, and includes `_footer`. Content that
42+
is quoted in more than one place — nav, stats, FAQ, docs index — lives in
43+
`Application.cfc`.
9844

99-
"Stable" means the release a human promoted, not the newest tag: RustCFML publishes
100-
every version tag as a prerelease, so following tags would bump this site a dozen times
101-
in two days. Neither workflow needs any access to the engine repository.
45+
## Deploying
10246

103-
Two repository secrets are required:
47+
Pushing to `main` deploys. An hourly job also follows the engine's stable releases and
48+
rebuilds when one lands.
10449

105-
| Secret | For |
106-
|---|---|
107-
| `CLOUDFLARE_API_TOKEN` | Deploying. Needs *Edit Cloudflare Workers* on the account |
108-
| `CLOUDFLARE_ACCOUNT_ID` | The account to deploy into |
50+
One number drives the build: the `cfml-worker` tag in `Cargo.toml`. The Worker compiles
51+
against it, `/try`'s browser engine is built from it, and `Application.cfc` quotes it, so
52+
the three cannot drift apart.
10953

110-
`GH_PAT` is optional, and only matters if the org stops `GITHUB_TOKEN` pushing to `main`.
111-
112-
## Layout
113-
114-
```
115-
Application.cfc project facts, nav, docs index, platforms, FAQ; one file to bump
116-
(`this.version` drives every download link and asset name;
117-
set it with tools/set-version.py, never by hand at release time)
118-
urlrewrite.xml /features -> features.cfm, plus the rule that hides the plumbing
119-
index.cfm home
120-
features.cfm capability tour, with code
121-
performance.cfm benchmarks and methodology
122-
docs.cfm an index of the repo's docs/ directory, plus recipes
123-
download.cfm per-platform binaries, WASM demo, build from source
124-
community.cfm how contributing works, and the projects this one stands on
125-
about.cfm background, aims, architecture, FAQ
126-
404.cfm rendered for a missing template, with a 404 status
127-
includes/_head.cfm <head>, theme bootstrap, meta
128-
includes/_header.cfm sticky nav, active state from request.section
129-
includes/_footer.cfm four-column footer, trademark note, closes the document
130-
lib/Icons.cfc the inline SVG icon set, so an icon is one call, not a paste
131-
assets/site.css the whole design system, both themes
132-
assets/site.js theme switch, mobile nav, copy buttons, reveals, star count
133-
assets/crab.png the mark: the project crab, trimmed and squared to 96px
134-
assets/crab-mark.svg the earlier line-drawn mark, kept but no longer referenced
135-
tools/ lint and smoke test (blocked from HTTP by urlrewrite.xml)
136-
assets/wasm/ the engine's WASM build, gitignored; 16 MB, copied from the
137-
engine repo's crates/wasm/pkg, powers /try
138-
assets/try-examples.js generated by tools/import-examples.py; the demo's own
139-
examples, ported verbatim rather than rewritten
140-
141-
Cargo.toml the Cloudflare Worker crate; pins the cfml-worker engine version
142-
build.rs embeds every .cfm/.cfc into the Worker at build time
143-
src/lib.rs Worker entry: urlrewrite.xml's rules, in Rust
144-
wrangler.toml bindings, custom domain, static assets, build command
145-
scripts/stage-assets.sh stages assets/ into public/ for Workers Static Assets
146-
scripts/esbuild-wrapper.sh, jspi-patch.mjs, package.json
147-
carried over verbatim from the reference worker host
148-
```
149-
150-
Pages set `request.section`, `request.title` and `request.desc` at the top, include
151-
`_head` and `_header`, write their markup, and include `_footer`. Nothing else is shared.
152-
153-
## Notes on writing for RustCFML
154-
155-
Two rules cost more time than everything else combined, because the engine does not behave
156-
like a JVM CFML server here:
157-
158-
1. **There is no implicit output.** `#variable#` in markup is *literal text*; it renders
159-
as source rather than interpolating. Every interpolation must be inside
160-
`<cfoutput>`/`</cfoutput>`.
161-
2. **Inside `<cfoutput>`, `#` always starts something.** `href="#pricing"` interpolates a
162-
variable called `pricing…`, and a code sample containing `#name#` quietly evaluates it.
163-
Literal hashes need `##`.
164-
165-
The house style this repo follows to stay safe: interpolate only with `#( … )#`, because the
166-
opening `#(` must always be closed by `)#`, otherwise everything up to the next `)#` in the
167-
file is swallowed and the parse error points somewhere else entirely. Code samples that
168-
contain hashes live outside output blocks; if one has to sit inside, its hashes are doubled.
169-
`tools/lint.py` enforces both rules, including the odd-hash-count case in tag attributes.
170-
171-
Two more things worth knowing, both measured rather than remembered:
172-
173-
* `onMissingTemplate` is not called for URLs that resolve to no file; the server answers
174-
those with its own 404 page. `404.cfm` is reached through `urlrewrite.xml`, and sets its
175-
own status so it is correct either way.
176-
* `<cfinclude>` inside `<cfoutput>` works and does not duplicate output, so includes can
177-
wrap themselves in their own output block. That is why every include here starts with one.
178-
179-
## Refreshing the project facts
180-
181-
The numbers, links and release version live at the top of `Application.cfc`. To refresh
182-
them from the repository:
183-
184-
```bash
185-
curl -s https://api.github.com/repos/RustCFML/RustCFML/releases/latest | jq -r .tag_name
186-
curl -s https://raw.githubusercontent.com/RustCFML/RustCFML/main/README.md | less
187-
```
54+
Deploying needs two repository secrets, `CLOUDFLARE_API_TOKEN` (from the *Edit Cloudflare
55+
Workers* template) and `CLOUDFLARE_ACCOUNT_ID`.
18856

189-
Bump `this.version`, and every download URL, changelog link and footer badge follows.
190-
Benchmark figures come from the project README. If they change there, change them here,
191-
and keep the methodology note that says where they came from.
192-
193-
## Design
194-
195-
Dark by default with a light theme on request, remembered in `localStorage` and applied
196-
before first paint by a three-line inline script so the page cannot flash. One accent
197-
colour, a rust-to-ember gradient, on a slate ground; Inter for prose, JetBrains Mono for
198-
everything that is code. Everything is legible and navigable without JavaScript, so the
199-
script only adds the theme button, the mobile menu, copy buttons and scroll reveals.
200-
201-
Three CSS habits keep it from breaking, all of them enforced by `layout.sh`:
202-
203-
* every grid track is `minmax(0, 1fr)`, never `1fr`, because an `auto` minimum lets one unwrapped
204-
URL widen the whole page past the width of the screen, and where the page cannot widen it
205-
starves the track next door instead. The hero was the one grid left on a bare `fr`: the
206-
install bar's `curl` URL is a single 869px token, so the text column claimed all 869 and
207-
the code window rendered at 159px. It read as a cramped sample; it was a starved track
208-
* every inline SVG gets a size in CSS, because an SVG with only a `viewBox` has no
209-
intrinsic size and will take the width of whatever it is dropped into
210-
* the nav collapses to a hamburger at 1060px, where six links and a button stop fitting,
211-
rather than at a rounder number that looks tidier in the stylesheet
212-
* a fixed minimum width is opt-in, never global. `table` carries no `min-width`; the wide
213-
benchmark tables ask for one with `.table-wrap.wide`, so a three-column table in a
214-
half-width track keeps its last column instead of scrolling it out of sight
215-
* code samples wrap (`pre-wrap` plus `break-word`) rather than scroll. A horizontal
216-
scrollbar inside a panel is invisible until you look for it, so a clipped line reads as
217-
broken rather than scrollable; wrapping cannot hide anything, and the copy buttons are
218-
unaffected because `innerText` reports required line breaks, never soft ones
219-
* a grid of terminals reflows on the width a shell line needs (`420px`), not the width that
220-
suits cards. Three-up inside a padded CTA left each one about 34 characters wide
57+
Two things that are less obvious than they look, if you touch the Worker:
22158

59+
- Only `.cfm` and `.cfc` are embedded in it. Everything under `assets/` is served by
60+
Workers Static Assets, because the engine runs *every* file it resolves through the VM —
61+
an embedded stylesheet would be executed rather than served.
62+
- `urlrewrite.xml` is read by the native server only. The Worker has its own copy of those
63+
rules in `src/lib.rs`. Change one, change the other.
22264

22365
## Licence
22466

225-
Same as the project it documents: MIT. The wording about ColdFusion being Adobe's
226-
trademark, and RustCFML not being affiliated with it, is kept in the footer of every page
227-
and again on the About page.
67+
MIT. ColdFusion is a registered trademark of Adobe Inc.; RustCFML is an independent
68+
project and is not affiliated with or endorsed by Adobe.

‎assets/site.js‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,22 @@
2727
syncThemeButton();
2828
}
2929

30+
/* Follow the OS while the visitor has not chosen for themselves: someone on
31+
an automatic day/night schedule sees the page turn with everything else.
32+
A stored choice is a decision, so it outranks the system. */
33+
if ( window.matchMedia ) {
34+
var scheme = window.matchMedia( "(prefers-color-scheme: light)" );
35+
var followSystem = function ( e ) {
36+
var chosen = null;
37+
try { chosen = localStorage.getItem( "rc-theme" ); } catch ( err ) {}
38+
if ( chosen ) return;
39+
root.setAttribute( "data-theme", e.matches ? "light" : "dark" );
40+
syncThemeButton();
41+
};
42+
if ( scheme.addEventListener ) scheme.addEventListener( "change", followSystem );
43+
else if ( scheme.addListener ) scheme.addListener( followSystem );
44+
}
45+
3046
/* ---- Mobile navigation ------------------------------------------------ */
3147
var menuBtn = document.getElementById( "menuBtn" );
3248
var nav = document.getElementById( "siteNav" );

‎includes/_head.cfm‎

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,17 +18,21 @@
1818
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;500;700&display=swap" rel="stylesheet">
1919
<link rel="stylesheet" href="/assets/site.css">
2020
<script>
21-
// Paint the stored theme before the first frame, or every page load flashes
22-
// the wrong colours. Inline here rather than in site.js, which is deferred and
21+
// Paint the theme before the first frame, or every page load flashes the
22+
// wrong colours. Inline here rather than in site.js, which is deferred and
2323
// would run too late.
24+
//
25+
// A stored choice wins; failing that the OS decides. The test is for light
26+
// rather than dark so that "no preference", an ancient browser and a blocked
27+
// localStorage all land on dark, which is this site's default.
2428
(function () {
2529
document.documentElement.className += " js";
26-
try {
27-
document.documentElement.setAttribute(
28-
"data-theme", localStorage.getItem( "rc-theme" ) || "dark" );
29-
} catch ( e ) {
30-
document.documentElement.setAttribute( "data-theme", "dark" );
31-
}
30+
var stored = null;
31+
try { stored = localStorage.getItem( "rc-theme" ); } catch ( e ) {}
32+
var light = window.matchMedia
33+
&& window.matchMedia( "(prefers-color-scheme: light)" ).matches;
34+
document.documentElement.setAttribute(
35+
"data-theme", stored || ( light ? "light" : "dark" ) );
3236
})();
3337
</script>
3438
</head>

0 commit comments

Comments
 (0)