create-caspian-app 1.3.2 → 1.3.5

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.
@@ -77,7 +77,7 @@ This is the top architectural requirement for this workspace. Treat it as a hard
77
77
  - When `caspian.config.json` has `websocket: true`, WebSocket endpoint paths are project-defined in `main.py`; do not assume any default socket path or route folder exists in every Caspian project. Keep shared socket helpers under `src/lib/websocket/**` when session extraction, auth payload validation, connection tracking, or broadcast behavior is reused.
78
78
  - For route creation, every route is one `src/app/**/index.py`: `page()` returns the page markup via `html(...)`, and the same module owns metadata, `@rpc()` actions, auth checks, caching, and redirects. Shared section wrappers live in `layout.py`, whose `layout()` returns the wrapper template (optionally with a props dict). Non-visual routes (redirect-only or action-only) are `index.py` files whose `page()` returns a `Response`.
79
79
  - Keep route-specific logic in that route's `index.py`. Move code into `src/lib/**` only when it is genuinely reusable across routes, components, integrations, or features; do not extract one-route orchestration just to make it look generic.
80
- - Treat the single-root template contract as a hard requirement, not a style preference: every authored route, layout, and component HTML file must have exactly one parent HTML element or one imported `x-*` component tag as its root. Do not leave sibling top-level markup, and do not place a `<script>` after the root element. If a script is needed, keep it inside that same root.
80
+ - Write every authored route, layout, and component template with one parent HTML element or one imported `x-*` component tag as its root, and keep any `<script>` inside that root rather than after it. A component **may** instead have sibling top-level nodes: that is a fragment (the `<>…</>` shape), the compiler frames it with a `<!--pp:id-->…<!--/pp-->` comment pair, and the browser materializes it into `<pp-fragment style="display: contents">` at mount — so it adds no element to the DOM and is the only shape that survives inside `<tbody>`/`<select>`. **A fragment cannot receive props**: passing any attribute (including `pp-ref`) on its `<x-*>` tag raises `FragmentPropsError`, because there is no root element for forwarding to land on. Use a single native root whenever the component takes props. A `.py` route or layout with sibling top-level nodes is instead wrapped in a layout-neutral `<div pp-component style="display: contents">` boundary host, so use plain siblings when a wrapper `<div>` would be meaningless. The same host appears around a component whose authored root is another `x-*` tag, carrying the parent's forwarded props — an extra `display: contents` div in rendered DOM is expected output. Fragment syntax is implicit: never hand-write `<pp-fragment>` or `<!--pp:…-->` markers, which are compiler output.
81
81
  - When the user asks for a dashboard, admin area, account area, or any grouped child-route section, follow the same mental model as the Next.js App Router: create a parent folder with `layout.py` and place the child routes beneath it. Use a normal folder such as `dashboard/` when the segment should appear in the URL, and use `(group)/` only when it should not.
82
82
  - In grouped section layouts with separate shell and content scrolling, put `pp-reset-scroll="true"` on the content scroll container that should reset on child-route navigation, usually the main pane. Leave persistent shell scrollers such as sidebars or rails unmarked so SPA navigation can preserve their scroll position.
83
83
  - When a single route needs to affect a wrapping layout, have `page()` return `(html(...), {"dashboard_body_class": ...})` and consume that value as `{{ layout.dashboard_body_class }}` in the wrapping layout template. Return the prop from `layout()` when the same value should apply across a whole subtree.
@@ -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 raw wrapper template string (compiled later with `children`/`layout`/`metadata` in scope), `(template, props_dict)`, a props dict alone (passthrough `<slot />` shell), or `None`.
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
 
@@ -194,7 +195,7 @@ This is the top architectural requirement for this workspace. Treat it as a hard
194
195
  - Preserve standard Jinja template syntax such as `{{ ... }}` in layouts and `pp-*` runtime attributes in rendered HTML.
