Add js web stuff to landing page + documentation
This commit is contained in:
110
CLAUDE.md
110
CLAUDE.md
@@ -17,35 +17,49 @@ forks it was extracted from. Scope will grow to more projects and languages.
|
||||
step. Editing a kjol file takes effect in the consuming app immediately.
|
||||
4. When a file exists in both apps and has drifted, reconcile by **merging best-of-both**.
|
||||
|
||||
## Organization — by language
|
||||
## Organization — by build root
|
||||
|
||||
Each top-level directory is one language / build root:
|
||||
|
||||
```
|
||||
kjol/
|
||||
go/ all Go. Module `kjol` (go.mod lives in go/). Imports are `kjol/<pkg>`.
|
||||
web/ all JS/TS (browser + SSR). No build system of its own; built by go/webbundler.
|
||||
# future: cpp/ kotlin/ swift/
|
||||
go/ the Go module `kjol` (go.mod lives here). Imports are `kjol/<pkg>`.
|
||||
ALSO holds jsruntime/ — all the JS/TS. See below.
|
||||
c/ C base layer (arena, strings, math, lexer, platform).
|
||||
jai/ Jai modules. Early.
|
||||
# future: kotlin/ swift/
|
||||
```
|
||||
|
||||
Language-first, **not** feature-first. Consequence: the **web bundler is Go** and lives in
|
||||
`go/webbundler` even though it builds `web/`. Don't "fix" this by splitting it.
|
||||
**The JS tree lives inside `go/`, at `go/jsruntime`.** It has no Go in it beyond a doc
|
||||
file — it is the Solid kit, the vendored Solid runtime, the FontAwesome SVGs and the
|
||||
Tailwind `@theme` scaffold. It sits there because the thing that BUILDS it is Go
|
||||
(`go/jsbundler`), the thing that styles it is Go (`go/tw`), and a sibling `web/` at the
|
||||
repo root was one more directory the build had to go hunting for. `go build ./...`
|
||||
ignores it; nothing imports it as a package.
|
||||
|
||||
Consequence, and don't "fix" it: the **web bundler is Go**. `tw` is the **Tailwind v4
|
||||
compiler** and is deliberately NOT inside the bundler — Tailwind only reads text and
|
||||
writes CSS, and the text is just as likely to be Go (the gowasm kit writes its markup in
|
||||
Go and has no JS build at all). Keeping it in the bundler made every Go-only consumer
|
||||
drag a JavaScript bundler along for a CSS file.
|
||||
|
||||
### go/ — module `kjol`
|
||||
|
||||
Packages, imported as `kjol/<name>`: `appenv basic chrono config csv dbutil finance httputil
|
||||
l4g security snailmail validation webbundler tw`, plus the **gowasm** web-UI engine (`vdom`
|
||||
l4g security snailmail validation jsbundler tw`, plus the **gowasm** web-UI engine (`vdom`
|
||||
`wasmruntime` `rsc` `wasmdevserver`, and `webui` — a Tailwind-styled component kit ported
|
||||
from `web/kit`; author components in pure Go compiled to WebAssembly; all stdlib-only), and
|
||||
`cmd/{bundle,twcss,migrate,loc,passgen,typecheck,wasmgen}`. A runnable
|
||||
example lives in `cmd/examples/go-wasm-web` (its own nested module so its go-chart dep stays
|
||||
out of kjol).
|
||||
from `jsruntime/uikit`; author components in pure Go compiled to WebAssembly; all
|
||||
stdlib-only), and `cmd/{bundle,twcss,migrate,loc,passgen,typecheck,wasmgen}`.
|
||||
|
||||
`webbundler` is the **JS** build (TSX → Solid → esbuild). `tw` is the **Tailwind v4
|
||||
compiler**, and it is deliberately NOT inside it: Tailwind only reads text and writes CSS,
|
||||
and the text is just as likely to be Go — the gowasm kit writes its markup in Go and has no
|
||||
JS build at all. Keeping it in the bundler made every Go-only consumer drag a JavaScript
|
||||
bundler along for a CSS file.
|
||||
`jsbundler` is the **JS** build (TSX → Solid → esbuild + the goja SSR bake). It was called
|
||||
`webbundler`.
|
||||
|
||||
**`cmd/kjol-web` is the website**: the landing page and documentation for the whole
|
||||
codebase, and the runnable example of both web layers. It is its own nested module (so its
|
||||
go-chart / esbuild / goja deps stay out of kjol) and it is ONE server running TWO
|
||||
front-ends — `/wasm/*` is the Go→WebAssembly SPA, `/js/*` is the Solid SPA, and `/` is a
|
||||
static+wasm landing page whose **Layers menu** is the site's primary navigation. Its
|
||||
README is the map. Anything user-visible you add to kjol should show up there, running.
|
||||
|
||||
Build / test (run from repo root):
|
||||
```
|
||||
@@ -75,34 +89,56 @@ signals, never inside a render closure. Floating panels share one positioning en
|
||||
(`webui/position.go`, pure math, unit-tested natively) driven by the `Floating` controller
|
||||
(`webui/floating.go`).
|
||||
|
||||
**Theming / dark mode.** The kit is themed by **semantic tokens**, not by a `dark:` variant on
|
||||
every class: components say `bg-surface` / `border-line` / `text-ink` / `text-accent` and never
|
||||
name a colour, so a theme is ten CSS variables rather than four hundred class strings. The app
|
||||
must define them (see `webui.ThemeTokens` for the required set, and the example's `css/app.css`
|
||||
for a working pair) plus `@custom-variant dark (&:where(.dark, .dark *));` — the built-in `dark`
|
||||
variant is a `prefers-color-scheme` media query, which a site with its own switch cannot use.
|
||||
Only genuinely *coloured* things (an alert's red tint) carry `dark:` variants. `webui.Theme` is
|
||||
the controller (`Toggle`, `ThemeToggle`, `Init`); `webui.ThemeBootScript` goes in the document
|
||||
head **before** the stylesheet, or dark-mode users get a white flash until the wasm loads. It is
|
||||
the only JavaScript in a gowasm app.
|
||||
**Theming / dark mode — BOTH kits, one vocabulary.** Neither kit names a colour: components say
|
||||
`bg-surface` / `border-line` / `text-ink` / `text-accent`, and a `.dark` class on `<html>`
|
||||
re-points what those mean. A theme is a dozen CSS variables rather than four hundred class
|
||||
strings, and `dark:` on every component is exactly the thing to avoid. The Go and Solid kits use
|
||||
the **same token names on purpose** — change `surface` once and both halves of a site move.
|
||||
|
||||
### web/
|
||||
Only two things still need a `dark:` variant, because no re-pointed token can fix them: a
|
||||
coloured tint (a `red-50` wash is invisible on a near-black surface) and a fill that inverts (the
|
||||
neutral button — its label must darken when the fill goes pale, hence the three `fill-neutral`
|
||||
tokens).
|
||||
|
||||
- `kit/` — Solid.js `.tsx` component kit. Apps import components as `@ui/*`.
|
||||
`@custom-variant dark (&:where(.dark, .dark *));` is required — the built-in `dark` variant is a
|
||||
`prefers-color-scheme` media query, which a site with its own switch cannot use (the OS says one
|
||||
thing, the switch says another, and the media query wins). For gowasm the app defines the tokens
|
||||
(see `webui.ThemeTokens`); for the Solid kit `jsruntime/styles/theme.css` defines them and the
|
||||
bundler prepends it, so the app's `style.css` carries brand only.
|
||||
|
||||
Controllers: `webui.Theme` (Go) and `jsruntime/uikit/Theme.tsx` (Solid) — both read the same
|
||||
`kjol-theme` localStorage key, so a preference survives crossing between two front-ends.
|
||||
`webui.ThemeBootScript` goes in the document head **before** the stylesheet, or dark-mode users
|
||||
get a white flash until the bundle loads. It is the only hand-written JavaScript in a gowasm app.
|
||||
|
||||
### go/jsruntime — the JS/TS tree
|
||||
|
||||
- `uikit/` — Solid.js `.tsx` component kit. Apps import components as `@ui/*`.
|
||||
- `runtime/` — vendored Solid runtime + `vendor.json` (base entrypoints). The app merges its
|
||||
own `vendor.json` (chart.js, pdf-lib, ...) on top; **kjol's solid-js must resolve first** so
|
||||
there is a single reactive instance.
|
||||
own `vendor.json` on top; **kjol's solid-js must resolve first** so there is a single reactive
|
||||
instance (a split one does not error — it silently stops flushing effects).
|
||||
⚠ `uikit/AutoTable.tsx` imports `pdf-lib` and `pdfjs-dist` at the TOP LEVEL, so any app using
|
||||
AutoTable must vendor them or the bundle fails to evaluate at all.
|
||||
- `icons/` — FontAwesome SVG source kit (the bundler scans usage and generates a per-app
|
||||
registry; the generated file is app-owned, not committed here).
|
||||
- `styles/` — `theme.css` (`@theme` scaffold + `:root` fa vars). Brand color/font tokens stay
|
||||
app-side; the app's `style.css` `@import`s this.
|
||||
registry; the generated file is app-owned, not committed here). kjol ships only the SUBSET its
|
||||
own kit + `kjol-web` reference. An app's own `frontend/icons` is searched FIRST, so an app with
|
||||
a fuller kit keeps it — see `jsbundler.iconsDirs`.
|
||||
- `styles/theme.css` — the `@theme` scaffold, the semantic tokens, the `.dark` overrides, and the
|
||||
`:root` fa vars. **It does the `@import "tailwindcss"`**, because the bundler PREPENDS it to the
|
||||
app's `style.css` and an `@import` has to come first. An app adopting the shared tree therefore
|
||||
drops that import from its own stylesheet and keeps only brand.
|
||||
- `auth/ utils/ hooks/ ssr/ env.ts basic.ts finance.ts superfun.ts types.d.ts` — generic TS
|
||||
scaffolding. Apps import as `@kjol/*`. (Concrete permission constants stay app-side.)
|
||||
|
||||
**Frontend import aliases** (resolved by the bundler and mirrored in each app's tsconfig
|
||||
`paths`): `@ui/*` → `web/uikit`, `@kjol/*` → `web/`, `@appgen/*` → the app's generated dir
|
||||
(e.g. the FA `faIcons` registry — app-owned, gitignored, regenerated each build). The kit's
|
||||
own imports of sibling components stay relative (`./Buttons.tsx`).
|
||||
`paths`): `@ui/*` → `go/jsruntime/uikit`, `@kjol/*` → `go/jsruntime/`, `@appgen/*` → the app's
|
||||
generated dir (e.g. the FA `faIcons` registry — app-owned, gitignored, regenerated each build).
|
||||
The kit's own imports of sibling components stay relative (`./Buttons.tsx`).
|
||||
|
||||
**Solid gotchas** (they bite every time): DOM handlers keep their DOM names — `onclick`,
|
||||
`oninput`, `onchange`, *not* `onClick`. Everything is a named export. And Tailwind finds classes
|
||||
by **scanning source for literal strings**, so `"bg-" + name` compiles to nothing — write the
|
||||
class out in full.
|
||||
|
||||
## Consumption (per app)
|
||||
|
||||
@@ -130,6 +166,8 @@ own imports of sibling components stay relative (`./Buttons.tsx`).
|
||||
| `dbutil.ConnConfig`, `snailmail.Settings` | DB / mail credentials injected, never read from app config |
|
||||
| `appenv` | compile-time environment via build tags (`-tags staging` / `-tags production`); the bundler reads `appenv.Environment` for the JS `__ENV_TYPE__` define |
|
||||
| `httputil.CorsMiddleware(CorsConfig{...})` | allowed domains + bundle-version source injected |
|
||||
| `wasmdevserver` | the app injects `Build` / `Render` / `Document` / `Handle` via `Config` |
|
||||
| `jsbundler` public pages | kjol generates the registry (`public_pages.gen.go`); the APP owns the `publicPage` type it is written against, and the document shell. See `cmd/kjol-web/internal/handlers`. |
|
||||
|
||||
## Stays app-side (never moves into kjol)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user