caspian-utils 0.0.32 → 0.0.34
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 +71 -53
- package/dist/docs/components.md +8 -6
- package/dist/docs/fetch-data.md +6 -3
- package/dist/docs/index.md +14 -12
- package/dist/docs/pulsepoint-runtime-map.md +4 -1
- package/dist/docs/pulsepoint.md +104 -23
- package/dist/docs/routing.md +10 -6
- 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
|
@@ -56,6 +56,7 @@ from casp.auth import (
|
|
|
56
56
|
- Use `@rpc(require_auth=True, allowed_roles=[...])` for browser-triggered actions that need protection.
|
|
57
57
|
- Use `StateManager` only for transient auth-adjacent request state; keep the authenticated session itself owned by `casp.auth`.
|
|
58
58
|
- Keep secrets and provider credentials in `.env`; keep route visibility, redirects, and RBAC policy in `src/lib/auth/auth_config.py`.
|
|
59
|
+
- Do not implement custom `next` parsing or post-login redirect selection inside sign-in UI flows when using the built-in Caspian auth stack. The runtime and centralized auth config already own guest redirects, auth-route redirects, and the default post-login target.
|
|
59
60
|
- Validate login, signup, reset-password, and profile-mutation inputs before hitting the database or external providers.
|
|
60
61
|
|
|
61
62
|
## Framework Internals Note
|
|
@@ -122,6 +123,23 @@ Important behavior from the current implementation:
|
|
|
122
123
|
- `role_identifier` defaults to `role`, and the current `auth.sign_in(...)` flow also normalizes `userRole` into `role` when possible.
|
|
123
124
|
- `api_auth_prefix` defaults to `/api/auth` and should stay centralized here rather than being hard-coded across routes.
|
|
124
125
|
|
|
126
|
+
## Redirect Ownership
|
|
127
|
+
|
|
128
|
+
Treat redirect behavior as framework-owned plus config-owned, not sign-in-page-owned.
|
|
129
|
+
|
|
130
|
+
- Keep the redirect policy in `src/lib/auth/auth_config.py`, especially `default_signin_redirect` and `default_signout_redirect`.
|
|
131
|
+
- In the current runtime, unauthenticated requests to protected routes are redirected by middleware and decorators to `/signin?next=...`.
|
|
132
|
+
- In the current runtime, authenticated users who hit an auth route such as `/signin` are redirected by the auth layer to `default_signin_redirect`, which defaults to `/dashboard`.
|
|
133
|
+
- `guest_only()` also prefers a safe `next` query value for already-authenticated users and otherwise falls back to `default_signin_redirect`.
|
|
134
|
+
- Because this behavior already exists in `main.py`, `.venv/Lib/site-packages/casp/auth.py`, and `src/lib/auth/auth_config.py`, sign-in implementations should not add their own redirect-decision layer, duplicate `next` handling, or hard-code a dashboard redirect inside the sign-in action.
|
|
135
|
+
- When building sign-in flows, focus on credential validation, calling `auth.sign_in(...)` or the relevant provider flow, and rendering errors. Let Caspian decide where the user goes next.
|
|
136
|
+
|
|
137
|
+
Use this rule of thumb for AI-generated sign-in work:
|
|
138
|
+
|
|
139
|
+
- If the task is only to build or style the sign-in page, do not add redirect support.
|
|
140
|
+
- If the task is to change where authenticated users land after sign-in, update `default_signin_redirect` in `src/lib/auth/auth_config.py` instead of editing the sign-in route logic.
|
|
141
|
+
- If the task is to change how protected routes send guests to sign-in, inspect the installed auth runtime and middleware instead of adding local redirect code to the sign-in page.
|
|
142
|
+
|
|
125
143
|
## Choosing Public vs Private Route Mode
|
|
126
144
|
|
|
127
145
|
Make this decision at app setup time in `src/lib/auth/auth_config.py`.
|
|
@@ -165,9 +183,9 @@ return AuthSettings(
|
|
|
165
183
|
|
|
166
184
|
The installed auth code reads several values from `.env` when explicit values are not passed.
|
|
167
185
|
|
|
168
|
-
- `AUTH_SECRET` backs `AuthSettings.secret_key`.
|
|
169
|
-
- App-owned startup helpers may also validate `AUTH_SECRET` and refuse production startup when the value is missing or still on a default placeholder.
|
|
170
|
-
- `AUTH_COOKIE_NAME` backs `AuthSettings.cookie_name`.
|
|
186
|
+
- `AUTH_SECRET` backs `AuthSettings.secret_key`.
|
|
187
|
+
- App-owned startup helpers may also validate `AUTH_SECRET` and refuse production startup when the value is missing or still on a default placeholder.
|
|
188
|
+
- `AUTH_COOKIE_NAME` backs `AuthSettings.cookie_name`.
|
|
171
189
|
- `SESSION_LIFETIME_HOURS` controls `SessionMiddleware.max_age` in the current `main.py` bootstrap.
|
|
172
190
|
- `APP_ENV=production` enables secure session cookies and the `Secure` flag on the current CSRF cookie.
|
|
173
191
|
- `CASPIAN_BROWSER_SYNC_PORT` can override the development cookie scope suffix used by the current `main.py` bootstrap.
|
|
@@ -225,29 +243,29 @@ This does two things:
|
|
|
225
243
|
- applies the centralized settings once at startup
|
|
226
244
|
- registers OAuth providers once so `AuthMiddleware` can delegate signin and callback paths through `auth.auth_providers(...)`
|
|
227
245
|
|
|
228
|
-
The current `main.py` also wires the session middleware directly, and some apps delegate the secret lookup to an app-owned helper:
|
|
229
|
-
|
|
230
|
-
```python
|
|
231
|
-
from starlette.middleware.sessions import SessionMiddleware
|
|
232
|
-
from casp.runtime_security import get_session_secret
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
SESSION_LIFETIME_HOURS = int(os.getenv("SESSION_LIFETIME_HOURS", 7))
|
|
236
|
-
IS_PRODUCTION = os.getenv("APP_ENV") == "production"
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
app.add_middleware(
|
|
240
|
-
SessionMiddleware,
|
|
241
|
-
secret_key=get_session_secret(is_production=IS_PRODUCTION),
|
|
242
|
-
session_cookie=os.getenv("AUTH_COOKIE_NAME", "session"),
|
|
243
|
-
max_age=SESSION_LIFETIME_HOURS * 3600,
|
|
244
|
-
same_site="lax",
|
|
246
|
+
The current `main.py` also wires the session middleware directly, and some apps delegate the secret lookup to an app-owned helper:
|
|
247
|
+
|
|
248
|
+
```python
|
|
249
|
+
from starlette.middleware.sessions import SessionMiddleware
|
|
250
|
+
from casp.runtime_security import get_session_secret
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
SESSION_LIFETIME_HOURS = int(os.getenv("SESSION_LIFETIME_HOURS", 7))
|
|
254
|
+
IS_PRODUCTION = os.getenv("APP_ENV") == "production"
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
app.add_middleware(
|
|
258
|
+
SessionMiddleware,
|
|
259
|
+
secret_key=get_session_secret(is_production=IS_PRODUCTION),
|
|
260
|
+
session_cookie=os.getenv("AUTH_COOKIE_NAME", "session"),
|
|
261
|
+
max_age=SESSION_LIFETIME_HOURS * 3600,
|
|
262
|
+
same_site="lax",
|
|
245
263
|
https_only=IS_PRODUCTION,
|
|
246
264
|
path="/",
|
|
247
265
|
)
|
|
248
266
|
```
|
|
249
267
|
|
|
250
|
-
Keep this wiring in `main.py`. Keep only the policy values in `src/lib/auth/auth_config.py`. Runtime security helpers for session-secret enforcement, safe public-file serving, production-safe error messages, and baseline response headers are package-owned by `casp.runtime_security`; keep the middleware attachment itself in `main.py`.
|
|
268
|
+
Keep this wiring in `main.py`. Keep only the policy values in `src/lib/auth/auth_config.py`. Runtime security helpers for session-secret enforcement, safe public-file serving, production-safe error messages, and baseline response headers are package-owned by `casp.runtime_security`; keep the middleware attachment itself in `main.py`.
|
|
251
269
|
|
|
252
270
|
## Session Lifetime Rules
|
|
253
271
|
|
|
@@ -260,40 +278,40 @@ The effective authenticated session lasts only while both are valid.
|
|
|
260
278
|
|
|
261
279
|
- If the session cookie expires first, the session disappears even if the auth payload expiration was longer.
|
|
262
280
|
- If the auth payload expires first, `auth.is_authenticated()` clears it even if the session cookie still exists.
|
|
263
|
-
- In apps where the request flow does not call `auth.refresh_session()`, `token_auto_refresh=True` still does nothing by itself.
|
|
281
|
+
- In apps where the request flow does not call `auth.refresh_session()`, `token_auto_refresh=True` still does nothing by itself.
|
|
264
282
|
|
|
265
283
|
Keep `SESSION_LIFETIME_HOURS` and `default_token_validity` aligned unless you intentionally want one boundary to end earlier than the other.
|
|
266
284
|
|
|
267
285
|
## Middleware Order
|
|
268
286
|
|
|
269
|
-
The current `main.py` adds middleware in this source order:
|
|
270
|
-
|
|
271
|
-
```python
|
|
272
|
-
app.add_middleware(RPCMiddleware)
|
|
273
|
-
app.add_middleware(AuthMiddleware)
|
|
274
|
-
app.add_middleware(CSRFMiddleware)
|
|
275
|
-
app.add_middleware(SessionMiddleware, ...)
|
|
276
|
-
app.add_middleware(SecurityHeadersMiddleware)
|
|
277
|
-
```
|
|
278
|
-
|
|
279
|
-
Because Starlette runs the last-added middleware first, the effective request order is:
|
|
280
|
-
|
|
281
|
-
1. `SecurityHeadersMiddleware`
|
|
282
|
-
2. `SessionMiddleware`
|
|
283
|
-
3. `CSRFMiddleware`
|
|
284
|
-
4. `AuthMiddleware`
|
|
285
|
-
5. `RPCMiddleware`
|
|
286
|
-
6. route handler or RPC endpoint
|
|
287
|
-
|
|
288
|
-
Current behavior by layer:
|
|
289
|
-
|
|
290
|
-
- `SecurityHeadersMiddleware` can attach baseline response headers from `casp.runtime_security`, such as `X-Content-Type-Options`, framing policy, referrer policy, permissions policy, and production HSTS, while preserving any headers already set by the response. Do not assume this layer emits CSP or owns third-party browser resource allowlists; verify the installed package helper first.
|
|
291
|
-
- `SessionMiddleware` provides `request.session` for the rest of the stack.
|
|
292
|
-
- `CSRFMiddleware` ensures `request.session["csrf_token"]` exists and emits a scoped CSRF cookie based on `pp_csrf`, for example `pp_csrf_5091` in development or plain `pp_csrf` when no dev scope is active.
|
|
293
|
-
- `AuthMiddleware` sets request context with `Auth.set_request(request)`, initializes `StateManager`, runs provider callbacks, skips configured static asset paths, and enforces public, auth, private, and role-based route redirects.
|
|
294
|
-
- `RPCMiddleware` handles `POST` requests with `X-PP-RPC: true` and forwards them to Caspian's RPC handler after auth and session setup are already available.
|
|
295
|
-
|
|
296
|
-
Keep `SessionMiddleware` immediately inside any outer response-header wrapper such as `SecurityHeadersMiddleware`. If CSRF, auth, or RPC handling runs before the session layer, `request.session` will not be available.
|
|
287
|
+
The current `main.py` adds middleware in this source order:
|
|
288
|
+
|
|
289
|
+
```python
|
|
290
|
+
app.add_middleware(RPCMiddleware)
|
|
291
|
+
app.add_middleware(AuthMiddleware)
|
|
292
|
+
app.add_middleware(CSRFMiddleware)
|
|
293
|
+
app.add_middleware(SessionMiddleware, ...)
|
|
294
|
+
app.add_middleware(SecurityHeadersMiddleware)
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
Because Starlette runs the last-added middleware first, the effective request order is:
|
|
298
|
+
|
|
299
|
+
1. `SecurityHeadersMiddleware`
|
|
300
|
+
2. `SessionMiddleware`
|
|
301
|
+
3. `CSRFMiddleware`
|
|
302
|
+
4. `AuthMiddleware`
|
|
303
|
+
5. `RPCMiddleware`
|
|
304
|
+
6. route handler or RPC endpoint
|
|
305
|
+
|
|
306
|
+
Current behavior by layer:
|
|
307
|
+
|
|
308
|
+
- `SecurityHeadersMiddleware` can attach baseline response headers from `casp.runtime_security`, such as `X-Content-Type-Options`, framing policy, referrer policy, permissions policy, and production HSTS, while preserving any headers already set by the response. Do not assume this layer emits CSP or owns third-party browser resource allowlists; verify the installed package helper first.
|
|
309
|
+
- `SessionMiddleware` provides `request.session` for the rest of the stack.
|
|
310
|
+
- `CSRFMiddleware` ensures `request.session["csrf_token"]` exists and emits a scoped CSRF cookie based on `pp_csrf`, for example `pp_csrf_5091` in development or plain `pp_csrf` when no dev scope is active.
|
|
311
|
+
- `AuthMiddleware` sets request context with `Auth.set_request(request)`, initializes `StateManager`, runs provider callbacks, skips configured static asset paths, and enforces public, auth, private, and role-based route redirects.
|
|
312
|
+
- `RPCMiddleware` handles `POST` requests with `X-PP-RPC: true` and forwards them to Caspian's RPC handler after auth and session setup are already available.
|
|
313
|
+
|
|
314
|
+
Keep `SessionMiddleware` immediately inside any outer response-header wrapper such as `SecurityHeadersMiddleware`. If CSRF, auth, or RPC handling runs before the session layer, `request.session` will not be available.
|
|
297
315
|
|
|
298
316
|
## Current `AuthMiddleware` Flow
|
|
299
317
|
|
|
@@ -765,7 +783,7 @@ Prefer `AuthSettings`, `configure_auth(...)`, and `auth.settings` in new code.
|
|
|
765
783
|
- Expired or malformed payloads are removed during `auth.is_authenticated()` checks.
|
|
766
784
|
- `auth.get_payload()` returns `None` for missing payloads, a dict for dict payloads, and `{"value": ...}` for non-dict payloads.
|
|
767
785
|
- OAuth callback helpers return `None` on failure, so calling routes should be prepared for a normal page render or explicit error state when provider login fails.
|
|
768
|
-
- The current app bootstrap may wrap auth and session handling in an outer `SecurityHeadersMiddleware`; auth-aware code still depends on `SessionMiddleware`, `CSRFMiddleware`, `AuthMiddleware`, and `RPCMiddleware` staying in the correct relative order so sessions and CSRF state exist before route checks or RPC handling.
|
|
786
|
+
- The current app bootstrap may wrap auth and session handling in an outer `SecurityHeadersMiddleware`; auth-aware code still depends on `SessionMiddleware`, `CSRFMiddleware`, `AuthMiddleware`, and `RPCMiddleware` staying in the correct relative order so sessions and CSRF state exist before route checks or RPC handling.
|
|
769
787
|
- Password reset, email verification, and other account-recovery workflows are application responsibilities layered on top of `casp.auth`, not built-in auth runtime features.
|
|
770
788
|
- Route lists and RBAC maps are exact-path checks, not wildcard or prefix rules.
|
|
771
789
|
|
|
@@ -808,7 +826,7 @@ If an AI agent is deciding how to handle auth in Caspian, apply these rules firs
|
|
|
808
826
|
- Do not treat `token_auto_refresh` as a route-privacy switch. In the current app it only affects sliding-session refresh if `auth.refresh_session()` is called.
|
|
809
827
|
- Use `@require_auth`, `@require_role`, and `@guest_only` for page-level access rules.
|
|
810
828
|
- Use `@rpc(require_auth=True, allowed_roles=[...])` for protected browser-triggered actions.
|
|
811
|
-
- Keep `SessionMiddleware` immediately inside any outer response-header wrapper so auth, CSRF, and RPC handlers can read `request.session`.
|
|
829
|
+
- Keep `SessionMiddleware` immediately inside any outer response-header wrapper so auth, CSRF, and RPC handlers can read `request.session`.
|
|
812
830
|
- Use [state.md](./state.md) only for transient auth-adjacent request state, not as the auth session store.
|
|
813
831
|
- Align `SESSION_LIFETIME_HOURS` with `default_token_validity` unless you intentionally want different cookie and auth-payload expiry windows.
|
|
814
832
|
- Prefer exact route strings in `public_routes`, `auth_routes`, `private_routes`, and `role_based_routes`.
|
|
@@ -816,6 +834,6 @@ If an AI agent is deciding how to handle auth in Caspian, apply these rules firs
|
|
|
816
834
|
- Keep auth secrets and OAuth provider credentials in `.env`.
|
|
817
835
|
- Set `AUTH_COOKIE_NAME` explicitly so the session middleware cookie name and auth settings stay aligned.
|
|
818
836
|
- Use `.venv/Lib/site-packages/casp/auth.py` only when the task is about framework auth internals or debugging installed behavior.
|
|
819
|
-
- Use `main.py` plus `casp.runtime_security` when the task is about auth bootstrap, session middleware, safe public-file serving, response headers, or middleware execution order.
|
|
837
|
+
- Use `main.py` plus `casp.runtime_security` when the task is about auth bootstrap, session middleware, safe public-file serving, response headers, or middleware execution order.
|
|
820
838
|
- Pair auth work with `fetch-data.md` for RPC forms and `validation.md` for credential validation.
|
|
821
839
|
- Check `routing.md` and `project-structure.md` before creating new auth routes or shared auth helpers.
|
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
|
|
|
@@ -354,8 +355,9 @@ Keep synchronous components as the default. Switch to `async def` only when the
|
|
|
354
355
|
|
|
355
356
|
- Put reusable components in `src/components/` and keep route files in `src/app/`.
|
|
356
357
|
- 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
|
-
-
|
|
358
|
+
- If the component includes PulsePoint behavior, prefer a thin Python wrapper plus a same-name `.html` template.
|
|
359
|
+
- 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.
|
|
360
|
+
- Keep the component file name, exported function name, and authored tag aligned, such as `Button.py`, `def Button(...)`, and `<x-button />`.
|
|
359
361
|
- Accept `children` or `**props` when the component should support nested content.
|
|
360
362
|
- Keep page-level data loading in `page()` when the data is not intrinsic to the component itself.
|
|
361
363
|
- If you add `@rpc()` functions inside a component file, keep their names globally unique because component RPCs are not route-scoped.
|
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
|
|
|
@@ -46,8 +48,9 @@ When a page belongs to a grouped subtree such as a dashboard, account area, admi
|
|
|
46
48
|
|
|
47
49
|
- Use `page()` for async or route-level data required before HTML renders, and use `layout()` only for synchronous shared 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
|
|
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
|
|
|
@@ -40,7 +40,7 @@ If an inspected browser DOM disagrees with authored template source, remember th
|
|
|
40
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
41
|
| Portals | `pp.portal(ref, target?)` | `pp-reactive-v2.js` | context should preserve logical ancestry through the registry |
|
|
42
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` |
|
|
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
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
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
46
|
| Streaming | `pp.rpc(..., { onStream })` | `pp-reactive-v2.js`, `casp/rpc.py`, `casp/streaming.py` | server generators become SSE responses |
|
|
@@ -56,6 +56,7 @@ If an inspected browser DOM disagrees with authored template source, remember th
|
|
|
56
56
|
- Read [fetch-data.md](./fetch-data.md) before adding browser-triggered backend work.
|
|
57
57
|
- Use this map when the task names a PulsePoint feature and you need the owning runtime file quickly.
|
|
58
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.
|
|
59
60
|
|
|
60
61
|
## Copy-Safe Authoring Rules
|
|
61
62
|
|
|
@@ -63,9 +64,11 @@ If an inspected browser DOM disagrees with authored template source, remember th
|
|
|
63
64
|
- Keep any owned plain `<script>` inside that same root.
|
|
64
65
|
- Do not handwrite `pp-component`, `type="text/pp"`, `data-pp-ref`, `pp-owner`, `pp-event-owner`, or other runtime-managed attributes.
|
|
65
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.
|
|
66
68
|
- Use `Context.Provider` and `pp.context(...)` for context.
|
|
67
69
|
- Use `pp-for` only on `<template>` and plain `key` for keyed lists.
|
|
68
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.
|
|
69
72
|
|
|
70
73
|
## Compact Examples
|
|
71
74
|
|
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
|
@@ -35,7 +35,8 @@ Start with these rules:
|
|
|
35
35
|
- In grouped shells with separate shell and content scrolling, put `pp-reset-scroll="true"` on the content pane in `layout.html` when that pane should reset on child-route navigation while the shell sidebar or rail keeps its own scroll.
|
|
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
|
-
- 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.
|
|
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
|
|
|
@@ -181,9 +182,11 @@ Route templates can import reusable Python components with `<!-- @import ... -->
|
|
|
181
182
|
|
|
182
183
|
Place those import comments at the top of the file, above the authored root element. They are file-level directives, not children of the route root.
|
|
183
184
|
|
|
184
|
-
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
|
-
|
|
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.
|
|
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
|
+
|
|
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:
|
|
189
192
|
|
|
@@ -501,8 +504,9 @@ If an AI agent is choosing where to add or update route code, apply these rules
|
|
|
501
504
|
- Use folder names to model URL segments.
|
|
502
505
|
- If a route renders UI, create or update `index.html` for the markup.
|
|
503
506
|
- Add `index.py` only when the same route needs metadata or server behavior; do not place route HTML in `index.py`.
|
|
504
|
-
- 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`.
|
|
505
|
-
-
|
|
507
|
+
- 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`.
|
|
508
|
+
- Use PulsePoint as the first-party interaction model for route and layout HTML. Avoid custom DOM wiring for normal events and reactivity.
|
|
509
|
+
- 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
510
|
- 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
511
|
- Use [cache.md](./cache.md) when an `index.py` route should opt into page-level HTML caching.
|
|
508
512
|
- Use `layout.html` for shared wrappers and `layout.py` for layout-level synchronous props or metadata.
|