195
196
  - Do not author `pp-component="..."` manually in route or layout templates; the Python render pipeline injects it onto the single root element.
196
197
  - Use a plain `<script>` inside the single route or layout root when it owns PulsePoint logic; no custom script type is required.
197
- - Keep authored route and layout templates to exactly one top-level parent node, the same constraint used for component templates. In source, that parent may be a native HTML element or a single imported `x-*` component tag. If a script is needed, keep it inside that parent instead of as a sibling top-level node. AI must follow this the same way React components return one parent node, otherwise Caspian raises `must have exactly one top-level HTML element so Caspian can inject pp-component`.
198
+ - Default authored route and layout templates to one top-level parent node, the same shape used for component templates. In source, that parent may be a native HTML element or a single imported `x-*` component tag. If a script is needed, keep it inside that parent instead of as a sibling top-level node. A **component** that breaks this raises `must have exactly one top-level HTML element so Caspian can inject pp-component`; a `.py` route or layout instead gets a `display: contents` boundary host, so reach for sibling top-level nodes only when a wrapper element would carry no meaning.
198
199
  - For dashboard, admin, or grouped sections with multiple child routes, prefer folder-level `layout.py` wrappers in `src/app/**` instead of repeating the same shell in each child route.
199
200
  - For grouped shells with independent sidebar and content scrolling, mark the content pane with `pp-reset-scroll="true"` when that pane should start at the top on each child-route navigation. Do not put the attribute on the whole shell when the sidebar or rail should retain its own scroll.
200
201
  - For upload managers and similar interactive lists, prefer `pp.state(...)` plus `pp-for` over manual DOM painting so rerenders keep the list stable.
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 and single-file. A route is one `index.py` whose `page()` returns `html(r"""...""", **context)`; a layout is one `layout.py` whose `layout()` returns its raw wrapper template (optionally with a props dict); a component is one `.py` returning `html(...)`. Inside `html(...)`, `{{ ... }}` is server-side Jinja and `{ ... }` stays for PulsePoint; do not use a Python f-string for the markup, and prefer `r"""..."""` when the markup's `<script>` contains backslashes. See `node_modules/caspian-utils/dist/docs/components.md` and `file-conventions.md`.
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.
@@ -96,7 +97,9 @@ Use `.github/copilot-instructions.md` for the repo-wide implementation rules. Th
96
97
  - The `pp` component-script API mirrors React hooks **inside the `<script>` only** — the surrounding markup is never JSX: `state`, `effect`, `layoutEffect`, `ref`, `memo`, `callback`, `reducer`, `context`, `portal`, `id`, `syncExternalStore`, `imperativeHandle`, `transition`, `deferredValue`, `optimistic`, `errorBoundary`, plus `props`. Use `pp.id()` for generated `id`/`for`/`aria-*` values, `pp.syncExternalStore(...)` for sources the component does not own, and wrap failure-prone subtrees in a parent with `pp.errorBoundary()` instead of letting a render throw reach the console. Verify against `public/js/pp-reactive-v2.min.js` and see `pulsepoint.md` "Hooks and runtime API".
97
98
  - For grouped-subtree SPA navigation UX, the current browser runtime keeps unmarked shell scrollers stable and uses `pp-reset-scroll="true"` on the content pane that should reset. Check `pulsepoint.md`, `routing.md`, and `public/js/pp-reactive-v2.min.js` before changing that behavior.
98
99
  - Before updating docs, verify runtime-specific claims such as middleware order, route param injection, `layout()` behavior, `StateManager` persistence, safe public-file serving, response header, or session-secret behavior against the current `main.py` and installed `casp` package, especially `.venv/Lib/site-packages/casp/runtime_security.py`, rather than copying older notes.
