# 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 ```sh 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.