caspian-utils 0.0.33 → 0.1.0
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.
- package/dist/docs/ai-validation-checklist.md +6 -4
- package/dist/docs/auth.md +19 -28
- package/dist/docs/components.md +9 -9
- package/dist/docs/core-runtime-map.md +3 -4
- package/dist/docs/database.md +2 -2
- package/dist/docs/fetch-data.md +12 -9
- package/dist/docs/file-conventions.md +11 -9
- package/dist/docs/index.md +14 -12
- package/dist/docs/metadata.md +10 -10
- package/dist/docs/project-structure.md +2 -2
- package/dist/docs/pulsepoint-runtime-map.md +128 -125
- package/dist/docs/pulsepoint.md +104 -23
- package/dist/docs/routing.md +33 -27
- package/package.json +1 -1
|
@@ -54,7 +54,8 @@ Use prompts like these to check whether AI lands on the correct docs and files.
|
|
|
54
54
|
| --- | --- | --- | --- |
|
|
55
55
|
| Create a protected dashboard section with child routes and a shared shell. | [index.md](./index.md), [routing.md](./routing.md), [auth.md](./auth.md), [project-structure.md](./project-structure.md) | `src/app/**`, `src/lib/auth/auth_config.py`, `main.py`, `.venv/Lib/site-packages/casp/layout.py` | section layout ownership, route privacy mode, and child-route wrapping |
|
|
56
56
|
| Make a grouped shell keep sidebar scroll while resetting page content on child-route navigation. | [index.md](./index.md), [routing.md](./routing.md), [pulsepoint.md](./pulsepoint.md), [core-runtime-map.md](./core-runtime-map.md) | `src/app/**/layout.html`, `public/js/pp-reactive-v2.js`, `main.py` | `pp-reset-scroll` placement, push-vs-history scroll behavior, and shared-shell ownership |
|
|
57
|
-
| Create a new contact page with interactive form behavior. | [index.md](./index.md), [routing.md](./routing.md), [pulsepoint.md](./pulsepoint.md), [components.md](./components.md) | `src/app/**/index.html`, `main.py`, `.venv/Lib/site-packages/casp/components_compiler.py`, `.venv/Lib/site-packages/casp/scripts_type.py` | single-root template shape, script-inside-root authoring, and authored-vs-runtime boundaries |
|
|
57
|
+
| Create a new contact page with interactive form behavior. | [index.md](./index.md), [routing.md](./routing.md), [pulsepoint.md](./pulsepoint.md), [components.md](./components.md) | `src/app/**/index.html`, `main.py`, `.venv/Lib/site-packages/casp/components_compiler.py`, `.venv/Lib/site-packages/casp/scripts_type.py` | single-root template shape, script-inside-root authoring, and authored-vs-runtime boundaries |
|
|
58
|
+
| Add a button, filter, or form interaction to a route template. | [index.md](./index.md), [routing.md](./routing.md), [pulsepoint.md](./pulsepoint.md), [pulsepoint-runtime-map.md](./pulsepoint-runtime-map.md) | `src/app/**/index.html`, `public/js/pp-reactive-v2.js`, `main.py` | PulsePoint `on*` event attributes, `pp.state`, directives, and avoiding id-driven `querySelector` or `addEventListener` wiring |
|
|
58
59
|
| Add a file manager page with upload progress and persisted metadata. | [index.md](./index.md), [file-uploads.md](./file-uploads.md), [fetch-data.md](./fetch-data.md), [database.md](./database.md) | `src/app/**/index.html`, `src/app/**/index.py`, `src/lib/**`, `prisma/schema.prisma` when Prisma applies | route-owned upload actions, persisted metadata flow, and Prisma-backed storage boundaries |
|
|
59
60
|
| Explain why authored Caspian templates use a plain `<script>` instead of `type="text/pp"`. | [index.md](./index.md), [pulsepoint.md](./pulsepoint.md), [components.md](./components.md), [routing.md](./routing.md) | `main.py`, `.venv/Lib/site-packages/casp/scripts_type.py`, `.venv/Lib/site-packages/casp/components_compiler.py` | script rewriting, `pp-component` injection, and authored-vs-runtime boundaries |
|
|
60
61
|
| Debug why `StateManager` does not persist across a full redirect. | [index.md](./index.md), [state.md](./state.md), [auth.md](./auth.md), [core-runtime-map.md](./core-runtime-map.md) | `main.py`, `.venv/Lib/site-packages/casp/state_manager.py` | wire vs non-wire reset behavior and `request.state.session` dependency |
|
|
@@ -72,9 +73,10 @@ Treat the table as a prompt pack for spot checks, not as a full validation matri
|
|
|
72
73
|
- AI skips `caspian.config.json` and assumes an optional feature is enabled because a packaged doc exists.
|
|
73
74
|
- AI reads only the packaged feature doc and never checks `main.py` or the installed runtime.
|
|
74
75
|
- AI edits framework internals when the task only requires app-owned route or helper changes.
|
|
75
|
-
- AI treats runtime HTML examples as authored template examples.
|
|
76
|
-
- AI generates a route or component template with a valid-looking root element but leaves a sibling top-level `<script>` or second top-level element after it.
|
|
77
|
-
- AI
|
|
76
|
+
- AI treats runtime HTML examples as authored template examples.
|
|
77
|
+
- AI generates a route or component template with a valid-looking root element but leaves a sibling top-level `<script>` or second top-level element after it.
|
|
78
|
+
- AI starts first-party interactivity with ids, `data-*` attributes, `querySelector`, `addEventListener`, manual `fetch`, or manual `innerHTML` instead of PulsePoint `on*` attributes, state, directives, and `pp.rpc()`.
|
|
79
|
+
- AI puts `pp-reset-scroll="true"` on the whole shell or `body` when only the page-content pane should reset, causing persistent sidebars or rails to lose their scroll position.
|
|
78
80
|
- AI decides behavior from memory without checking the owning implementation details.
|
|
79
81
|
|
|
80
82
|
## Decision Rule
|
package/dist/docs/auth.md
CHANGED
|
@@ -20,14 +20,14 @@ Treat `casp.auth` as the default authentication layer in Caspian app code. Do no
|
|
|
20
20
|
|
|
21
21
|
## Overview
|
|
22
22
|
|
|
23
|
-
Caspian authentication has two main layers:
|
|
24
|
-
|
|
25
|
-
- app-level policy
|
|
26
|
-
- framework runtime behavior
|
|
23
|
+
Caspian authentication has two main layers:
|
|
24
|
+
|
|
25
|
+
- app-level policy, controlled by `src/lib/auth/auth_config.py`
|
|
26
|
+
- framework runtime behavior, implemented by `.venv/Lib/site-packages/casp/auth.py`
|
|
27
27
|
|
|
28
28
|
The main public API includes:
|
|
29
29
|
|
|
30
|
-
- `AuthSettings` for centralized auth
|
|
30
|
+
- `AuthSettings` for centralized app auth policy
|
|
31
31
|
- `configure_auth(...)` and `get_auth_settings()` for app startup and reads
|
|
32
32
|
- the global `auth` object for session lifecycle work
|
|
33
33
|
- `require_auth`, `require_role`, and `guest_only` for page-level protection
|
|
@@ -49,7 +49,7 @@ from casp.auth import (
|
|
|
49
49
|
|
|
50
50
|
## Default Auth Rule
|
|
51
51
|
|
|
52
|
-
- Define app-wide auth behavior in `build_auth_settings()` and apply it once at startup with `configure_auth(...)`.
|
|
52
|
+
- Define app-wide auth behavior in `src/lib/auth/auth_config.py` through `build_auth_settings()` and apply it once at startup with `configure_auth(...)`.
|
|
53
53
|
- Use `auth.sign_in(...)` and `auth.sign_out(...)` instead of setting or clearing session keys directly.
|
|
54
54
|
- Prefer `pp.rpc(...)` plus `@rpc(require_auth=True)` for signout buttons or menus rendered in pages or components. Use a dedicated signout route only when you need a plain HTML form POST or a no-JavaScript fallback.
|
|
55
55
|
- Use `@require_auth`, `@require_role`, and `@guest_only` for page access rules.
|
|
@@ -61,15 +61,15 @@ from casp.auth import (
|
|
|
61
61
|
|
|
62
62
|
## Framework Internals Note
|
|
63
63
|
|
|
64
|
-
- The centralized app auth
|
|
65
|
-
- The installed framework implementation lives in `.venv/Lib/site-packages/casp/auth.py`.
|
|
66
|
-
- Treat `auth_config.py` as project code and `casp/auth.py` as framework code.
|
|
64
|
+
- The centralized app auth policy controller is `src/lib/auth/auth_config.py`.
|
|
65
|
+
- The installed framework implementation lives in `.venv/Lib/site-packages/casp/auth.py`.
|
|
66
|
+
- Treat `auth_config.py` as project code and `casp/auth.py` as framework runtime code. Do not edit `casp/auth.py` to control app route privacy, redirects, or RBAC.
|
|
67
67
|
- If upstream docs and the installed implementation disagree, prefer the installed implementation for local project guidance.
|
|
68
68
|
- Use [core-runtime-map.md](./core-runtime-map.md) when an auth task crosses `main.py` bootstrap behavior such as development cookie scoping or middleware ownership.
|
|
69
69
|
|
|
70
70
|
## Centralized Auth Settings
|
|
71
71
|
|
|
72
|
-
Keep application auth policy in `src/lib/auth/auth_config.py`.
|
|
72
|
+
Keep application auth policy in `src/lib/auth/auth_config.py`. This file is the controller for route privacy, redirects, and RBAC.
|
|
73
73
|
|
|
74
74
|
Example:
|
|
75
75
|
|
|
@@ -80,10 +80,12 @@ from casp.auth import AuthSettings
|
|
|
80
80
|
|
|
81
81
|
def build_auth_settings() -> AuthSettings:
|
|
82
82
|
"""
|
|
83
|
-
Centralized auth
|
|
84
|
-
|
|
85
|
-
Keep secrets (AUTH_SECRET, AUTH_COOKIE_NAME) in .env.
|
|
86
|
-
Keep app-level session settings in .env (SESSION_LIFETIME_HOURS, etc).
|
|
83
|
+
Centralized app auth policy controller.
|
|
84
|
+
|
|
85
|
+
Keep secrets (AUTH_SECRET, AUTH_COOKIE_NAME) in .env.
|
|
86
|
+
Keep app-level session settings in .env (SESSION_LIFETIME_HOURS, etc).
|
|
87
|
+
Decide route privacy, redirects, and RBAC here at app setup time instead of
|
|
88
|
+
changing Caspian core runtime files.
|
|
87
89
|
"""
|
|
88
90
|
|
|
89
91
|
return AuthSettings(
|
|
@@ -101,7 +103,7 @@ def build_auth_settings() -> AuthSettings:
|
|
|
101
103
|
is_role_based=False,
|
|
102
104
|
role_identifier="role",
|
|
103
105
|
|
|
104
|
-
#
|
|
106
|
+
# RBAC policy is app-owned here; the runtime expects ROUTE/PATTERN -> [ROLES].
|
|
105
107
|
role_based_routes={},
|
|
106
108
|
|
|
107
109
|
# Redirects / prefixes
|
|
@@ -150,7 +152,7 @@ Make this decision at app setup time in `src/lib/auth/auth_config.py`.
|
|
|
150
152
|
- In the current runtime, `auth_routes=["/signin", "/signup"]` stays public by default, and most apps do not need to change it unless the user explicitly asks for different auth routes.
|
|
151
153
|
- In all-private mode, the default `public_routes=["/"]` keeps the home page public unless you change that list.
|
|
152
154
|
- `token_auto_refresh=True` does not make routes private. It only enables sliding-session refresh when the request lifecycle calls `auth.refresh_session()`.
|
|
153
|
-
-
|
|
155
|
+
- Do not modify Caspian core files for this decision. Keep the policy in `src/lib/auth/auth_config.py`.
|
|
154
156
|
- If you customize `src/lib/auth/auth_config.py`, add it to `excludeFiles` in `caspian.config.json` so update commands do not overwrite your local auth policy.
|
|
155
157
|
|
|
156
158
|
Example all-private setup with a few public exceptions:
|
|
@@ -765,18 +767,7 @@ Behavior:
|
|
|
765
767
|
|
|
766
768
|
Use this helper when custom form or fetch flows need access to the session CSRF token.
|
|
767
769
|
|
|
768
|
-
##
|
|
769
|
-
|
|
770
|
-
The installed auth file still includes `AuthConfig` as a compatibility alias.
|
|
771
|
-
|
|
772
|
-
It exposes:
|
|
773
|
-
|
|
774
|
-
- property proxies for `PUBLIC_ROUTES`, `PRIVATE_ROUTES`, `AUTH_ROUTES`, `IS_ALL_ROUTES_PRIVATE`, `DEFAULT_SIGNIN_REDIRECT`, and `DEFAULT_SIGNOUT_REDIRECT`
|
|
775
|
-
- `AuthConfig.check_auth_role(...)` as a proxy to `auth.check_role(...)`
|
|
776
|
-
|
|
777
|
-
Prefer `AuthSettings`, `configure_auth(...)`, and `auth.settings` in new code.
|
|
778
|
-
|
|
779
|
-
## Current Implementation Notes
|
|
770
|
+
## Current Implementation Notes
|
|
780
771
|
|
|
781
772
|
- The installed auth runtime is session-backed. It stores the auth payload and CSRF token in `request.session`.
|
|
782
773
|
- Expiration uses timestamps and the current duration parser only accepts `s`, `m`, `h`, and `d` units.
|
package/dist/docs/components.md
CHANGED
|
@@ -26,10 +26,11 @@ As the app grows, treat `src/components/` as the default home for reusable appli
|
|
|
26
26
|
|
|
27
27
|
## Mental Model
|
|
28
28
|
|
|
29
|
-
- Use a Python component when you want a reusable server-rendered UI building block.
|
|
30
|
-
- Return an HTML string directly for small presentational components.
|
|
31
|
-
- Use `render_html(...)` with a same-name `.html` file when the component has more markup, PulsePoint behavior, or clearer separation between Python logic and UI.
|
|
32
|
-
-
|
|
29
|
+
- Use a Python component when you want a reusable server-rendered UI building block.
|
|
30
|
+
- Return an HTML string directly for small presentational components.
|
|
31
|
+
- Use `render_html(...)` with a same-name `.html` file when the component has more markup, PulsePoint behavior, or clearer separation between Python logic and UI.
|
|
32
|
+
- When a component needs first-party interactivity, bind events in the component template with PulsePoint-handled `on*` attributes and keep state in `pp.state(...)`; do not build id-driven `querySelector(...)` or `addEventListener(...)` wiring for normal component behavior.
|
|
33
|
+
- Keep page-level workflows in `src/app/`, move reusable UI into `src/components/`, and keep helpers, services, validators, and adapters in `src/lib/`.
|
|
33
34
|
|
|
34
35
|
## Framework Internals Note
|
|
35
36
|
|
|
@@ -38,8 +39,6 @@ When the task is about component internals rather than normal app-owned componen
|
|
|
38
39
|
- `.venv/Lib/site-packages/casp/component_decorator.py` owns `@component`, `Component`, `render_html(...)`, and component loading.
|
|
39
40
|
- `.venv/Lib/site-packages/casp/components_compiler.py` owns `@import` parsing, `x-*` tag resolution, root validation, and `pp-component` injection.
|
|
40
41
|
- `.venv/Lib/site-packages/casp/html_attrs.py` owns `get_attributes(...)` and the Python-side `merge_classes(...)` contract.
|
|
41
|
-
- `.venv/Lib/site-packages/casp/syntax_compiler.py` owns Caspian template syntax transpilation before Jinja render.
|
|
42
|
-
|
|
43
42
|
Use [core-runtime-map.md](./core-runtime-map.md) when you need the broader Python runtime-module map. Use [pulsepoint-runtime-map.md](./pulsepoint-runtime-map.md) when a component task is specifically about browser-side PulsePoint state, refs, context, portals, events, RPC, or SPA behavior.
|
|
44
43
|
|
|
45
44
|
## Basic Component
|
|
@@ -202,7 +201,7 @@ def Counter(label: str = "Clicks") -> str:
|
|
|
202
201
|
|
|
203
202
|
```html
|
|
204
203
|
<div>
|
|
205
|
-
<h3>
|
|
204
|
+
<h3>{{ label }}</h3>
|
|
206
205
|
<button onclick="setCount(count + 1)">
|
|
207
206
|
{count}
|
|
208
207
|
</button>
|
|
@@ -354,8 +353,9 @@ Keep synchronous components as the default. Switch to `async def` only when the
|
|
|
354
353
|
|
|
355
354
|
- Put reusable components in `src/components/` and keep route files in `src/app/`.
|
|
356
355
|
- For dashboards, admin areas, account sections, and route groups with child routes, put the shared shell in the parent folder's `layout.html` and compose it from reusable components there instead of repeating the same shell in every child `index.html`.
|
|
357
|
-
- If the component includes PulsePoint behavior, prefer a thin Python wrapper plus a same-name `.html` template.
|
|
358
|
-
-
|
|
356
|
+
- If the component includes PulsePoint behavior, prefer a thin Python wrapper plus a same-name `.html` template.
|
|
357
|
+
- For component clicks, inputs, menus, toggles, filters, and list updates, use PulsePoint events and directives inside that `.html` template. Avoid manual DOM selection, manual listener setup, and manual `innerHTML` rendering unless integrating a third-party imperative widget.
|
|
358
|
+
- Keep the component file name, exported function name, and authored tag aligned, such as `Button.py`, `def Button(...)`, and `<x-button />`.
|
|
359
359
|
- Accept `children` or `**props` when the component should support nested content.
|
|
360
360
|
- Keep page-level data loading in `page()` when the data is not intrinsic to the component itself.
|
|
361
361
|
- If you add `@rpc()` functions inside a component file, keep their names globally unique because component RPCs are not route-scoped.
|
|
@@ -88,7 +88,7 @@ Interactive CRUD page:
|
|
|
88
88
|
|
|
89
89
|
| Runtime file | Primary responsibility | Read these docs |
|
|
90
90
|
| --- | --- | --- |
|
|
91
|
-
| [.venv/Lib/site-packages/casp/layout.py](../../../../.venv/Lib/site-packages/casp/layout.py) | `render_page(...)`, `render_layout(...)`, nested layout discovery, metadata merge,
|
|
91
|
+
| [.venv/Lib/site-packages/casp/layout.py](../../../../.venv/Lib/site-packages/casp/layout.py) | `render_page(...)`, `render_layout(...)`, nested layout discovery, metadata merge, sync or async `layout()` results, and parser-based `<slot />` replacement | [routing.md](./routing.md), [metadata.md](./metadata.md) |
|
|
92
92
|
| [.venv/Lib/site-packages/casp/auth.py](../../../../.venv/Lib/site-packages/casp/auth.py) | `AuthSettings`, route privacy checks, session payloads, OAuth providers, CSRF helper behavior, and redirect logic | [auth.md](./auth.md) |
|
|
93
93
|
| [.venv/Lib/site-packages/casp/runtime_security.py](../../../../.venv/Lib/site-packages/casp/runtime_security.py) | safe public-file serving, baseline non-CSP response headers, production-safe error messages, and production session-secret enforcement used by `main.py` | [project-structure.md](./project-structure.md), [auth.md](./auth.md) |
|
|
94
94
|
| [.venv/Lib/site-packages/casp/rpc.py](../../../../.venv/Lib/site-packages/casp/rpc.py) | `@rpc()` registration, rate limits, request handling, auth-aware action checks, and streamed RPC responses | [fetch-data.md](./fetch-data.md) |
|
|
@@ -101,9 +101,8 @@ Interactive CRUD page:
|
|
|
101
101
|
| [.venv/Lib/site-packages/casp/scripts_type.py](../../../../.venv/Lib/site-packages/casp/scripts_type.py) | rewriting authored body `<script>` tags to `type="text/pp"` in rendered HTML | [pulsepoint.md](./pulsepoint.md), [routing.md](./routing.md), [components.md](./components.md) |
|
|
102
102
|
| [.venv/Lib/site-packages/casp/html_attrs.py](../../../../.venv/Lib/site-packages/casp/html_attrs.py) | `get_attributes(...)`, alias normalization, and the Python-side `merge_classes(...)` contract | [components.md](./components.md) |
|
|
103
103
|
| [.venv/Lib/site-packages/casp/caspian_config.py](../../../../.venv/Lib/site-packages/casp/caspian_config.py) | typed config loading, feature-flag reads, `settings/files-list.json` parsing, and route rule derivation | [project-structure.md](./project-structure.md), [commands.md](./commands.md), [routing.md](./routing.md) |
|
|
104
|
-
| [.venv/Lib/site-packages/casp/syntax_compiler.py](../../../../.venv/Lib/site-packages/casp/syntax_compiler.py) | transpiling Caspian `<[[ ... ]]>` and `<template casp-*>` syntax before template render | [components.md](./components.md), [routing.md](./routing.md) |
|
|
105
104
|
|
|
106
|
-
Secondary helpers such as `html_native.py`, `loading.py`, and `string_helpers.py` support the modules above. Read
|
|
105
|
+
Secondary helpers such as `html_native.py`, `loading.py`, and `string_helpers.py` support the modules above. `html_native.py` owns BeautifulSoup-backed fragment parsing used by component/root transforms and layout slot replacement. Read these helpers only when a higher-level runtime file is still insufficient to explain the behavior you are tracing.
|
|
107
106
|
|
|
108
107
|
## Verification Focus
|
|
109
108
|
|
|
@@ -116,7 +115,7 @@ Use these behavior checkpoints when AI needs the fastest verification path for a
|
|
|
116
115
|
| `casp.auth` | auth settings, signin and signout flow, provider wiring, and page protection behavior |
|
|
117
116
|
| `casp.rpc` and streamed RPC responses | middleware interception, CSRF and session expectations, registry behavior, and helper-level RPC contracts |
|
|
118
117
|
| `casp.layout` | layout discovery, metadata merge, root handling, and layout rendering rules |
|
|
119
|
-
| `casp.components_compiler`, `casp.component_decorator`, `casp.scripts_type`, and template-root injection | `@import` parsing, `x-*` expansion,
|
|
118
|
+
| `casp.components_compiler`, `casp.component_decorator`, `casp.scripts_type`, and template-root injection | `@import` parsing, `x-*` expansion, deterministic root keys, `pp-component` injection, and authored-script rewriting to `type="text/pp"` |
|
|
120
119
|
| `casp.state_manager` | wire vs non-wire reset behavior, request-state persistence assumptions, and AttributeDict access |
|
|
121
120
|
| `casp.cache_handler` | filename generation, manifest writes, TTL handling, and invalidation behavior |
|
|
122
121
|
| `casp.caspian_config` | config parsing, files index building, and Next.js-style route inventory behavior |
|
package/dist/docs/database.md
CHANGED
|
@@ -178,7 +178,7 @@ Prisma calls fit naturally in:
|
|
|
178
178
|
- `async def page()` for first-render data
|
|
179
179
|
- `@rpc()` actions for browser-triggered reads and writes
|
|
180
180
|
|
|
181
|
-
Keep Prisma I/O
|
|
181
|
+
Keep route-specific Prisma I/O in `page()` or `@rpc()` actions. The installed layout engine supports synchronous and async `layout()` results, but layout work should stay focused on shared subtree props or metadata.
|
|
182
182
|
|
|
183
183
|
See `fetch-data.md` for the recommended route-render versus RPC split.
|
|
184
184
|
|
|
@@ -352,5 +352,5 @@ If an AI agent is working on a Caspian app with Prisma enabled, apply these rule
|
|
|
352
352
|
- Reuse the existing `src/lib/prisma/` package when the Python app needs database access.
|
|
353
353
|
- For file managers and uploads, persist metadata in Prisma and keep blob storage separate. See [file-uploads.md](./file-uploads.md).
|
|
354
354
|
- Put reusable database helpers in `src/lib/`; keep route and RPC orchestration in `src/app/`.
|
|
355
|
-
- Use `async def page()` for first-render reads.
|
|
355
|
+
- Use `async def page()` for route-specific first-render reads. Use `layout()` only for shared subtree props or metadata, and use `@rpc()` plus `pp.rpc()` for browser-triggered reads and writes.
|
|
356
356
|
- Check `fetch-data.md` for route versus RPC guidance and `validation.md` before writing public mutations.
|
package/dist/docs/fetch-data.md
CHANGED
|
@@ -21,7 +21,9 @@ related:
|
|
|
21
21
|
|
|
22
22
|
This page explains how data fetching works in Caspian. Use route functions for initial page data and use RPC actions for browser-triggered reads, writes, streams, uploads, and normal CRUD work.
|
|
23
23
|
|
|
24
|
-
Treat RPC as the default way for browser code to talk to Python in Caspian. For CRUD operations and any browser-initiated backend reads after first render, default to `@rpc()` on the server and `pp.rpc()` in PulsePoint code. Do not reach for ad hoc fetch calls to custom JSON endpoints, alternate transport layers, or older helper names unless the task explicitly requires that shape.
|
|
24
|
+
Treat RPC as the default way for browser code to talk to Python in Caspian. For CRUD operations and any browser-initiated backend reads after first render, default to `@rpc()` on the server and `pp.rpc()` in PulsePoint code. Do not reach for ad hoc fetch calls to custom JSON endpoints, alternate transport layers, or older helper names unless the task explicitly requires that shape.
|
|
25
|
+
|
|
26
|
+
Browser-triggered data work should still be PulsePoint-first at the event layer. Bind the initiating click, submit, input, upload, refresh, filter, or pagination control in authored HTML with `onclick`, `onsubmit`, `oninput`, `onchange`, or another native `on*` attribute handled by PulsePoint. Do not set up first-party data actions by assigning ids and then wiring `querySelector(...)`, `addEventListener(...)`, manual `fetch(...)`, or manual DOM repainting.
|
|
25
27
|
|
|
26
28
|
MCP is a separate integration surface. Do not place app-owned FastMCP tools in route `index.py` files or treat `@rpc()` actions as a replacement for MCP tools. Use `mcp.md` and `src/lib/mcp/` only when `caspian.config.json` has `mcp: true`. If `mcp` is false, do not assume those files exist.
|
|
27
29
|
|
|
@@ -29,7 +31,7 @@ MCP is a separate integration surface. Do not place app-owned FastMCP tools in r
|
|
|
29
31
|
|
|
30
32
|
Caspian has two main data-loading paths:
|
|
31
33
|
|
|
32
|
-
- `page()` for initial-render data, plus `layout()` for
|
|
34
|
+
- `page()` for initial-render data, plus `layout()` for shared props or metadata during the render
|
|
33
35
|
- `@rpc()` plus `pp.rpc()` for interactive fetches after the page is already loaded
|
|
34
36
|
|
|
35
37
|
In practice, most pages use both:
|
|
@@ -44,10 +46,11 @@ When a page belongs to a grouped subtree such as a dashboard, account area, admi
|
|
|
44
46
|
|
|
45
47
|
## Default Data Rule
|
|
46
48
|
|
|
47
|
-
- Use `page()` for
|
|
49
|
+
- Use `page()` for route-level data required before HTML renders, and use `layout()` only for shared subtree props or metadata.
|
|
48
50
|
- When a route renders UI and also needs backend work, keep the HTML in the sibling `index.html`; `index.py` should prepare data and call `render_page(__file__, ...)`, not inline the route markup.
|
|
49
|
-
- Use `@rpc()` on the server and `pp.rpc()` in PulsePoint code for all browser-triggered data work after first render, including CRUD operations and follow-up reads.
|
|
50
|
-
-
|
|
51
|
+
- Use `@rpc()` on the server and `pp.rpc()` in PulsePoint code for all browser-triggered data work after first render, including CRUD operations and follow-up reads.
|
|
52
|
+
- Trigger those browser actions through PulsePoint event attributes in the HTML, not through a separate DOM listener layer.
|
|
53
|
+
- Keep custom REST or other endpoint patterns as explicit exceptions, not the baseline Caspian approach.
|
|
51
54
|
|
|
52
55
|
## Initial Data In `index.py`
|
|
53
56
|
|
|
@@ -78,7 +81,7 @@ If a route's first-render HTML is public and stable enough to reuse across reque
|
|
|
78
81
|
Notes:
|
|
79
82
|
|
|
80
83
|
- Prefer `async def page()` when your database or API client is async-capable.
|
|
81
|
-
- Put shared section-level props in `layout.py` when multiple child routes need the same
|
|
84
|
+
- Put shared section-level props in `layout.py` when multiple child routes need the same payload. The current layout engine supports synchronous and async `layout()` results, but route-specific data should stay in `page()`.
|
|
82
85
|
- Use a normal parent folder such as `dashboard/` when the section name should appear in the URL. Use a route-group folder such as `(reports)/` only when the shared wrapper should organize child routes without adding a URL segment.
|
|
83
86
|
- Keep reusable database or API clients under `src/lib/`; keep route-specific orchestration in `src/app/`.
|
|
84
87
|
|
|
@@ -291,7 +294,7 @@ For initial route rendering with `render_page(...)`, prefer passing plain templa
|
|
|
291
294
|
|
|
292
295
|
## Recommended Decision Rule
|
|
293
296
|
|
|
294
|
-
Use `page()` when
|
|
297
|
+
Use `page()` when route-specific data is part of the page render. Use `layout()` for shared subtree props or metadata. Use `@rpc()` plus `pp.rpc()` when the browser needs to ask the server for more data after the page is already visible.
|
|
295
298
|
|
|
296
299
|
A common pattern is:
|
|
297
300
|
|
|
@@ -323,8 +326,8 @@ If the first-render HTML is expensive to produce and safe to share between visit
|
|
|
323
326
|
If an AI agent is choosing how to load data in Caspian, apply these rules first.
|
|
324
327
|
|
|
325
328
|
- Put first-render data loading in `src/app/**/index.py`.
|
|
326
|
-
- Put shared section props in `layout.py` only when multiple child routes need the same
|
|
327
|
-
- Keep async I/O in `page()` or `@rpc()`
|
|
329
|
+
- Put shared section props in `layout.py` only when multiple child routes need the same shared data.
|
|
330
|
+
- Keep route-specific async I/O in `page()` or `@rpc()` so `layout.py` stays focused on shared subtree props or metadata.
|
|
328
331
|
- For grouped subtrees, follow the section layout pattern in [routing.md](./routing.md) before deciding where `page()` data and `@rpc()` actions belong.
|
|
329
332
|
- Treat RPC as the default read and write layer between PulsePoint code and Python route logic, especially for CRUD and interactive backend reads.
|
|
330
333
|
- Use `@rpc()` for backend functions that should be callable from the browser.
|
|
@@ -33,7 +33,7 @@ Treat `caspian.config.json` and the actual project tree as the source of truth f
|
|
|
33
33
|
| `index.html` | Authored visible page template for a route | The route renders UI | `src/app/**`, `routing.md`, `pulsepoint.md` |
|
|
34
34
|
| `index.py` | Backend companion for route logic and metadata | The route needs `page()`, metadata, auth checks, redirects, caching, or route-owned `@rpc()` actions | `main.py`, `.venv/Lib/site-packages/casp/layout.py` |
|
|
35
35
|
| `layout.html` | Shared shell for a route subtree | Multiple child routes share wrapper markup | `.venv/Lib/site-packages/casp/layout.py`, `routing.md` |
|
|
36
|
-
| `layout.py` | Shared
|
|
36
|
+
| `layout.py` | Shared props and metadata defaults for a subtree | The shared shell needs Python-provided values or metadata | `.venv/Lib/site-packages/casp/layout.py`, `metadata.md` |
|
|
37
37
|
| `loading.html` | Route-scope loading UI used during SPA navigation | A section or page needs an immediate loading state before the next route finishes rendering | `.venv/Lib/site-packages/casp/loading.py`, `public/js/pp-reactive-v2.js` |
|
|
38
38
|
| `not-found.html` | Global 404 page | The app needs a branded fallback for unmatched URLs | `main.py` |
|
|
39
39
|
| `error.html` | Global 500 page | The app needs a safe fallback for unhandled exceptions | `main.py` |
|
|
@@ -129,9 +129,9 @@ When one page needs to influence a wrapping layout, `page()` can return `(page_h
|
|
|
129
129
|
Use it for:
|
|
130
130
|
|
|
131
131
|
- shared shell markup such as sidebars, headers, docs rails, or dashboard frames
|
|
132
|
-
- the
|
|
133
|
-
- shared layout props consumed as `
|
|
134
|
-
- shared metadata fields consumed as `
|
|
132
|
+
- the `<slot />` insertion point for child routes
|
|
133
|
+
- shared layout props consumed as `{{ layout.* }}`
|
|
134
|
+
- shared metadata fields consumed as `{{ metadata.* }}`
|
|
135
135
|
|
|
136
136
|
Example:
|
|
137
137
|
|
|
@@ -140,12 +140,14 @@ Example:
|
|
|
140
140
|
<aside class="docs-nav">Docs navigation</aside>
|
|
141
141
|
|
|
142
142
|
<main class="docs-content" pp-reset-scroll="true">
|
|
143
|
-
|
|
143
|
+
<slot />
|
|
144
144
|
</main>
|
|
145
145
|
</div>
|
|
146
146
|
```
|
|
147
147
|
|
|
148
|
-
Use nested `layout.html` files for sections like `dashboard/`, `docs/`, `account/`, or route groups such as `(marketing)/`.
|
|
148
|
+
Use nested `layout.html` files for sections like `dashboard/`, `docs/`, `account/`, or route groups such as `(marketing)/`.
|
|
149
|
+
|
|
150
|
+
The child outlet must be a real authored `<slot />` element in `layout.html`. The runtime parses layout HTML and replaces real slot elements during nested rendering; it does not invent an implicit slot from `layout.py` or replace escaped documentation text such as `<slot />`.
|
|
149
151
|
|
|
150
152
|
In grouped shells with separate shell and content scrolling, put `pp-reset-scroll="true"` on the content pane that should reset on child-route navigation. Leave persistent shell scrollers such as sidebars unmarked when they should keep their own scroll position.
|
|
151
153
|
|
|
@@ -155,7 +157,7 @@ In grouped shells with separate shell and content scrolling, put `pp-reset-scrol
|
|
|
155
157
|
|
|
156
158
|
Use it for:
|
|
157
159
|
|
|
158
|
-
- shared
|
|
160
|
+
- shared props
|
|
159
161
|
- metadata defaults for everything below that folder
|
|
160
162
|
- small shared layout decisions that belong to the subtree rather than one page
|
|
161
163
|
|
|
@@ -179,7 +181,7 @@ def layout():
|
|
|
179
181
|
}
|
|
180
182
|
```
|
|
181
183
|
|
|
182
|
-
Important runtime detail: `layout()`
|
|
184
|
+
Important runtime detail: `layout()` may be synchronous or async in the installed runtime. Keep async layout work focused on shared subtree props or metadata; put route-specific first-render data in `page()` and browser-triggered work in route-owned `@rpc()` actions.
|
|
183
185
|
|
|
184
186
|
## `loading.html`
|
|
185
187
|
|
|
@@ -199,7 +201,7 @@ Example layout shell:
|
|
|
199
201
|
|
|
200
202
|
```html
|
|
201
203
|
<main class="docs-content" pp-loading-content="true" pp-reset-scroll="true">
|
|
202
|
-
|
|
204
|
+
<slot />
|
|
203
205
|
</main>
|
|
204
206
|
```
|
|
205
207
|
|
package/dist/docs/index.md
CHANGED
|
@@ -29,8 +29,9 @@ Before making feature, tooling, or file-placement decisions in a Caspian project
|
|
|
29
29
|
|
|
30
30
|
When generating or editing a Caspian app, treat these as the default choices unless the task explicitly requires something else:
|
|
31
31
|
|
|
32
|
-
- Use PulsePoint for reactive frontend behavior.
|
|
33
|
-
-
|
|
32
|
+
- Use PulsePoint for reactive frontend behavior.
|
|
33
|
+
- For first-party HTML events and reactivity, use PulsePoint `on*` attributes, state, refs, effects, directives, and `pp.rpc()` instead of ordinary DOM wiring with ids, `data-*` state, `querySelector`, `addEventListener`, or manual `innerHTML`.
|
|
34
|
+
- Treat every authored route, layout, and component HTML file like a React component return value: exactly one top-level parent HTML element or one imported `x-*` root, with any owned plain `<script>` kept inside that same root.
|
|
34
35
|
- When `caspian.config.json` has `tailwindcss: true`, use Python `merge_classes(...)` plus browser `twMerge(...)` as the only supported Tailwind class-merging path.
|
|
35
36
|
- Use `@rpc()` plus `pp.rpc()` for browser-triggered reads, writes, streaming, and uploads.
|
|
36
37
|
- Use `Validate` and `Rule` from `casp.validate` for server-side input validation and sanitization.
|
|
@@ -80,7 +81,7 @@ The packaged Caspian docs referenced by this index live here:
|
|
|
80
81
|
- `auth.md` - Session-backed authentication with `casp.auth`, centralized `auth_config.py`, public-vs-private route mode guidance, RPC-first signout guidance, RBAC, and OAuth provider helpers
|
|
81
82
|
- `file-conventions.md` - quick decision guide for `index.html`, `index.py`, `layout.html`, `layout.py`, `loading.html`, `not-found.html`, and `error.html`, plus the owning runtime files to verify
|
|
82
83
|
- `components.md` - Create reusable Python components, template-backed UI, HTML-first `x-*` component tags, the single-parent authored-root rule for component HTML files, and the Python-side `merge_classes(...)` contract when Tailwind CSS is enabled
|
|
83
|
-
- `pulsepoint.md` - Default reactive frontend runtime contract for component scripts, state, effects, directives, SPA navigation scroll restoration, `pp-reset-scroll`, and direct browser `twMerge(...)` usage when Tailwind CSS is enabled
|
|
84
|
+
- `pulsepoint.md` - Default reactive frontend runtime contract for component scripts, first-party `on*` events, state, effects, directives, SPA navigation scroll restoration, `pp-reset-scroll`, and direct browser `twMerge(...)` usage when Tailwind CSS is enabled
|
|
84
85
|
- `fetch-data.md` - Initial server-side data loading and browser-triggered RPC flows with `pp.rpc()`, streaming, uploads, and auth-aware actions
|
|
85
86
|
- `file-uploads.md` - Route-local file uploads and file-manager flows with `@rpc()`, `pp.rpc()`, Prisma metadata, public asset storage, and BrowserSync ignore rules
|
|
86
87
|
- `state.md` - Request-scoped server state with `StateManager`, session-backed JSON persistence, and listener callbacks for transient flows
|
|
@@ -98,15 +99,16 @@ Preferred lookup order:
|
|
|
98
99
|
|
|
99
100
|
1. Read `node_modules/caspian-utils/dist/docs/index.md` to discover available local docs.
|
|
100
101
|
2. Read `./caspian.config.json` before making any feature assumption. A doc existing in the package does not mean that feature is enabled in the current project.
|
|
101
|
-
3. Before authoring or editing any `src/app/**` or component HTML template, apply the single-root invariant: one authored root only, any owned `<script>` inside that root, and no handwritten `pp-component` or `type="text/pp"`.
|
|
102
|
-
4.
|
|
103
|
-
5.
|
|
104
|
-
6. If the task
|
|
105
|
-
7.
|
|
106
|
-
8.
|
|
107
|
-
9.
|
|
108
|
-
10.
|
|
109
|
-
11.
|
|
102
|
+
3. Before authoring or editing any `src/app/**` or component HTML template, apply the single-root invariant: one authored root only, any owned `<script>` inside that root, and no handwritten `pp-component` or `type="text/pp"`.
|
|
103
|
+
4. Before inventing browser JavaScript, check whether the interaction is first-party UI behavior. If it is, use PulsePoint `on*` attributes, `pp.state`, refs, effects, directives, and `pp.rpc()` rather than id-driven DOM scripting.
|
|
104
|
+
5. Treat `caspian.config.json` as the single source of truth for optional features. Use feature-specific docs only when the matching flag is enabled. If a feature is disabled and the user wants it, ask first, then update `caspian.config.json` and follow the update workflow in `commands.md`.
|
|
105
|
+
6. If the task touches `main.py` or `.venv/Lib/site-packages/casp/**`, read `core-runtime-map.md` to jump to the controlling runtime file and the matching feature doc.
|
|
106
|
+
7. If the task names a PulsePoint feature or directive, read `pulsepoint-runtime-map.md` for the fastest feature-to-runtime lookup, then read `pulsepoint.md` for authoring rules.
|
|
107
|
+
8. After the feature is confirmed, inspect the actual project files that decide behavior, such as `package.json`, `main.py`, `src/app/**`, `src/lib/**`, `settings/**`, `prisma/**`, and the installed `casp` runtime.
|
|
108
|
+
9. Use `file-conventions.md` for quick decisions about `index.html`, `index.py`, `layout.html`, `layout.py`, `loading.html`, `not-found.html`, and `error.html`; use `commands.md` for scaffold and update workflows, `project-structure.md` for placement decisions, and the feature docs such as `mcp.md`, `database.md`, `auth.md`, `fetch-data.md`, and `file-uploads.md` for task-specific guidance.
|
|
109
|
+
10. Prefer packaged Caspian docs before upstream documentation when generating code, commands, or migration guidance.
|
|
110
|
+
11. Use `ai-validation-checklist.md` when you want to verify that the docs lead AI to the correct files and behavior checkpoints.
|
|
111
|
+
12. Keep `index.md` and cross-links aligned so AI can quickly discover the right doc.
|
|
110
112
|
|
|
111
113
|
## Maintenance
|
|
112
114
|
|
package/dist/docs/metadata.md
CHANGED
|
@@ -12,7 +12,7 @@ related:
|
|
|
12
12
|
|
|
13
13
|
This page explains how Caspian handles document metadata, SEO fields, and social sharing tags.
|
|
14
14
|
|
|
15
|
-
At render time, Caspian resolves metadata through the layout engine and exposes the merged result to templates as `
|
|
15
|
+
At render time, Caspian resolves metadata through the layout engine and exposes the merged result to templates as `{{ metadata.* }}`.
|
|
16
16
|
|
|
17
17
|
## Source Of Truth
|
|
18
18
|
|
|
@@ -20,7 +20,7 @@ At render time, Caspian resolves metadata through the layout engine and exposes
|
|
|
20
20
|
- Route registration and runtime metadata collection are controlled by `main.py`.
|
|
21
21
|
- Shared metadata defaults belong in `src/app/**/layout.py`.
|
|
22
22
|
- Page-specific static or dynamic metadata belongs in `src/app/**/index.py`.
|
|
23
|
-
- Rendered metadata is consumed from layout templates through `
|
|
23
|
+
- Rendered metadata is consumed from layout templates through `{{ metadata.* }}`.
|
|
24
24
|
|
|
25
25
|
## Overview
|
|
26
26
|
|
|
@@ -168,10 +168,10 @@ Resolved metadata is passed into layout rendering as a `metadata` object.
|
|
|
168
168
|
Example:
|
|
169
169
|
|
|
170
170
|
```html
|
|
171
|
-
<title>
|
|
172
|
-
<meta name="description" content="
|
|
173
|
-
<meta property="og:image" content="
|
|
174
|
-
<meta name="twitter:card" content="
|
|
171
|
+
<title>{{ metadata.title }}</title>
|
|
172
|
+
<meta name="description" content="{{ metadata.description }}" />
|
|
173
|
+
<meta property="og:image" content="{{ metadata['og:image'] }}" />
|
|
174
|
+
<meta name="twitter:card" content="{{ metadata['twitter:card'] }}" />
|
|
175
175
|
```
|
|
176
176
|
|
|
177
177
|
Use bracket access for `extra` keys that contain characters such as `:`.
|
|
@@ -196,9 +196,9 @@ Use stable, publicly reachable image paths for social cards so crawlers can fetc
|
|
|
196
196
|
|
|
197
197
|
Keep visual layout data and SEO metadata separate.
|
|
198
198
|
|
|
199
|
-
- Values returned from `layout()` are exposed as `
|
|
200
|
-
- The second dict returned from `page()` as `(page_html, layout_props_dict)` is also exposed to wrapping layouts as `
|
|
201
|
-
- SEO values are exposed as `
|
|
199
|
+
- Values returned from `layout()` are exposed as `{{ layout.* }}`.
|
|
200
|
+
- The second dict returned from `page()` as `(page_html, layout_props_dict)` is also exposed to wrapping layouts as `{{ layout.* }}`.
|
|
201
|
+
- SEO values are exposed as `{{ metadata.* }}`.
|
|
202
202
|
- Do not return `title` or `description` from `layout()` expecting SEO changes.
|
|
203
203
|
- The layout engine explicitly strips `title` and `description` from layout props to avoid mixing visual props with metadata.
|
|
204
204
|
|
|
@@ -223,5 +223,5 @@ If an AI agent is deciding where to put SEO fields, apply these rules first.
|
|
|
223
223
|
- If a single route only needs to tweak a wrapping layout, return `(render_page(__file__, ...), {"dashboard_body_class": ...})` from `page()` instead of moving that prop into metadata.
|
|
224
224
|
- Use `extra` for Open Graph and Twitter card tags.
|
|
225
225
|
- Access `extra` values in templates with bracket syntax such as `metadata['og:image']`.
|
|
226
|
-
- Keep `layout()` return data in `
|
|
226
|
+
- Keep `layout()` return data in `{{ layout.* }}` and keep SEO fields in `Metadata(...)`.
|
|
227
227
|
- Check [routing.md](./routing.md) when deciding whether metadata belongs in a layout or a specific route folder.
|
|
@@ -106,7 +106,7 @@ This is the main application area. It contains route files, templates, styles, a
|
|
|
106
106
|
|
|
107
107
|
This directory handles file-based routing. Route templates and route-specific backend logic live here.
|
|
108
108
|
|
|
109
|
-
For any route that renders UI, keep that markup in `src/app/**/index.html`. If the route is UI-only, `index.html` alone is enough. Add `src/app/**/index.py` only as a companion when the same route needs metadata, `page()`, `@rpc()` actions, auth checks, caching, redirects, or other server-side behavior. Keep shared wrappers in `layout.html` and use `layout.py` only for shared
|
|
109
|
+
For any route that renders UI, keep that markup in `src/app/**/index.html`. If the route is UI-only, `index.html` alone is enough. Add `src/app/**/index.py` only as a companion when the same route needs metadata, `page()`, `@rpc()` actions, auth checks, caching, redirects, or other server-side behavior. Keep shared wrappers in `layout.html` and use `layout.py` only for shared props or metadata. Use a lone `index.py` only for non-visual routes such as redirect-only or action-only handlers.
|
|
110
110
|
|
|
111
111
|
When a folder represents a section with child routes, such as `dashboard`, `account`, `settings`, or `docs`, create `layout.html` in that folder and let the child routes live beneath it. See [routing.md](./routing.md) for the canonical section layout pattern.
|
|
112
112
|
|
|
@@ -261,7 +261,7 @@ Keep visible wrapper markup in `layout.html`, not in `layout.py`.
|
|
|
261
261
|
|
|
262
262
|
### `src/app/layout.py`
|
|
263
263
|
|
|
264
|
-
The backend companion for a layout. Use this file for shared
|
|
264
|
+
The backend companion for a layout. Use this file for shared props, metadata defaults, and other server-side preparation for the sibling `layout.html`.
|
|
265
265
|
|
|
266
266
|
Do not store layout HTML in `layout.py`. Keep the authored wrapper in `layout.html` and let `layout.py` return props or metadata.
|
|
267
267
|
|
|
@@ -1,129 +1,132 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: PulsePoint Runtime Map
|
|
3
|
-
description: Use this page when AI needs a fast feature-to-runtime lookup for PulsePoint behavior before editing `src/app/**`, component templates, or `public/js/pp-reactive-v2.js`.
|
|
4
|
-
related:
|
|
5
|
-
title: Related docs
|
|
6
|
-
description: Use the PulsePoint guide for authoring rules, the routing and component guides for template placement, and the core runtime map when Python-side transforms are involved.
|
|
7
|
-
links:
|
|
8
|
-
- /docs/pulsepoint
|
|
9
|
-
- /docs/routing
|
|
10
|
-
- /docs/components
|
|
11
|
-
- /docs/fetch-data
|
|
12
|
-
- /docs/core-runtime-map
|
|
13
|
-
- /docs/index
|
|
14
|
-
---
|
|
15
|
-
|
|
16
|
-
This page is the quick AI routing layer for PulsePoint core features.
|
|
17
|
-
|
|
18
|
-
Use [pulsepoint.md](./pulsepoint.md) for the full authoring contract. Use this file when you need to jump from a feature name, directive, or runtime symptom to the files and behavior checkpoints that matter.
|
|
19
|
-
|
|
20
|
-
## Source Of Truth
|
|
21
|
-
|
|
22
|
-
- `public/js/pp-reactive-v2.js` is the shipped browser runtime.
|
|
23
|
-
- `main.py` owns the final render pipeline that calls `transform_components(...)` and `transform_scripts(...)`.
|
|
24
|
-
- `.venv/Lib/site-packages/casp/components_compiler.py` injects `pp-component` after component expansion and validates the single-root contract.
|
|
25
|
-
- `.venv/Lib/site-packages/casp/scripts_type.py` rewrites authored body `<script>` tags to `type="text/pp"`.
|
|
26
|
-
- `.venv/Lib/site-packages/casp/html_attrs.py` owns Python-side class and attribute helpers such as `merge_classes(...)`.
|
|
27
|
-
|
|
28
|
-
If an inspected browser DOM disagrees with authored template source, remember that the runtime DOM includes framework-managed output. Do not copy runtime-only attributes back into authored templates.
|
|
29
|
-
|
|
30
|
-
## Feature Map
|
|
31
|
-
|
|
32
|
-
| PulsePoint feature | Authoring surface | Runtime owner | Verify before changing |
|
|
33
|
-
| --- | --- | --- | --- |
|
|
34
|
-
| Component roots | `src/app/**/index.html`, `layout.html`, component `.html` files | `components_compiler.py`, `pp-reactive-v2.js` | one authored root, final expanded root receives one `pp-component`, no sibling scripts |
|
|
35
|
-
| Component scripts | plain `<script>` inside the authored root | `scripts_type.py`, `pp-reactive-v2.js` | authored scripts are plain, runtime scripts become `type="text/pp"`, one owned script per root |
|
|
36
|
-
| Template expressions | text and attributes with `{...}` | `pp-reactive-v2.js` | top-level script bindings are exported, nested bindings are not assumed |
|
|
37
|
-
| State | `pp.state(initial)` | `pp-reactive-v2.js` | setters accept values or updater functions, state belongs to the component instance |
|
|
38
|
-
| Effects | `pp.effect(...)`, `pp.layoutEffect(...)` | `pp-reactive-v2.js` | callbacks may return cleanup functions, promises are not awaited |
|
|
39
|
-
| Refs | `pp.ref(...)`, `pp-ref` | `pp-reactive-v2.js` | generated ref internals are runtime-managed; do not author `data-pp-ref` |
|
|
40
|
-
| Context | `pp.createContext(...)`, `<Context.Provider>`, `pp.context(token)` | `pp-reactive-v2.js` | ancestry is logical component ancestry; do not invent `pp-context` or `pp.provideContext` |
|
|
41
|
-
| Portals | `pp.portal(ref, target?)` | `pp-reactive-v2.js` | context should preserve logical ancestry through the registry |
|
|
42
|
-
| Lists | `<template pp-for="item in items">` | `pp-reactive-v2.js` | `pp-for` belongs on `<template>`, use plain `key`, not `pp-key` |
|
|
43
|
-
| Events | native `onclick`, `oninput`, `onsubmit` | `pp-reactive-v2.js` | event scope exposes `event`, `e`, `$event`, `target`, `currentTarget`, and `el` |
|
|
44
|
-
| RPC | `pp.rpc(...)` in scripts, `@rpc()` in Python | `pp-reactive-v2.js`, `casp/rpc.py` | use `pp.rpc`, not legacy `pp.fetchFunction`; protected actions use `@rpc(require_auth=True)` |
|
|
45
|
-
| Upload progress | `pp.rpc(..., { onUploadProgress })` | `pp-reactive-v2.js`, `casp/rpc.py` | XHR path is used for progress callbacks; replace state from returned payload |
|
|
46
|
-
| Streaming | `pp.rpc(..., { onStream })` | `pp-reactive-v2.js`, `casp/rpc.py`, `casp/streaming.py` | server generators become SSE responses |
|
|
47
|
-
| SPA navigation | `body[pp-spa="true"]`, links | `pp-reactive-v2.js`, `main.py` | same-origin eligible links intercept; root-layout mismatches hard reload |
|
|
48
|
-
| Scroll restoration | `pp-reset-scroll="true"` | `pp-reactive-v2.js` | push navigation resets window; mark only content panes that should reset |
|
|
49
|
-
| Tailwind merge | `{twMerge(...)}`, Python `merge_classes(...)` | `html_attrs.py`, `pp-reactive-v2.js` | Python emits frontend-ready expressions when Tailwind is enabled |
|
|
50
|
-
|
|
51
|
-
## AI Decision Rules
|
|
52
|
-
|
|
53
|
-
- Read `caspian.config.json` first when the task depends on Tailwind, generated files, or optional project features.
|
|
54
|
-
- Read [routing.md](./routing.md) before adding route or layout templates.
|
|
55
|
-
- Read [components.md](./components.md) before adding reusable Python components or `x-*` imports.
|
|
56
|
-
- Read [fetch-data.md](./fetch-data.md) before adding browser-triggered backend work.
|
|
57
|
-
- Use this map when the task names a PulsePoint feature and you need the owning runtime file quickly.
|
|
58
|
-
- Verify implemented behavior in `public/js/pp-reactive-v2.js` before adding new PulsePoint API claims.
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
-
|
|
64
|
-
-
|
|
65
|
-
-
|
|
66
|
-
- Use `
|
|
67
|
-
- Use `
|
|
68
|
-
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
<
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
1
|
+
---
|
|
2
|
+
title: PulsePoint Runtime Map
|
|
3
|
+
description: Use this page when AI needs a fast feature-to-runtime lookup for PulsePoint behavior before editing `src/app/**`, component templates, or `public/js/pp-reactive-v2.js`.
|
|
4
|
+
related:
|
|
5
|
+
title: Related docs
|
|
6
|
+
description: Use the PulsePoint guide for authoring rules, the routing and component guides for template placement, and the core runtime map when Python-side transforms are involved.
|
|
7
|
+
links:
|
|
8
|
+
- /docs/pulsepoint
|
|
9
|
+
- /docs/routing
|
|
10
|
+
- /docs/components
|
|
11
|
+
- /docs/fetch-data
|
|
12
|
+
- /docs/core-runtime-map
|
|
13
|
+
- /docs/index
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
This page is the quick AI routing layer for PulsePoint core features.
|
|
17
|
+
|
|
18
|
+
Use [pulsepoint.md](./pulsepoint.md) for the full authoring contract. Use this file when you need to jump from a feature name, directive, or runtime symptom to the files and behavior checkpoints that matter.
|
|
19
|
+
|
|
20
|
+
## Source Of Truth
|
|
21
|
+
|
|
22
|
+
- `public/js/pp-reactive-v2.js` is the shipped browser runtime.
|
|
23
|
+
- `main.py` owns the final render pipeline that calls `transform_components(...)` and `transform_scripts(...)`.
|
|
24
|
+
- `.venv/Lib/site-packages/casp/components_compiler.py` injects `pp-component` after component expansion and validates the single-root contract.
|
|
25
|
+
- `.venv/Lib/site-packages/casp/scripts_type.py` rewrites authored body `<script>` tags to `type="text/pp"`.
|
|
26
|
+
- `.venv/Lib/site-packages/casp/html_attrs.py` owns Python-side class and attribute helpers such as `merge_classes(...)`.
|
|
27
|
+
|
|
28
|
+
If an inspected browser DOM disagrees with authored template source, remember that the runtime DOM includes framework-managed output. Do not copy runtime-only attributes back into authored templates.
|
|
29
|
+
|
|
30
|
+
## Feature Map
|
|
31
|
+
|
|
32
|
+
| PulsePoint feature | Authoring surface | Runtime owner | Verify before changing |
|
|
33
|
+
| --- | --- | --- | --- |
|
|
34
|
+
| Component roots | `src/app/**/index.html`, `layout.html`, component `.html` files | `components_compiler.py`, `pp-reactive-v2.js` | one authored root, final expanded root receives one `pp-component`, no sibling scripts |
|
|
35
|
+
| Component scripts | plain `<script>` inside the authored root | `scripts_type.py`, `pp-reactive-v2.js` | authored scripts are plain, runtime scripts become `type="text/pp"`, one owned script per root |
|
|
36
|
+
| Template expressions | text and attributes with `{...}` | `pp-reactive-v2.js` | top-level script bindings are exported, nested bindings are not assumed |
|
|
37
|
+
| State | `pp.state(initial)` | `pp-reactive-v2.js` | setters accept values or updater functions, state belongs to the component instance |
|
|
38
|
+
| Effects | `pp.effect(...)`, `pp.layoutEffect(...)` | `pp-reactive-v2.js` | callbacks may return cleanup functions, promises are not awaited |
|
|
39
|
+
| Refs | `pp.ref(...)`, `pp-ref` | `pp-reactive-v2.js` | generated ref internals are runtime-managed; do not author `data-pp-ref` |
|
|
40
|
+
| Context | `pp.createContext(...)`, `<Context.Provider>`, `pp.context(token)` | `pp-reactive-v2.js` | ancestry is logical component ancestry; do not invent `pp-context` or `pp.provideContext` |
|
|
41
|
+
| Portals | `pp.portal(ref, target?)` | `pp-reactive-v2.js` | context should preserve logical ancestry through the registry |
|
|
42
|
+
| Lists | `<template pp-for="item in items">` | `pp-reactive-v2.js` | `pp-for` belongs on `<template>`, use plain `key`, not `pp-key` |
|
|
43
|
+
| Events | native `onclick`, `oninput`, `onchange`, `onsubmit` | `pp-reactive-v2.js` | first-party events belong in `on*` attributes; event scope exposes `event`, `e`, `$event`, `target`, `currentTarget`, and `el`; avoid id-driven `querySelector`/`addEventListener` for normal UI |
|
|
44
|
+
| RPC | `pp.rpc(...)` in scripts, `@rpc()` in Python | `pp-reactive-v2.js`, `casp/rpc.py` | use `pp.rpc`, not legacy `pp.fetchFunction`; protected actions use `@rpc(require_auth=True)` |
|
|
45
|
+
| Upload progress | `pp.rpc(..., { onUploadProgress })` | `pp-reactive-v2.js`, `casp/rpc.py` | XHR path is used for progress callbacks; replace state from returned payload |
|
|
46
|
+
| Streaming | `pp.rpc(..., { onStream })` | `pp-reactive-v2.js`, `casp/rpc.py`, `casp/streaming.py` | server generators become SSE responses |
|
|
47
|
+
| SPA navigation | `body[pp-spa="true"]`, links | `pp-reactive-v2.js`, `main.py` | same-origin eligible links intercept; root-layout mismatches hard reload |
|
|
48
|
+
| Scroll restoration | `pp-reset-scroll="true"` | `pp-reactive-v2.js` | push navigation resets window; mark only content panes that should reset |
|
|
49
|
+
| Tailwind merge | `{twMerge(...)}`, Python `merge_classes(...)` | `html_attrs.py`, `pp-reactive-v2.js` | Python emits frontend-ready expressions when Tailwind is enabled |
|
|
50
|
+
|
|
51
|
+
## AI Decision Rules
|
|
52
|
+
|
|
53
|
+
- Read `caspian.config.json` first when the task depends on Tailwind, generated files, or optional project features.
|
|
54
|
+
- Read [routing.md](./routing.md) before adding route or layout templates.
|
|
55
|
+
- Read [components.md](./components.md) before adding reusable Python components or `x-*` imports.
|
|
56
|
+
- Read [fetch-data.md](./fetch-data.md) before adding browser-triggered backend work.
|
|
57
|
+
- Use this map when the task names a PulsePoint feature and you need the owning runtime file quickly.
|
|
58
|
+
- Verify implemented behavior in `public/js/pp-reactive-v2.js` before adding new PulsePoint API claims.
|
|
59
|
+
- If an interaction is normal first-party HTML behavior, route it through PulsePoint before considering standard DOM scripting.
|
|
60
|
+
|
|
61
|
+
## Copy-Safe Authoring Rules
|
|
62
|
+
|
|
63
|
+
- Author one root element or one imported `x-*` root per route, layout, or component template.
|
|
64
|
+
- Keep any owned plain `<script>` inside that same root.
|
|
65
|
+
- Do not handwrite `pp-component`, `type="text/pp"`, `data-pp-ref`, `pp-owner`, `pp-event-owner`, or other runtime-managed attributes.
|
|
66
|
+
- Use `pp.rpc(...)` for current browser-to-server calls.
|
|
67
|
+
- Use native `on*` attributes for button clicks, form submits, input changes, filters, toggles, and menus instead of adding ids and manual listeners.
|
|
68
|
+
- Use `Context.Provider` and `pp.context(...)` for context.
|
|
69
|
+
- Use `pp-for` only on `<template>` and plain `key` for keyed lists.
|
|
70
|
+
- Prefer PulsePoint state and directives over manual `innerHTML` repainting.
|
|
71
|
+
- Keep direct DOM APIs inside `pp.ref(...)` plus `pp.effect(...)` only when a third-party or browser API integration actually requires them.
|
|
72
|
+
|
|
73
|
+
## Compact Examples
|
|
74
|
+
|
|
75
|
+
Context provider:
|
|
76
|
+
|
|
77
|
+
```html
|
|
78
|
+
<section>
|
|
79
|
+
<script>
|
|
80
|
+
const ThemeContext = pp.createContext("light");
|
|
81
|
+
const [theme, setTheme] = pp.state("dark");
|
|
82
|
+
</script>
|
|
83
|
+
|
|
84
|
+
<ThemeContext.Provider value="{theme}">
|
|
85
|
+
<button onclick="setTheme(theme === 'dark' ? 'light' : 'dark')">
|
|
86
|
+
Theme: {theme}
|
|
87
|
+
</button>
|
|
88
|
+
</ThemeContext.Provider>
|
|
89
|
+
</section>
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Upload progress:
|
|
93
|
+
|
|
94
|
+
```html
|
|
95
|
+
<section>
|
|
96
|
+
<input type="file" onchange="{uploadFile(event.target.files?.[0])}" />
|
|
97
|
+
<p>{progress}%</p>
|
|
98
|
+
|
|
99
|
+
<script>
|
|
100
|
+
const [progress, setProgress] = pp.state(0);
|
|
101
|
+
|
|
102
|
+
async function uploadFile(file) {
|
|
103
|
+
if (!file) return;
|
|
104
|
+
|
|
105
|
+
await pp.rpc("upload_asset", { file }, {
|
|
106
|
+
onUploadProgress: (event) => setProgress(event.percentage ?? 0),
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
</script>
|
|
110
|
+
</section>
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Grouped shell scroll reset:
|
|
114
|
+
|
|
115
|
+
```html
|
|
116
|
+
<section class="dashboard-shell">
|
|
114
117
|
<aside class="dashboard-sidebar">...</aside>
|
|
115
118
|
<main class="dashboard-content" pp-reset-scroll="true">
|
|
116
|
-
|
|
119
|
+
<slot />
|
|
117
120
|
</main>
|
|
118
121
|
</section>
|
|
119
|
-
```
|
|
120
|
-
|
|
121
|
-
## Verification Prompts
|
|
122
|
-
|
|
123
|
-
Use these prompts after docs or runtime changes to confirm AI can route correctly:
|
|
124
|
-
|
|
125
|
-
- "Create an interactive filter in a Caspian route template."
|
|
126
|
-
- "Explain why authored scripts are plain `<script>` but browser output shows `type=\"text/pp\"`."
|
|
127
|
-
- "Debug a dashboard sidebar losing scroll during child-route navigation."
|
|
128
|
-
- "Add an upload widget with progress and a reactive file list."
|
|
129
|
-
- "Use context to share theme state between parent and child components."
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## Verification Prompts
|
|
125
|
+
|
|
126
|
+
Use these prompts after docs or runtime changes to confirm AI can route correctly:
|
|
127
|
+
|
|
128
|
+
- "Create an interactive filter in a Caspian route template."
|
|
129
|
+
- "Explain why authored scripts are plain `<script>` but browser output shows `type=\"text/pp\"`."
|
|
130
|
+
- "Debug a dashboard sidebar losing scroll during child-route navigation."
|
|
131
|
+
- "Add an upload widget with progress and a reactive file list."
|
|
132
|
+
- "Use context to share theme state between parent and child components."
|
package/dist/docs/pulsepoint.md
CHANGED
|
@@ -48,16 +48,92 @@ Use [core-runtime-map.md](./core-runtime-map.md) when the controlling runtime fi
|
|
|
48
48
|
|
|
49
49
|
Use [pulsepoint-runtime-map.md](./pulsepoint-runtime-map.md) when the task names a specific PulsePoint feature or directive and you need a quick feature-to-runtime lookup before reading the full guide.
|
|
50
50
|
|
|
51
|
-
## Default Frontend Rule
|
|
52
|
-
|
|
53
|
-
When a Caspian page needs reactive browser behavior, use PulsePoint.
|
|
54
|
-
|
|
55
|
-
- Use PulsePoint component roots, scripts, directives, and runtime helpers for interactive UI.
|
|
56
|
-
- Use PulsePoint state, effects, refs, and template directives as the default reactivity model in authored Caspian templates.
|
|
57
|
-
-
|
|
58
|
-
-
|
|
59
|
-
-
|
|
60
|
-
-
|
|
51
|
+
## Default Frontend Rule
|
|
52
|
+
|
|
53
|
+
When a Caspian page needs reactive browser behavior, use PulsePoint.
|
|
54
|
+
|
|
55
|
+
- Use PulsePoint component roots, scripts, directives, and runtime helpers for interactive UI.
|
|
56
|
+
- Use PulsePoint state, effects, refs, and template directives as the default reactivity model in authored Caspian templates.
|
|
57
|
+
- Bind first-party events in the HTML with PulsePoint-handled native `on*` attributes such as `onclick`, `oninput`, `onchange`, and `onsubmit`.
|
|
58
|
+
- When the browser needs CRUD operations or follow-up reads from the backend, call `pp.rpc()` from PulsePoint code and back it with route or backend `@rpc()` actions.
|
|
59
|
+
- Keep server-rendered HTML plus PulsePoint enhancement as the baseline architecture.
|
|
60
|
+
- For dashboards, admin areas, account sections, docs sections, and other grouped subtrees, keep shared shell markup and shared PulsePoint behavior in the parent folder's `layout.html`, then keep child-route PulsePoint state local to each `index.html`. Follow the same mental model as the Next.js App Router.
|
|
61
|
+
- Only introduce another frontend runtime when the user explicitly asks for it or the project already depends on one.
|
|
62
|
+
|
|
63
|
+
## PulsePoint-First Events And Reactivity
|
|
64
|
+
|
|
65
|
+
For first-party Caspian HTML, PulsePoint is not a later enhancement after ordinary JavaScript. It is the event, state, and reactivity model AI should use from the start.
|
|
66
|
+
|
|
67
|
+
Default to this workflow:
|
|
68
|
+
|
|
69
|
+
- Put the button, form, input, toggle, menu, filter, upload control, or list markup directly in the route, layout, or component HTML template.
|
|
70
|
+
- Bind events with native `on*` attributes handled by PulsePoint, for example `onclick="save()"`, `oninput="setQuery(event.target.value)"`, or `onsubmit="{submitForm(event)}"`.
|
|
71
|
+
- This is the PulsePoint `onClick`/native event-attribute model. Authored examples use lowercase HTML spellings such as `onclick` because browser HTML normalizes attribute names, but the important rule is to bind the event in the template instead of wiring it later with DOM selectors.
|
|
72
|
+
- Keep reactive values in `pp.state(...)`.
|
|
73
|
+
- Render conditional text, classes, attributes, lists, and styles with template expressions, `pp-for`, `pp-style`, `pp-spread`, and other PulsePoint-supported template features.
|
|
74
|
+
- Use `pp.ref(...)` and `pp-ref` when a real element reference is needed.
|
|
75
|
+
- Use `pp.effect(...)` or `pp.layoutEffect(...)` for lifecycle work that must happen after render.
|
|
76
|
+
- Use `pp.rpc(...)` for browser-triggered backend reads and writes.
|
|
77
|
+
|
|
78
|
+
Avoid building a parallel JavaScript layer for normal UI behavior:
|
|
79
|
+
|
|
80
|
+
- Do not add ids only so a script can find elements with `document.querySelector(...)` or `document.getElementById(...)`.
|
|
81
|
+
- Do not use `data-*` attributes as a private client state system when PulsePoint state or props should own the data.
|
|
82
|
+
- Do not bind normal first-party clicks, input changes, submits, filters, menus, or toggles with `addEventListener(...)`.
|
|
83
|
+
- Do not repaint first-party lists or panels with manual `innerHTML` writes when `pp.state(...)` plus `pp-for` can express the same UI.
|
|
84
|
+
- Do not create a custom client-side store, event bus, or hydration routine for behavior that belongs in a PulsePoint component script.
|
|
85
|
+
|
|
86
|
+
Use direct DOM APIs only as a narrow escape hatch: third-party widgets, browser APIs that require imperative access, measurements, focus, media, canvas, or behavior the current PulsePoint runtime cannot express declaratively. Even then, keep the imperative code inside the owning PulsePoint component script, usually through `pp.ref(...)` plus `pp.effect(...)`, so PulsePoint still owns the component's state, cleanup, and event flow.
|
|
87
|
+
|
|
88
|
+
Preferred authored pattern:
|
|
89
|
+
|
|
90
|
+
```html
|
|
91
|
+
<section>
|
|
92
|
+
<input value="{query}" oninput="setQuery(event.target.value)" />
|
|
93
|
+
<button onclick="clearSearch()" disabled="{query.length === 0}">Clear</button>
|
|
94
|
+
|
|
95
|
+
<ul>
|
|
96
|
+
<template pp-for="item in filteredItems">
|
|
97
|
+
<li key="{item.id}">{item.label}</li>
|
|
98
|
+
</template>
|
|
99
|
+
</ul>
|
|
100
|
+
|
|
101
|
+
<script>
|
|
102
|
+
const [query, setQuery] = pp.state("");
|
|
103
|
+
const items = pp.props.items ?? [];
|
|
104
|
+
const filteredItems = items.filter((item) =>
|
|
105
|
+
item.label.toLowerCase().includes(query.toLowerCase())
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
function clearSearch() {
|
|
109
|
+
setQuery("");
|
|
110
|
+
}
|
|
111
|
+
</script>
|
|
112
|
+
</section>
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Avoid this first-party pattern:
|
|
116
|
+
|
|
117
|
+
```html
|
|
118
|
+
<section>
|
|
119
|
+
<input id="search" />
|
|
120
|
+
<button id="clear-search">Clear</button>
|
|
121
|
+
<ul id="results"></ul>
|
|
122
|
+
|
|
123
|
+
<script>
|
|
124
|
+
const input = document.querySelector("#search");
|
|
125
|
+
const button = document.querySelector("#clear-search");
|
|
126
|
+
const results = document.querySelector("#results");
|
|
127
|
+
|
|
128
|
+
button.addEventListener("click", () => {
|
|
129
|
+
input.value = "";
|
|
130
|
+
results.innerHTML = "";
|
|
131
|
+
});
|
|
132
|
+
</script>
|
|
133
|
+
</section>
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
That second shape recreates a separate event and rendering system inside a Caspian component. It is harder to maintain because it bypasses PulsePoint's rerender, event rebinding, refs, cleanup, and backend RPC conventions.
|
|
61
137
|
|
|
62
138
|
## Authoring Model
|
|
63
139
|
|
|
@@ -387,14 +463,16 @@ Example:
|
|
|
387
463
|
</div>
|
|
388
464
|
```
|
|
389
465
|
|
|
390
|
-
## Events
|
|
391
|
-
|
|
392
|
-
- Use native `on*` attributes such as `onclick`, `oninput`, and `onsubmit
|
|
393
|
-
-
|
|
394
|
-
-
|
|
395
|
-
-
|
|
396
|
-
-
|
|
397
|
-
-
|
|
466
|
+
## Events
|
|
467
|
+
|
|
468
|
+
- Use native `on*` attributes such as `onclick`, `oninput`, `onchange`, and `onsubmit` for first-party events.
|
|
469
|
+
- Treat this as the HTML form of `onClick`-style PulsePoint event binding. Prefer lowercase examples in authored HTML because the browser normalizes attribute names.
|
|
470
|
+
- Event values may be raw code or wrapped in `{...}`.
|
|
471
|
+
- The runtime injects `event`, `e`, `$event`, `target`, `currentTarget`, and `el`.
|
|
472
|
+
- Do not use hyphenated event attrs like `on-click`.
|
|
473
|
+
- Event attributes are removed from the live DOM after binding and rebound after DOM morphing.
|
|
474
|
+
- Owned template/event-owner internals are runtime-managed. Do not author them directly.
|
|
475
|
+
- Do not replace normal PulsePoint event attributes with id-driven `querySelector(...)` plus `addEventListener(...)` wiring. If an imperative listener is unavoidable for an integration, attach and clean it up from `pp.effect(...)`.
|
|
398
476
|
|
|
399
477
|
## SPA, loading, and navigation helpers
|
|
400
478
|
|
|
@@ -448,15 +526,17 @@ These are runtime details.
|
|
|
448
526
|
|
|
449
527
|
Use these rules when generating or editing PulsePoint runtime code:
|
|
450
528
|
|
|
451
|
-
- Treat PulsePoint as the default reactive frontend for Caspian app code.
|
|
452
|
-
-
|
|
529
|
+
- Treat PulsePoint as the default reactive frontend for Caspian app code.
|
|
530
|
+
- For first-party HTML interactions, use PulsePoint `on*` event attributes, state, refs, effects, directives, and `pp.rpc()` before reaching for DOM APIs.
|
|
531
|
+
- Treat `pp.rpc()` as the default browser-to-server path for CRUD operations and interactive backend reads.
|
|
453
532
|
- Use `public/js/pp-reactive-v2.js` as the shipped runtime contract AI should follow.
|
|
454
533
|
- Keep `main.py` in view because it injects the runtime-facing attributes and rewrites authored scripts before the browser sees them.
|
|
455
534
|
- If a development-only source tree exists behind the shipped runtime, treat it as optional implementation detail rather than something generated apps are guaranteed to contain.
|
|
456
535
|
- In authored Caspian templates, do not handwrite `pp-component` or `type="text/pp"`; let the render pipeline inject them.
|
|
457
536
|
- For grouped subtrees, follow the section layout pattern in [routing.md](./routing.md), keep the shared interactive shell in the parent folder's `layout.html`, and keep route-specific PulsePoint code in each child `index.html`.
|
|
458
537
|
- For grouped shells with independent shell and content scrolling, put `pp-reset-scroll="true"` on the content pane rather than the whole shell when only the page content should reset between child-route navigations.
|
|
459
|
-
- Prefer PulsePoint state and template directives over manual DOM mutation for reactive updates.
|
|
538
|
+
- Prefer PulsePoint state and template directives over manual DOM mutation for reactive updates.
|
|
539
|
+
- Avoid generating ids, `data-*` state, `querySelector`, `getElementById`, `addEventListener`, manual `innerHTML`, or custom event buses for normal Caspian UI behavior.
|
|
460
540
|
- If you are explicitly editing raw runtime HTML or internals, keep `pp-component` unique per live instance.
|
|
461
541
|
- In authored templates, use a plain `<script>` inside the root. In runtime HTML, the owned script appears as `script[type="text/pp"]`.
|
|
462
542
|
- Keep template-facing variables at top level.
|
|
@@ -475,8 +555,9 @@ Use these rules when generating or editing PulsePoint runtime code:
|
|
|
475
555
|
|
|
476
556
|
Do not generate these unless the current source explicitly adds support:
|
|
477
557
|
|
|
478
|
-
- React, Vue, Svelte, Alpine, HTMX, or JSX-first patterns as the default Caspian frontend approach
|
|
479
|
-
-
|
|
558
|
+
- React, Vue, Svelte, Alpine, HTMX, or JSX-first patterns as the default Caspian frontend approach
|
|
559
|
+
- standard DOM scripting as the default first-party interaction model, including id/data-attribute driven `querySelector(...)`, `addEventListener(...)`, or manual `innerHTML` rendering for normal buttons, forms, filters, toggles, uploads, and reactive lists
|
|
560
|
+
- `pp-context`
|
|
480
561
|
- `pp-key`
|
|
481
562
|
- `data-pp-ref`
|
|
482
563
|
- `pp-context-provider`
|
package/dist/docs/routing.md
CHANGED
|
@@ -36,6 +36,7 @@ Start with these rules:
|
|
|
36
36
|
- Use `layout.py` when a layout needs shared props or metadata before rendering. The `layout()` function may be synchronous or async.
|
|
37
37
|
- Keep visible route and layout markup in `index.html` and `layout.html`. Treat `index.py` and `layout.py` as backend companions, not as places to author visible HTML.
|
|
38
38
|
- Treat every authored route and layout template like a React component body: it must have exactly one top-level parent HTML element or one imported `x-*` root, and any owned plain `<script>` must live inside that same root.
|
|
39
|
+
- For route and layout interactivity, use PulsePoint in the authored HTML first: native `on*` event attributes, `pp.state(...)`, refs, effects, directives, and `pp.rpc()`. Do not create standard JavaScript event systems with ids, `data-*` state, `querySelector`, `addEventListener`, or manual `innerHTML` for normal first-party UI.
|
|
39
40
|
|
|
40
41
|
## Hard Template Invariant
|
|
41
42
|
|
|
@@ -183,6 +184,8 @@ Place those import comments at the top of the file, above the authored root elem
|
|
|
183
184
|
|
|
184
185
|
Route templates follow the same authored-vs-runtime contract documented in [pulsepoint.md](./pulsepoint.md) and the same single-root discipline documented in [components.md](./components.md): keep one authored parent node, keep any `<!-- @import ... -->` directives above that root, use a plain `<script>` inside that root when needed, and do not handwrite `pp-component` or `type="text/pp"`. That root may be a native HTML element or a single imported `x-*` component tag, but after expansion it must resolve to one final HTML root.
|
|
185
186
|
|
|
187
|
+
When a route needs button clicks, form submits, input changes, filters, tabs, menus, uploads, polling, or reactive list updates, author those interactions as PulsePoint behavior in `index.html`. Use `onclick`, `oninput`, `onchange`, `onsubmit`, `pp.state(...)`, `pp-for`, refs, effects, and `pp.rpc(...)` instead of starting with `id` attributes plus `document.querySelector(...)` or `addEventListener(...)`.
|
|
188
|
+
|
|
186
189
|
For AI-generated route templates, treat `src/app/**/index.html` the same way you would a React component body: return one parent node that contains the entire route markup. This includes any owned script.
|
|
187
190
|
|
|
188
191
|
Good:
|
|
@@ -257,16 +260,16 @@ Use this pattern when the route needs to fetch data, compute metadata, or do oth
|
|
|
257
260
|
`page()` may also return a 2-item tuple: `(page_html, layout_props_dict)`.
|
|
258
261
|
|
|
259
262
|
- The first item is the rendered page HTML, usually `render_page(__file__, page_context)`.
|
|
260
|
-
- The second item must be a dict. Its keys are merged into the wrapping layout context and become available to parent layouts as `
|
|
263
|
+
- The second item must be a dict. Its keys are merged into the wrapping layout context and become available to parent layouts as `{{ layout.* }}`.
|
|
261
264
|
|
|
262
265
|
Use that tuple form when one route needs to influence a wrapper without turning that value into a section-wide default. A common example is a dashboard page that needs to lock the root body with `overflow-hidden` while the rest of the app keeps normal scrolling.
|
|
263
266
|
|
|
264
267
|
Example root layout:
|
|
265
268
|
|
|
266
269
|
```html
|
|
267
|
-
<body class="
|
|
268
|
-
|
|
269
|
-
</body>
|
|
270
|
+
<body class="{{ layout.dashboard_body_class | default('') }}">
|
|
271
|
+
<slot />
|
|
272
|
+
</body>
|
|
270
273
|
```
|
|
271
274
|
|
|
272
275
|
Example route:
|
|
@@ -281,7 +284,7 @@ async def page():
|
|
|
281
284
|
)
|
|
282
285
|
```
|
|
283
286
|
|
|
284
|
-
The key name is arbitrary, but it must match exactly between the dict returned from `page()` and the `
|
|
287
|
+
The key name is arbitrary, but it must match exactly between the dict returned from `page()` and the `{{ layout.some_key }}` lookup in `layout.html`.
|
|
285
288
|
|
|
286
289
|
Use distinct names for those layout props. In the current router, the second dict is merged into the full layout context after path params and `request`, so a key such as `slug` or `request` can shadow an existing value.
|
|
287
290
|
|
|
@@ -363,15 +366,17 @@ In practice, this means a dashboard is usually a folder-level layout, not a sing
|
|
|
363
366
|
|
|
364
367
|
When a layout imports components, keep each `<!-- @import ... -->` comment above the layout's authored wrapper element, such as the root `<section>` in a nested layout or the root `<html>` in the app layout.
|
|
365
368
|
|
|
366
|
-
Resolved SEO fields are exposed to layouts as `
|
|
369
|
+
Resolved SEO fields are exposed to layouts as `{{ metadata.* }}`, while values returned from `layout.py` are exposed separately as `{{ layout.* }}`.
|
|
367
370
|
|
|
368
371
|
### `layout.html`
|
|
369
372
|
|
|
370
373
|
Use `layout.html` for the shared wrapper markup of a subtree. Keep the visible shell here, not in `layout.py`.
|
|
371
374
|
|
|
372
|
-
Follow the same authoring contract used by route templates: one authored parent node, top-of-file `<!-- @import ... -->` directives above that root, plain `<script>` inside the root when needed, and no handwritten `pp-component` or `type="text/pp"`. See [pulsepoint.md](./pulsepoint.md) for the canonical authored-vs-runtime explanation.
|
|
373
|
-
|
|
374
|
-
|
|
375
|
+
Follow the same authoring contract used by route templates: one authored parent node, top-of-file `<!-- @import ... -->` directives above that root, plain `<script>` inside the root when needed, and no handwritten `pp-component` or `type="text/pp"`. See [pulsepoint.md](./pulsepoint.md) for the canonical authored-vs-runtime explanation.
|
|
376
|
+
|
|
377
|
+
Place child routes with a plain HTML `<slot />` tag. Caspian replaces that layout slot with the current child route or nested layout while rendering.
|
|
378
|
+
|
|
379
|
+
For example, a page inside `/dashboard/settings` is wrapped by the root layout first and then by the dashboard layout.
|
|
375
380
|
|
|
376
381
|
Example root layout:
|
|
377
382
|
|
|
@@ -379,19 +384,19 @@ Example root layout:
|
|
|
379
384
|
<!DOCTYPE html>
|
|
380
385
|
<html>
|
|
381
386
|
<head>
|
|
382
|
-
<title>
|
|
383
|
-
<meta name="description" content="
|
|
387
|
+
<title>{{ metadata.title }}</title>
|
|
388
|
+
<meta name="description" content="{{ metadata.description }}" />
|
|
384
389
|
</head>
|
|
385
|
-
<body>
|
|
386
|
-
<NavBar />
|
|
387
|
-
|
|
388
|
-
</body>
|
|
389
|
-
</html>
|
|
390
|
+
<body>
|
|
391
|
+
<NavBar />
|
|
392
|
+
<slot />
|
|
393
|
+
</body>
|
|
394
|
+
</html>
|
|
390
395
|
```
|
|
391
396
|
|
|
392
397
|
### `layout.py`
|
|
393
398
|
|
|
394
|
-
If a layout needs shared
|
|
399
|
+
If a layout needs shared props or metadata, add a `layout.py` file next to the HTML layout. Treat it as the backend companion for the layout, not as the place to author visible wrapper markup.
|
|
395
400
|
|
|
396
401
|
When `layout()` calls `render_layout(__file__, ...)`, root-validation errors are attributed to the sibling `layout.html` because that file is the authored template. If `layout()` returns a raw HTML string directly, Caspian treats that value as a runtime fragment instead of an authored template and wraps it in a runtime host root when needed.
|
|
397
402
|
|
|
@@ -412,16 +417,16 @@ def layout(context_data):
|
|
|
412
417
|
|
|
413
418
|
`context_data` includes URL parameters such as dynamic route values.
|
|
414
419
|
|
|
415
|
-
In the common case, return a dict and let the sibling `layout.html` read those values through `
|
|
420
|
+
In the common case, return a dict and let the sibling `layout.html` read those values through `{{ layout.* }}`.
|
|
416
421
|
|
|
417
422
|
`layout()` currently supports these result shapes:
|
|
418
423
|
|
|
419
|
-
- `dict`: load the sibling `layout.html` and expose the dict as `
|
|
424
|
+
- `dict`: load the sibling `layout.html` and expose the dict as `{{ layout.* }}`
|
|
420
425
|
- `str`: use that string as the layout content
|
|
421
|
-
- `(layout_html, props_dict)`: use the first item as the layout content and expose the second dict as `
|
|
426
|
+
- `(layout_html, props_dict)`: use the first item as the layout content and expose the second dict as `{{ layout.* }}`
|
|
422
427
|
- `None`: fall back to the sibling `layout.html` with no extra layout props
|
|
423
428
|
|
|
424
|
-
If you intentionally want to render `layout.html` immediately with direct local variables instead of the `layout.*` namespace, call `render_layout(__file__, {...})` and reference those keys directly in the template.
|
|
429
|
+
If you intentionally want to render `layout.html` immediately with direct local variables instead of the `layout.*` namespace, call `render_layout(__file__, {...})` and reference those keys directly in the template. `render_layout(...)` does not create a hidden child outlet; the layout template must still author the real `<slot />` element where child routes should render.
|
|
425
430
|
|
|
426
431
|
Example:
|
|
427
432
|
|
|
@@ -432,9 +437,9 @@ def layout():
|
|
|
432
437
|
return render_layout(__file__, {"my_class": "size-8"})
|
|
433
438
|
```
|
|
434
439
|
|
|
435
|
-
In that pattern, the matching `layout.html` reads `
|
|
440
|
+
In that pattern, the matching `layout.html` reads `{{ my_class }}`, not `{{ layout.my_class }}`, because the template string was already rendered before the nested layout pipeline continues.
|
|
436
441
|
|
|
437
|
-
If you need both a custom layout string and standard `
|
|
442
|
+
If you need both a custom layout string and standard `{{ layout.* }}` props, return a tuple:
|
|
438
443
|
|
|
439
444
|
```python
|
|
440
445
|
from casp.layout import render_layout
|
|
@@ -446,7 +451,7 @@ def layout():
|
|
|
446
451
|
)
|
|
447
452
|
```
|
|
448
453
|
|
|
449
|
-
`layout()`
|
|
454
|
+
`layout()` may be synchronous or async in the installed runtime. Keep async layout work focused on shared subtree props or metadata; use `page()` or `@rpc()` when the work belongs to one route or a browser-triggered user action.
|
|
450
455
|
|
|
451
456
|
Use [metadata.md](./metadata.md) when a layout also needs SEO defaults. Return dictionaries from `layout()` for visual or template props, and use `Metadata(...)` for title, description, and social tags.
|
|
452
457
|
|
|
@@ -502,13 +507,14 @@ If an AI agent is choosing where to add or update route code, apply these rules
|
|
|
502
507
|
- If a route renders UI, create or update `index.html` for the markup.
|
|
503
508
|
- Add `index.py` only when the same route needs metadata or server behavior; do not place route HTML in `index.py`.
|
|
504
509
|
- Keep visible page markup in `index.html` and shared subtree shells in `layout.html`; do not place route HTML in `index.py` or layout HTML in `layout.py`.
|
|
510
|
+
- Use PulsePoint as the first-party interaction model for route and layout HTML. Avoid custom DOM wiring for normal events and reactivity.
|
|
505
511
|
- When the user asks for a dashboard, admin area, account section, or any grouped subtree of child routes, create a parent folder with `layout.html` and place the child routes beneath it. Follow the same mental model as the Next.js App Router.
|
|
506
512
|
- Use a normal folder such as `dashboard/` when the segment should appear in the URL. Use `(group)/` only when the folder should organize or wrap child routes without adding a path segment.
|
|
507
513
|
- Use [cache.md](./cache.md) when an `index.py` route should opt into page-level HTML caching.
|
|
508
|
-
- Use `layout.html` for shared wrappers and `layout.py` for layout-level
|
|
514
|
+
- Use `layout.html` for shared wrappers and `layout.py` for layout-level props or metadata.
|
|
509
515
|
- For grouped shells with separate shell and content scrolling, put `pp-reset-scroll="true"` on the content pane instead of the whole shell when only the page content should reset between child routes.
|
|
510
|
-
- When one route needs to change a parent layout, return `(render_page(__file__, ...), {"dashboard_body_class": ...})` from `page()` and read that value as `
|
|
511
|
-
- Use `layout.py` for layout props that should apply across an entire subtree. Use `render_layout(__file__, {...})` only when the layout should consume direct local variables such as `
|
|
516
|
+
- When one route needs to change a parent layout, return `(render_page(__file__, ...), {"dashboard_body_class": ...})` from `page()` and read that value as `{{ layout.dashboard_body_class }}` in the wrapping `layout.html`.
|
|
517
|
+
- Use `layout.py` for layout props that should apply across an entire subtree. Use `render_layout(__file__, {...})` only when the layout should consume direct local variables such as `{{ my_class }}` instead of the standard `{{ layout.* }}` namespace.
|
|
512
518
|
- Keep `<!-- @import ... -->` directives at the top of `index.html` and `layout.html`, above the single authored parent node.
|
|
513
519
|
- Use [metadata.md](./metadata.md) when a route or layout needs SEO fields.
|
|
514
520
|
- Use `[segment]` for single dynamic parameters.
|