99
- - When generating or reviewing page templates, layout templates, or component markup, treat the single-root rule as a hard requirement: exactly one authored top-level parent element or one imported `x-*` root, with any owned `<script>` kept inside that same root. Do not allow sibling top-level tags, sibling scripts, or stray top-level text, because Caspian injects `pp-component` on that final root and errors if it cannot.
100
+ - When generating or reviewing page templates, layout templates, or component markup, single-root is the default shape: one authored top-level parent element or one imported `x-*` root, with any owned `<script>` kept inside that same root. **For a component it is no longer a hard requirement**: sibling top-level nodes make it a fragment, framed by the comment-pair boundary described below, and `TemplateRootError` is now reserved for a component template with no root at all or with an `x-*` tag as its only root. Prefer a single native root anyway — it is the only shape that can receive props. **For a `.py` page (`index.py`) or layout (`layout.py`)**: sibling top-level nodes are wrapped in a layout-neutral `<div pp-component="…" style="display: contents">` boundary host instead of raising, so a page whose sections are genuinely siblings does not need a meaningless wrapper `<div>`. The relaxation is gated on the `.py` source; `.html` templates still require one root. The same host is emitted for a _composition component_ whose authored root is another `x-*` tag, carrying the parent's forwarded props (which is what makes them reach `pp.props`) and `pp-ref-forward` so an extra `display: contents` div between two component roots in rendered DOM is expected output, not a bug. Keep the owned `<script>` inside the template either way: the host is the boundary, so one script still covers every root.
101
+ - **A multi-root component is a fragment — the `<>…</>` shape — and is now supported.** A component whose `html(...)` has sibling top-level nodes no longer raises: the compiler frames them with the comment pair `<!--pp:id-->…<!--/pp-->`, which `materializeRangeBoundaries` turns into a live `<pp-fragment style="display: contents" pp-component="id">` at mount, before the boundary scan. So a fragment component adds **no element** to the rendered tree, and it is the only shape that survives inside `<tbody>`/`<tr>`/`<select>`/`<optgroup>`, where a `display: contents` wrapper is foster-parented out by the HTML parser (the browser runtime deliberately leaves markers under those parents as comments, so the grouping renders but the fragment owns no identity there — a stateful fragment needs a context an element could also live in). The syntax is **implicit**: siblings in the template, nothing to hand-write. Still never type `<pp-fragment>` or `<!--pp:…-->` yourself — those are compiler/runtime output, and an authored marker is refused by the subtree render cache. **A fragment cannot receive props**: with no root element there is nowhere for `get_attributes(...)` forwarding to land and `pp.props` would be silently empty, so passing any attribute (including `pp-ref`) on the `<x-*>` tag of a fragment component raises `FragmentPropsError` at compile time — give the component a single native root when it needs props. Fragments are excluded from the subtree render cache; a fragment nested inside a cached subtree still has its marker id re-minted per instance.
102
+ - Form controls are controlled _or_ uncontrolled for an element's lifetime. `value="{state}"` / `checked="{state}"` is controlled; the lowercase HTML attributes `defaultvalue="{expr}"` / `defaultchecked="{expr}"` are the uncontrolled form and are real PulsePoint syntax (the camelCase React spellings are not). Binding `value` to state that starts `undefined` flips the mode and makes the runtime log `[PP-WARN] <input#x> changed from uncontrolled to controlled` once — fix the initial state, do not add both attributes.
100
103
  - When generating or reviewing sign-in flows, do not ask the sign-in page to decide redirect targets by re-implementing `next` support or post-login routing. In this stack, redirect behavior is already owned by the Caspian auth runtime plus `src/lib/auth/auth_config.py`; protected-route guest redirects, auth-route redirects, and the default destination are centralized there, with `default_signin_redirect` defaulting to `/dashboard`.
