docs(agents): rendering-ownership rule — templ renders, JS decides what shows
Codifies the standing rule discussed for the templ/JS split: content derived from server data is rendered by templ and refreshed via templ partials (htmx.ajax), pure client-local state may render in TS/Alpine, web/src is TypeScript-only, and inline <script> in templ files requires a pre-paint justification (theme_flash_guard.templ only). Lists the sanctioned JS islands (reader, Chart.js, toasts, password validation). Also refreshes gotcha #1: the 'harmlessly masked' aside is stale — the quoted-attribute sweep fixed every occurrence; the note now points at templates.JSONArg for free text.
This commit is contained in:
1 parent
b89eac22e3
commit
1fdf306f2d
1 file changed
+33
-3
@@ -4,6 +4,34 @@ Standing rules for any agent or developer working in this repo.
|
||||
Project-wide context lives in the sibling repos' AGENTS.md files
|
||||
(../bookhoard-app/AGENTS.md is the Android client's).
|
||||
|
||||
## Rendering ownership (templ vs TypeScript) — ALWAYS apply
|
||||
|
||||
The point of templ + htmx + Alpine is: the server renders the UI, and
|
||||
JavaScript is kept minimal. The rule that keeps it that way:
|
||||
|
||||
> **JavaScript decides what shows. templ decides what it looks like.**
|
||||
|
||||
- Content derived from **server data** (DB rows, search results, match
|
||||
lists, conflict cards, any list/card/badge/table) is rendered by
|
||||
templ. When it changes client-side, fetch a **templ partial** from an
|
||||
SSR route and swap it in (`htmx.ajax(...)`), never rebuild it from a
|
||||
JSON payload with `innerHTML` string templates.
|
||||
- **Pure functions of client-local state** (open/closed panels, tabs,
|
||||
password strength from a typed value, toasts) may render in
|
||||
TS/Alpine — no server round trip needed.
|
||||
- `web/src` is **TypeScript only** — no new `.js` source files.
|
||||
Vendored third-party and vite build output under `web/static` are
|
||||
exempt.
|
||||
- Inline `<script>` in a `.templ` file needs a reason a bundled module
|
||||
cannot do the job (pre-paint execution). Currently sanctioned:
|
||||
`theme_flash_guard.templ` only.
|
||||
|
||||
Sanctioned JS islands (engines, not UI renderers — exempt from the
|
||||
above): the reader (foliate-js + `web/src/reader/`), Chart.js graphs in
|
||||
stats, toasts, password validation. Anything else that renders app UI
|
||||
in TypeScript is a defect; if a templ twin of it exists, one of the two
|
||||
must be deleted.
|
||||
|
||||
## Library visibility (family-lens rule) — ALWAYS apply
|
||||
|
||||
Per-user library visibility (`library_visibility` table, default-ALLOW:
|
||||
@@ -96,9 +124,11 @@ of a server bug: stop and flag the user.
|
||||
values** — it emits the literal text. `x-init="f('{ col.ID }')"`
|
||||
shipped a request to `/api/collections/{ collection.ID }` found by
|
||||
the request logs; the whole attribute value must BE an expression:
|
||||
`x-init={ "f('" + col.ID + "')" }`. Note
|
||||
`class="theme-{ user.Theme }"` has always been literal (harmlessly
|
||||
masked by the JS theme engine).
|
||||
`x-init={ "f('" + col.ID + "')" }`. (Every occurrence of this bug —
|
||||
including `class="theme-{ user.Theme }"` on 22 page bodies and 26
|
||||
attribute interpolations — was swept and fixed in 2026-10; when
|
||||
adding attributes with dynamic values, the whole value is an
|
||||
expression, and free text goes through `templates.JSONArg`.)
|
||||
2. **SettingsRegistry caches**: writes must go through the settings
|
||||
API (Reload on save). Direct SQL writes to system_settings leave
|
||||
the cache stale until the next API-path write — never "preview"
|
||||
|
||||
Reference in new issue
Block a user