create-caspian-app 1.3.2 → 1.3.4

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.
@@ -194,7 +194,7 @@ This is the top architectural requirement for this workspace. Treat it as a hard
194
194
  - Preserve standard Jinja template syntax such as `{{ ... }}` in layouts and `pp-*` runtime attributes in rendered HTML.
195
195
  - Do not author `pp-component="..."` manually in route or layout templates; the Python render pipeline injects it onto the single root element.
196
196
  - 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`.
197
+ - 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
198
  - 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
199
  - 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
200
  - 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
@@ -96,7 +96,9 @@ Use `.github/copilot-instructions.md` for the repo-wide implementation rules. Th
96
96
  - 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
97
  - 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
98
  - 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.
99
+ - 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.
100
+ - **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.
101
+ - 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
102
  - 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
103
  - 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
104
  - 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 +132,7 @@ Use `.github/copilot-instructions.md` for the repo-wide implementation rules. Th
130
132
 
131
133
  Use this map before making changes.
132
134
 
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.
135
+ 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
136
 
135
137
  - 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
138
  - 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`.