101
104
  - Component markup is server-deferred in an inert `<template>`. `main.py` finalizes every page through `defer_component_roots(...)`, which wraps each outermost `pp-component` root in `<template pp-component="…">`. The browser never parses/validates/fetches `<template>` contents, so raw `{...}` placeholders never reach live DOM at first paint. During `mount()`, PulsePoint captures and empties each plain component `<script>` before materializing `template[pp-component]` into live DOM, then evaluates that captured source in component scope; the same guard applies to scripts introduced by later morphs. Because of this, `{...}` is safe in ANY attribute or position — SVG geometry (`d`, `viewBox`, `points`, `transform`), URL attributes (`src`, `srcset`, `href`, `poster`), form `value`/date/number/color, and text placed directly inside `<table>`/`<select>`. Do NOT add per-tag workarounds to dodge browser first-paint validation: no static-path `hidden` toggles just to avoid binding `d`, no `data-*` URL holders, no gating `<img src>` behind `hidden`, and no SSR-resolving an initial value only to prevent a validation flash. Two compiler transforms still apply for different reasons and stay: `pp-style` (so `.html` source-file HTML/CSS tooling does not choke on `style="{...}"`) and the `<input>`/`<select>`/`checked`/`defaultvalue`/`<textarea>` value rewrites (attribute-vs-property correctness for controlled form fields), not first-paint validation.
102
105
  - This workspace has an app-level quality gate for its own Python (`main.py`, `src/**`, `settings/*.py`), added on top of Caspian — the framework itself ships no test runner. One command, `npm run check` (which calls `uv run python settings/check.py`), runs `pyright` (types), `ruff` (lint), and `pytest` (tests) in a single pass and prints each problem as `path:line:col [tool:code] message`, exiting non-zero on failure. Running it is mandatory: after you create, edit, or delete app-owned Python — bug fix, new file, refactor, or feature — run it and get it fully green before treating the change as done, and do not report work as finished on the assumption that it passes. Fix every reported location and re-run until clean. The gate runs four tools: `pyright`, `ruff`, `templates`, and `pytest`.
@@ -130,7 +133,7 @@ Use `.github/copilot-instructions.md` for the repo-wide implementation rules. Th
130
133
 
131
134
  Use this map before making changes.
132
135
 
133
- If the task generates or edits route, layout, or component HTML templates, check `routing.md`, `components.md`, and `pulsepoint.md` before writing markup. Enforce the single-root contract there: one authored root only, any owned `<script>` inside that root, and no sibling top-level nodes. For reactive behavior, button clicks, form events, uploads, filters, toggles, and list updates, use PulsePoint in the template first instead of standard DOM-event wiring. For normal form submits, prefer `onsubmit="{submitForm(event)}"` plus `Object.fromEntries(new FormData(event.currentTarget).entries())` over `pp-ref`/`pp.effect` listener boilerplate.
136
+ If the task generates or edits route, layout, or component HTML templates, check `routing.md`, `components.md`, and `pulsepoint.md` before writing markup. Enforce the root-shape contract there: one authored root by default, any owned `<script>` inside that root — relaxed to a comment-pair fragment boundary for a multi-root component (which then cannot take props) and to a `display: contents` boundary host for a multi-root `.py` page or layout. For reactive behavior, button clicks, form events, uploads, filters, toggles, and list updates, use PulsePoint in the template first instead of standard DOM-event wiring. For normal form submits, prefer `onsubmit="{submitForm(event)}"` plus `Object.fromEntries(new FormData(event.currentTarget).entries())` over `pp-ref`/`pp.effect` listener boilerplate.
134
137
 
135
138
  - Project layout and file placement: read `node_modules/caspian-utils/dist/docs/index.md` and `node_modules/caspian-utils/dist/docs/project-structure.md`. Verify against the current workspace tree.
136
139
  - File conventions and special route files: read `node_modules/caspian-utils/dist/docs/file-conventions.md` and `node_modules/caspian-utils/dist/docs/routing.md`. Verify against `main.py`, `.venv/Lib/site-packages/casp/layout.py`, `.venv/Lib/site-packages/casp/loading.py`, and `.venv/Lib/site-packages/casp/caspian_config.py`.