kjol-website — the kjol website
The landing page and documentation for the whole codebase. It is also the thing it documents: every page runs the code it describes, and there is not a screenshot of a component anywhere on the site.
It is one server running two entirely different front-ends, and that is the point of it. kjol has two web layers — one written in Go and compiled to WebAssembly, one written in Solid and bundled by a Go toolchain — and the only honest way to document both is to build the site out of both.
Run it
cd go/cmd/kjol-website
go run ./server -build # cold build, then exit
go run ./server # build, then SSR + /rsc + hot reload at http://localhost:8085
go run ./server performs that same build on every save and hot-swaps the result into
the browser, so day to day it is the only command you need. A .go save rebuilds the
wasm and swaps it in without a reload or a flash, preserving page state; a .css save
recompiles only Tailwind; a compile error lands in a browser overlay rather than in a
terminal you were not looking at.
The shape of the site
| Path | Rendered by | What it is |
|---|---|---|
/, /about |
Go → WebAssembly, SSR'd | The landing page. The Layers menu is the site's primary navigation. |
/wasm/* |
Go → WebAssembly | Kjol Wasm Web — the gowasm engine: SSR + hydration, server components, data fetching, and the whole webui kit on one page (/wasm/components). |
/js/* |
Solid → esbuild, client-rendered | Kjol JS Web — the Solid kit, all of it on one page (/js/components), with a sidebar that jumps to each group. |
/js/ssr |
Solid → goja, at request time | A public page server-rendered with live data injected — the ISR path. |
Crossing between /wasm and /js is a real page load. They are different binaries, and
pretending otherwise would mean shipping both to every visitor.
Layout
app/ Kjol Wasm Web — pages as plain Go functions returning a *VNode
pages.go Deps + Shell + the public/app layouts + the landing page
layers.go the Layers, as data. MIRRORED in frontend/src/layers.ts.
docs.go the docs chrome; docsNav() is the sidebar AND the index
components.go THE WHOLE KIT, on one page. componentGroups() is the single source
for both the sections and the sidebar that jumps to them.
table.go the AutoTable's data + controller (shared with its tests)
chart.go data.go server_counter.go
*.gen.go GENERATED by kjol/cmd/wasmgen (routes, layout dispatch, RSC stubs)
wasm/ the js/wasm client entry (main_native.go is a host stub)
css/app.css its Tailwind entry — kjol/tw scans the .go files for class names
frontend/ Kjol JS Web — .tsx pages written against @ui/*
css/style.css brand ONLY. kjol's theme.css is prepended by the bundler, and it
is the one that does `@import "tailwindcss"`.
vendor/ pdf-lib + pdfjs-dist. @ui/AutoTable imports them at the TOP LEVEL,
so a bundle without them does not degrade — it fails to evaluate.
src/app.ts the SPA entry. It is .ts, not .tsx, because the bundler resolves
the entry as src/app.ts and nothing else — so it can hold no JSX.
src/layers.ts the Layers again. Keep in step with app/layers.go.
server/ ONE Go server: serves wwwroot, SSRs the wasm routes, hosts /rsc,
mounts the Solid SPA at /js/*, serves the SSR'd public pages, and
carries the -build flag.
build/ the build pipeline, in Go rather than a shell script, so the cold build
and the watch loop call the SAME functions and cannot drift. It is a
library, not a command: the server imports it, and Go will not let you
import a main — hence `go run ./server -build` rather than `./build`.
internal/handlers/ the app side of the public-page inversion: kjol generates the
registry; this owns the type and the document shell.
wwwroot/ both layers write here. They never collide: app.css / app.wasm for one,
bundle.min.* for the other. One static dir, one server.
Theming
One theme, both layers. The kits are themed by semantic tokens — components say
bg-surface, text-ink, border-line and never name a colour — so dark mode
re-points about a dozen CSS variables and not one component knows it happened.
The choice is stored under a single kjol-theme key that both front-ends read, so
switching to dark in /wasm and walking over to /js keeps it dark. A ten-line boot
script in the document head applies the class before first paint; without it every
dark-mode reader would get a white page until the bundle landed, and then have it
snatched away.
The only places a dark: variant survives are the two a re-pointed token cannot fix: a
coloured tint (a red-50 wash is invisible on a near-black surface) and a fill that has
to invert (the neutral button, whose label must go dark when the fill goes pale).
Adding things
A Kjol Wasm Web page: write the function, mark it //gowasm:page /wasm/thing layout=app,
build. wasmgen regenerates the routing. Add static to have it server-rendered.
A component: add a section to app/components.go (Go) or frontend/src/pages/Components.tsx
(Solid), and one entry to componentGroups() / COMPONENT_GROUPS. That single list drives the
sections, the sidebar that jumps to them, and the index — so none of the three can drift.
A layer: one entry in app/layers.go and one in frontend/src/layers.ts. They are
two files because nothing is upstream of both a WebAssembly binary and an esbuild bundle;
keeping each to a flat list of plain data is what makes that duplication survivable.
How it maps onto the engine
| Package | Role | This app's use |
|---|---|---|
kjol/vdom |
neutral virtual DOM (native + wasm): VNode, Signal, RenderHTML |
pages build *VNode; the server SSRs with vdom.RenderHTML |
kjol/wasmruntime |
wasm client runtime: reconcile, Run/Hydrate, router, fetch |
wasm/main.go calls Hydrate/Run |
kjol/rsc |
stateless server components over HTTP | //gowasm:server + its generated client stub |
kjol/wasmdevserver |
reusable dev server: SSR, /rsc, hot reload, error overlay |
server/main.go fills a wasmdevserver.Config |
kjol/webui |
the Go component kit | every /wasm/* page |
kjol/jsbundler |
TSX → Solid → esbuild, the Tailwind driver, the SSR bake | build.JS, and the ISR render at request time |
kjol/jsruntime |
the Solid kit, the vendored runtime, the icons, theme.css |
everything under /js/* |
kjol/tw |
the Tailwind v4 engine, in Go | both stylesheets — it scans .go for one and .tsx for the other |
The golden rule holds throughout: no kjol package imports application code. The app
injects Build, Render and Document into wasmdevserver; it owns the publicPage
type that kjol's generated registry is written against. The dependency only ever points
one way.
Its own module
go.mod declares module kjolwebsite with replace kjol => ../.., so its dependencies —
go-chart for the server-drawn charts, plus esbuild and goja by way of the bundler — stay
out of kjol, whose engine packages are stdlib-only. go build ./... at the kjol root
does not descend into this nested module; build it from here.