# kjol `kjol` ("keel" in Norwegian) is a **shared, multilingual base layer** factored out of the user's applications so they stay in sync. It is consumed as a **git submodule** inside each app (at `/kjol`). Current consumers: `cdrateline.com_2.0` and `Hotlap` — near-identical forks it was extracted from. Scope will grow to more projects and languages. ## Golden rules 1. **The framework NEVER imports application code.** Wherever kjol needs app-specific behavior, the app injects it (interfaces, registration functions, config structs, callbacks). See **Coupling inversions** below. If you find yourself wanting to `import "/internal/..."` from kjol, invert it instead. 2. **Never run git operations here on the user's behalf.** The user creates the submodule and makes all commits. You may edit files, build, and test. 3. kjol is edited **in place** via the submodule + `go.work` — there is no publish / `go get` 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 Each top-level directory is one language / build root: ``` kjol/ go/ all Go. Module `kjol` (go.mod lives in go/). Imports are `kjol/`. web/ all JS/TS (browser + SSR). No build system of its own; built by go/webbundler. # future: cpp/ 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. ### go/ — module `kjol` Packages, imported as `kjol/`: `appenv basic chrono config csv dbutil finance httputil l4g security snailmail validation webbundler 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). `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. Build / test (run from repo root): ``` go -C go build ./... go -C go vet ./... go -C go test ./... ``` **The wasm host API** (`wasmruntime/host.go` + `host_wasm.go` / `host_native.go`) is how neutral component code reaches the browser: element measurement (`Measure`, `Viewport`), imperative style writes (`SetStyle` — positioning must NOT go through signals, which re-render the whole tree), the post-commit hook (`AfterRender`, the only point at which a just-rendered element can be measured), document/window listeners, timers, localStorage, and file download. It is **dual-build**: real under `js && wasm`, no-op stubs natively — which is what lets `webui` call it unconditionally and still SSR. Refs come from `vdom.Ref` + `vdom.WithRef`; `vdom.Portal` mounts children at `document.body` (needed to escape `overflow:hidden` / `transform` ancestors). The reconciler is wasm-only and needs a DOM, so `go test ./...` cannot reach it. It has its own harness — a minimal DOM under node: ``` GOOS=js GOARCH=wasm go -C go test -exec="node testdata/domexec.js" ./wasmruntime ``` `webui` components that measure the page (Tooltip, Popover, Menu, Modal, DatePicker, Tutorial, AutoTable) are **controllers**: create them once alongside your signals, never inside a render closure. Floating panels share one positioning engine (`webui/position.go`, pure math, unit-tested natively) driven by the `Floating` controller (`webui/floating.go`). ### web/ - `kit/` — 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. - `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. - `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`). ## Consumption (per app) - `go.work` at the app root: ``` use ( . ./kjol/go ) ``` then vendor with `go work vendor` (not `go mod vendor`). - Startup wiring the app performs (this is how the inversions get their app-side halves): `dbutil.RegisterAll(models.Tables)`, `l4g.SetDatabaseWriter(...)`, `dbutil.Init(dbutil.ConnConfig{...})`, `snailmail.Configure(snailmail.Settings{...})`, a thin `config.Load` wrapper over `config.Load[T]`, and an app-side `internal/httpauth` for the session/authn middleware. ## Coupling inversions (how the framework stays app-agnostic) | Package | Inversion | |---|---| | `dbutil` | table names via `Register`/`RegisterAll` (not the app's `models.Tables`) | | `l4g` | owns the `Entry` type; DB persistence via `SetDatabaseWriter(func(Entry) error)` | | `config` | generic `Load[T](file, *T) error`; each app defines its own config struct | | `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 | ## Stays app-side (never moves into kjol) Domain models/repository/handlers-api, migrations, pages/routes/layouts, brand UI (`TopBar`/`AppSidebar`/`TransitionOverlay`), concrete permission constants, the app config struct, `embed.go` + `wwwroot/`, and generated artifacts (`faIcons` registry, `routes.gen.ts`, `public_pages.gen.go`). ## Provenance Big-bang extraction from cdrateline + Hotlap. The detailed migration plan lives on the author's machine at `~/.claude/plans/foamy-humming-tulip.md`.