create-caspian-app 1.3.4 → 1.3.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -90,7 +90,8 @@ This is the top architectural requirement for this workspace. Treat it as a hard
|
|
|
90
90
|
- Treat `pp-component` on routes, layouts, and components as compiler-injected by the Python side; do not add it manually in authored templates unless the task is explicitly about runtime internals. Author owned PulsePoint logic as a plain `<script>` inside the component root.
|
|
91
91
|
- `layout()` can be synchronous or async in the installed runtime. Keep async layout work focused on shared layout props or metadata; use `page()` or `@rpc()` when the work belongs to a specific route or user action.
|
|
92
92
|
- Dynamic route params currently reach `page()` as a single positional `dict`, with query params injected by name and `request` injected by keyword when declared.
|
|
93
|
-
- In `layout.py`, `layout()` returns the
|
|
93
|
+
- In `layout.py`, `layout()` returns `html(r"""...""", **context)` — the same entrypoint pages and components use. It is *deferred*: `children` is the page beneath the layout and does not exist yet, so `html(...)` hands back an unrendered `LayoutTemplate` and the engine renders it later with `children`/`layout`/`metadata` merged in (those three always win over author context). Also accepted: `(html(...), props_dict)`, a props dict alone (passthrough `<slot />` shell), `None`, or a bare template string (the legacy form, still supported). A layout that never places its children — no `<slot />` and no `{{ children }}` — raises `LayoutChildrenError` rather than serving an empty shell.
|
|
94
|
+
- Never author markup as a Python f-string. `{{ x }}` is server interpolation and `{ x }` is a PulsePoint binding inside `html(...)`; an f-string inverts both, skips autoescaping while still marking the result trusted, and skips the `<x-*>` scope stash. The `templates` gate freezes the existing f-string components in `settings/fstring-components.json` and fails on new ones.
|
|
94
95
|
- Do not assume `StateManager` survives across requests unless `request.state.session` is explicitly bridged from `request.session`.
|
|
95
96
|
- Route, layout, and component templates must keep exactly one authored top-level parent node so Caspian can inject `pp-component` after component expansion. In source, that parent may be a native HTML element or a single imported `x-*` component tag, but it must resolve to one final HTML root. Keep any owned PulsePoint script inside that same parent.
|
|
96
97
|
|
package/dist/AGENTS.md
CHANGED
|
@@ -76,7 +76,8 @@ Use `.github/copilot-instructions.md` for the repo-wide implementation rules. Th
|
|
|
76
76
|
- Component-first page composition is the highest-priority authoring rule for this workspace (see `.github/copilot-instructions.md`). Build pages as a short assembly of `x-*` chunk components (top menu, sidebar, header, content sections, cards, forms, footer) and keep each chunk's long markup inside its own focused single-file `html(...)` component, so the page template in `src/app/**/index.py` stays small instead of holding a wall of HTML. Plan the chunk breakdown before writing the route, not as a later cleanup pass.
|
|
77
77
|
- **PulsePoint is not React and its templates are not JSX.** This workspace's guidance compares PulsePoint to React in exactly two places — the `pp.*` hook API inside `<script>`, and how components are split by responsibility — and that comparison stops at the markup. Template files are plain HTML. Never generate `{cond && (<div/>)}`, `{cond ? <A/> : <B/>}`, `{list.map(item => (<tr/>))}`, `className`, `htmlFor`, camelCase `onClick`, `style={{...}}`, `dangerouslySetInnerHTML`, or `<>…</>`. Use `hidden="{!cond}"` for conditionals, `<template pp-for="item in list">` with `key="{item.id}"` for lists, and **always quote brace attributes** — `class="{...}"`, never `class={...}`. The unquoted form is invalid HTML: the parser splits the value on spaces into junk attributes, the component root never compiles, and the route serves a blank page with no console error (the body's `opacity: 0` reveal never fires). There is no `pp-if`, `pp-show`, `pp-else`, or `pp-key`. Sanity check before finishing any template: it must still be valid HTML with every `{}` deleted. See `node_modules/caspian-utils/dist/docs/pulsepoint.md` sections "PulsePoint Is Not JSX", "Complete Directive And API Surface", and "Conditional rendering".
|
|
78
78
|
- Split single-file Python components by responsibility, using the same mental model as React components — **for decomposition and single-root shape only, never for syntax** (see the rule above). A page with tabs should usually have one component for the tab shell and separate components for each substantial tab panel. A section with its own form, table, toolbar, or list should usually be its own component with data and options passed by props, not an unrelated block inside a giant Python file.
|
|
79
|
-
- Authoring is Python-only
|
|
79
|
+
- **Authoring is Python-only, single-file, and `html(...)` is the one markup entrypoint — pages, layouts and components alike.** A route is one `index.py` whose `page()` returns `html(r"""...""", **context)`; a layout is one `layout.py` whose `layout()` returns `html(r"""...""", **context)` (optionally as `(html(...), props_dict)`); a component is one `.py` returning `html(...)`. Inside `html(...)`, `{{ ... }}` is server-side Jinja and `{ ... }` stays for PulsePoint. **Never author markup as a Python f-string**: the brace dialects invert (`{x}` becomes server interpolation and a PulsePoint binding must be written `{{x}}`), the string is not autoescaped yet `Component.acall` still marks it trusted, and the `<x-*>` scope stash is skipped so a directly-called component cannot resolve nested tags. The `templates` gate enforces this — existing f-string components are frozen in `settings/fstring-components.json` and any NEW one fails `npm run check`; converting one means deleting its baseline line. Prefer `r"""..."""` when the markup's `<script>` contains backslashes. See `node_modules/caspian-utils/dist/docs/components.md` and `file-conventions.md`.
|
|
80
|
+
- **A layout's `html(...)` is deferred, not rendered at call time**, because `children` is the page beneath it and does not exist yet. `html(...)` called from a `layout()` returns a `LayoutTemplate` — the unrendered source plus the author's context — and the layout engine renders it once with `children`/`layout`/`metadata` merged in; those three engine-owned names always win over author context. Deferral is keyed on the `layout()` frame specifically, so a component the layout calls, or a helper in the same file, still renders eagerly. The legacy shapes (a bare template string, `(str, props)`, a props `dict`, `None`) all still work — `LayoutTemplate`'s string value _is_ the raw source, so both forms take the same engine path. A layout that places its children nowhere (no `<slot />`, no `{{ children }}`) now raises `LayoutChildrenError` instead of serving an empty shell.
|
|
80
81
|
- In a prop-receiving single-file Python component, `x-*` attributes arrive as raw string kwargs (including unevaluated strings such as `"{permOpen}"`) and do not become browser `pp.props` automatically. Forward every browser-facing prop onto the single native root with `get_attributes({...}, props)`, render `<root {{ attributes }}>`, and pass `attributes=attributes` into `html(...)`. Props accepted by Python but not re-emitted are silently absent from `pp.props`; no server error or browser warning is raised. Remember that forwarded names are real DOM attributes, so avoid unintended native collisions such as `title` when a component-specific name like `user-name` is appropriate. A named Python parameter is consumed out of `**props`, so it is no longer in the passthrough dict and must be listed explicitly in the `get_attributes` defaults. Forwarding also does not preserve types: a brace expression (`volume="{vol}"`) is evaluated in parent scope and keeps its real type, but a literal server value renders as a string, so `volume="0"` makes `volume === 0` false; a valueless attribute becomes `true`; `None`/`False`/`""` are omitted entirely so the prop reads `undefined` rather than `false`; and JS reserved words such as `class` are dropped from `pp.props`. When an icon toggle, `hidden`, or class binding silently does nothing, verify the prop is on the rendered root before debugging the expression. See `node_modules/caspian-utils/dist/docs/components.md` "Receiving Props In A Python Component" and "Every Prop A Template Reads Must Be Forwarded To The Root."
|
|
81
82
|
- Composition is Python-import-driven everywhere: a module's `x-*` tags (page, layout, or component) resolve from the Component objects imported into that module; the Python import is the only import mechanism, enforced by the compiler and the `templates` gate rule `import-comment`. Runtime resolution precedence inside a component's output is inherited ancestor components, then the module's own Python imports. Slot content (children) resolves in the scope where it was authored, so the module that writes an `x-*` tag in markup must import that component. For directories whose names are not valid Python identifiers (hyphens, `(group)`), bind the component with `Name = importlib.import_module("src.app.some-dir.Name").Name`.
|
|
82
83
|
- For first-party HTML interactivity in this workspace, PulsePoint is the required default. Use PulsePoint `on*` event attributes, `pp.state`, refs, effects, directives, and `pp.rpc()` instead of inventing id/data-attribute driven JavaScript with `querySelector`, `getElementById`, `addEventListener`, manual `innerHTML`, or parallel client state. For simple forms, bind `onsubmit` in the HTML, convert named fields with `Object.fromEntries(new FormData(event.currentTarget).entries())`, and validate/normalize that payload in Python; do not add `pp-ref` to each input, create a form ref, and attach an effect-managed submit listener just to collect submitted values.
|