caspian-utils 0.2.6 → 0.2.8
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 +2 -2
- package/dist/docs/components.md +9 -9
- package/dist/docs/core-runtime-map.md +2 -3
- package/dist/docs/fetch-data.md +17 -13
- package/dist/docs/file-conventions.md +1 -1
- package/dist/docs/file-uploads.md +10 -8
- package/dist/docs/index.md +1 -1
- package/dist/docs/project-structure.md +4 -4
- package/dist/docs/pulsepoint-runtime-map.md +10 -11
- package/dist/docs/pulsepoint.md +62 -65
- package/dist/docs/routing.md +2 -2
- package/package.json +1 -1
|
@@ -54,13 +54,13 @@ 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`,
|
|
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`, `public/js/pp-reactive-v2.js` | single-root template shape, plain-script-inside-root authoring, and safe template materialization |
|
|
58
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, `Object.fromEntries(new FormData(...).entries())` for simple form submits, and avoiding id-driven `querySelector` or `addEventListener` wiring |
|
|
59
59
|
| A debounced server search pauses the first key typed after the timer fires. Determine whether the component or runtime owns the problem. | [index.md](./index.md), [pulsepoint.md](./pulsepoint.md), [components.md](./components.md), [fetch-data.md](./fetch-data.md), [pulsepoint-runtime-map.md](./pulsepoint-runtime-map.md) | Owning `src/app/**` or `src/components/**`; `public/js/pp-reactive-v2.js` only when profiling points to runtime reconciliation | State versus ref ownership, debounce frequency versus render cost, stale-response generations, the smallest useful render owner, and `pp.enablePerf()` evidence before a runtime edit |
|
|
60
60
|
| 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 mandatory Prisma Python ORM use for database reads and writes when Prisma is enabled |
|
|
61
61
|
| Add `public/icons/app.png` and make it browser-accessible without editing routes. | [index.md](./index.md), [project-structure.md](./project-structure.md), [core-runtime-map.md](./core-runtime-map.md), [auth.md](./auth.md) | `public/**`, `main.py`, `.venv/Lib/site-packages/casp/runtime_security.py` | root-relative URL mapping, no per-directory route or prefix registry, traversal and symlink containment, `GET`/`HEAD` only, and middleware order |
|
|
62
62
|
| Store untrusted uploads in a custom public directory. | [index.md](./index.md), [file-uploads.md](./file-uploads.md), [project-structure.md](./project-structure.md), [core-runtime-map.md](./core-runtime-map.md) | `src/app/**/index.py`, `src/lib/**`, `main.py`, `.venv/Lib/site-packages/casp/runtime_security.py` | top-level `inline_safe_subdirectories` registration, attachment mode for HTML/SVG/unknown types, `nosniff`, path containment, and BrowserSync ignore rules |
|
|
63
|
-
| Explain
|
|
63
|
+
| Explain how a plain component `<script>` stays inert during template materialization. | [index.md](./index.md), [pulsepoint.md](./pulsepoint.md), [components.md](./components.md), [routing.md](./routing.md) | `main.py`, `.venv/Lib/site-packages/casp/components_compiler.py`, `public/js/pp-reactive-v2.js` | component-root deferral, script-source capture, native-execution prevention, and component-scope evaluation |
|
|
64
64
|
| 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 |
|
|
65
65
|
| Trace the auth cookie and CSRF cookie names used in development. | [index.md](./index.md), [auth.md](./auth.md), [core-runtime-map.md](./core-runtime-map.md) | `main.py`, `.venv/Lib/site-packages/casp/auth.py` | development cookie scoping and CSRF or session naming ownership |
|
|
66
66
|
| Find how route params and query params reach `page()`. | [index.md](./index.md), [routing.md](./routing.md), [core-runtime-map.md](./core-runtime-map.md) | `main.py`, `.venv/Lib/site-packages/casp/layout.py` | path-dict delivery, query coercion, and `request` injection |
|
package/dist/docs/components.md
CHANGED
|
@@ -258,7 +258,7 @@ def UserCard(user, **props):
|
|
|
258
258
|
""", attrs=attrs, user=user)
|
|
259
259
|
```
|
|
260
260
|
|
|
261
|
-
The returned value is `Markup`, so the normal pipeline still injects `pp-component` on the single root and
|
|
261
|
+
The returned value is `Markup`, so the normal pipeline still injects `pp-component` on the single root and defers that root inside an inert template. The plain component `<script>` remains plain; PulsePoint captures its source before materialization and evaluates it in component scope. A single-file component renders identically to the two-file `render_html(...)` form; the choice is purely about readability.
|
|
262
262
|
|
|
263
263
|
### Receiving Props In A Python Component
|
|
264
264
|
|
|
@@ -608,16 +608,16 @@ For ref purposes, a component's root is the single native element that receives
|
|
|
608
608
|
|
|
609
609
|
The default takes precedence over catch-all `**props`: `pp-ref` is reserved for root ref forwarding and is not delivered in that dictionary. A component that intentionally wants `pp-ref` as ordinary data can opt out by declaring an explicit camel-case `ppRef` parameter, for example `def Inspector(ppRef="", **props)`. That explicit declaration consumes the prop, disables automatic root forwarding for that component invocation, and makes the component responsible for how the value is used. Use this escape hatch sparingly; ordinary UI primitives should keep the standard root-ref contract.
|
|
610
610
|
|
|
611
|
-
## Auto-Injected `pp-component` And
|
|
611
|
+
## Auto-Injected `pp-component` And Plain Component Scripts
|
|
612
612
|
|
|
613
|
-
Treat `pp-component="componentName"`
|
|
613
|
+
Treat `pp-component="componentName"` as framework output, not authored source. The canonical authored-vs-runtime explanation lives in [pulsepoint.md](./pulsepoint.md).
|
|
614
614
|
|
|
615
615
|
- Do not manually add `pp-component="..."` to route templates, layout templates, or component HTML files.
|
|
616
|
-
-
|
|
617
|
-
-
|
|
618
|
-
- `main.py`
|
|
619
|
-
-
|
|
620
|
-
- If a runtime example
|
|
616
|
+
- The Python render pipeline injects `pp-component` onto the single root element during render.
|
|
617
|
+
- Add a plain, untyped `<script>` only when the route or component actually needs PulsePoint logic, and keep it inside that single root element.
|
|
618
|
+
- `main.py` preserves that plain script while deferring the root inside an inert template.
|
|
619
|
+
- Before materialization or morph insertion, PulsePoint captures and empties the script so the browser cannot execute it natively, then evaluates the captured source in component scope.
|
|
620
|
+
- If a runtime example shows `pp-component`, treat that attribute as rendered output rather than authored source.
|
|
621
621
|
|
|
622
622
|
## Single-Root Rule
|
|
623
623
|
|
|
@@ -754,5 +754,5 @@ Keep synchronous components as the default. Switch to `async def` only when the
|
|
|
754
754
|
|
|
755
755
|
- Read [project-structure.md](./project-structure.md) for where reusable components should live.
|
|
756
756
|
- Read [routing.md](./routing.md) when importing components into route templates.
|
|
757
|
-
- Read [pulsepoint.md](./pulsepoint.md) for the runtime contract of `pp-component`,
|
|
757
|
+
- Read [pulsepoint.md](./pulsepoint.md) for the runtime contract of `pp-component`, plain owned scripts, props, and browser-side reactivity.
|
|
758
758
|
- Read [fetch-data.md](./fetch-data.md) when a component needs browser-triggered `pp.rpc()` calls or component-owned server actions.
|
|
@@ -37,7 +37,7 @@ Important current `main.py` behaviors AI should keep in mind:
|
|
|
37
37
|
|
|
38
38
|
- In development, `_scoped_cookie_name(...)` appends the active BrowserSync or dev port to both the session cookie name and the CSRF cookie name. The scope is resolved from `CASPIAN_BROWSER_SYNC_PORT`, then `settings/bs-config.json`, then `PORT`.
|
|
39
39
|
- `register_single_route(...)` passes path params to `page()` as one positional dict, injects matching query params by name, and injects `request` by keyword when declared.
|
|
40
|
-
- The render pipeline
|
|
40
|
+
- The render pipeline transforms components, renders nested layouts, and finally defers outermost component roots inside inert templates.
|
|
41
41
|
- Route-level generators returned from `page()` are wrapped in `SSE(...)` before the response is sent.
|
|
42
42
|
- Safe public-file helpers live in `casp.runtime_security`. `PublicFilesMiddleware` maps every existing nested file under `public/**` to the same root-relative URL without per-directory routes, rejects traversal and symlink escape, handles only `GET`/`HEAD`, and falls through when no file exists so page and mounted-app routing still works. User-upload directories must retain their restricted inline-media policy.
|
|
43
43
|
- Session middleware secrets may be resolved through `casp.runtime_security` so production can fail fast when `AUTH_SECRET` is missing or still on a default placeholder.
|
|
@@ -100,7 +100,6 @@ Interactive CRUD page:
|
|
|
100
100
|
| [.venv/Lib/site-packages/casp/validate.py](../../../../.venv/Lib/site-packages/casp/validate.py) | `Validate`, `Rule`, sanitization, and file-validation internals | [validation.md](./validation.md) |
|
|
101
101
|
| [.venv/Lib/site-packages/casp/component_decorator.py](../../../../.venv/Lib/site-packages/casp/component_decorator.py) | `@component`, `render_html(...)`, the inline `html(...)` single-file helper (with direct-call scope capture), component loading, and prop normalization before component calls | [components.md](./components.md) |
|
|
102
102
|
| [.venv/Lib/site-packages/casp/components_compiler.py](../../../../.venv/Lib/site-packages/casp/components_compiler.py) | `@import` parsing, `x-*` component resolution (including resolving a component's own tags from its Python module imports and the scope captured by directly-called components), parent-scope expansion of slot content, single-root validation, and `pp-component` injection | [components.md](./components.md), [routing.md](./routing.md), [pulsepoint.md](./pulsepoint.md) |
|
|
103
|
-
| [.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) |
|
|
104
103
|
| [.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) |
|
|
105
104
|
| [.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) |
|
|
106
105
|
|
|
@@ -118,7 +117,7 @@ Use these behavior checkpoints when AI needs the fastest verification path for a
|
|
|
118
117
|
| `casp.auth` | auth settings, signin and signout flow, provider wiring, and page protection behavior |
|
|
119
118
|
| `casp.rpc` and streamed RPC responses | middleware interception, CSRF and session expectations, registry behavior, and helper-level RPC contracts |
|
|
120
119
|
| `casp.layout` | layout discovery, metadata merge, root handling, and layout rendering rules |
|
|
121
|
-
| `casp.components_compiler`, `casp.component_decorator`, `
|
|
120
|
+
| `casp.components_compiler`, `casp.component_decorator`, `main.py` template-root deferral, and the browser runtime | `@import` parsing, `x-*` expansion, Python-module-import tag resolution, parent-scope slot resolution, inline `html(...)` rendering and children-safe handling, deterministic root keys, `pp-component` injection, plain component-script preservation, and safe script capture during materialization |
|
|
122
121
|
| `casp.state_manager` | wire vs non-wire reset behavior, request-state persistence assumptions, and AttributeDict access |
|
|
123
122
|
| `casp.cache_handler` | filename generation, manifest writes, TTL handling, and invalidation behavior |
|
|
124
123
|
| `casp.caspian_config` | config parsing, files index building, and Next.js-style route inventory behavior |
|
package/dist/docs/fetch-data.md
CHANGED
|
@@ -320,7 +320,7 @@ async def chat(message: str):
|
|
|
320
320
|
yield text
|
|
321
321
|
```
|
|
322
322
|
|
|
323
|
-
Client side, append each chunk to reactive state so the UI grows token by token:
|
|
323
|
+
Client side, append each chunk to reactive state so the UI grows token by token:
|
|
324
324
|
|
|
325
325
|
```html
|
|
326
326
|
<script>
|
|
@@ -333,10 +333,12 @@ Client side, append each chunk to reactive state so the UI grows token by token:
|
|
|
333
333
|
onStreamComplete: () => console.log("done"),
|
|
334
334
|
});
|
|
335
335
|
}
|
|
336
|
-
</script>
|
|
337
|
-
```
|
|
338
|
-
|
|
339
|
-
|
|
336
|
+
</script>
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
When a streaming call uses `abortPrevious: true`, it remains the active cancellable RPC until the stream's final chunk. A later RPC call with `abortPrevious: true` can therefore cancel a response even after its headers and early chunks have arrived. Cancellation resolves to `{ cancelled: true }`; use a request generation as well when the UI must state explicitly which response is authoritative.
|
|
340
|
+
|
|
341
|
+
In the current runtime, generator and async-generator RPC results are wrapped with `SSE(...)` inside `casp.rpc.py`, and route-level generator results are also wrapped by `main.py` before the response is returned.
|
|
340
342
|
|
|
341
343
|
## File Uploads
|
|
342
344
|
|
|
@@ -379,11 +381,11 @@ Client example:
|
|
|
379
381
|
<script>
|
|
380
382
|
async function uploadSelectedFiles(fileList, collection = "auto") {
|
|
381
383
|
for (const file of Array.from(fileList ?? [])) {
|
|
382
|
-
const response = await pp.rpc("upload_file", { file, collection }, {
|
|
383
|
-
onUploadProgress: (progress) => {
|
|
384
|
-
console.log(progress.
|
|
385
|
-
},
|
|
386
|
-
});
|
|
384
|
+
const response = await pp.rpc("upload_file", { file, collection }, {
|
|
385
|
+
onUploadProgress: (progress) => {
|
|
386
|
+
console.log(progress.percent ?? 0);
|
|
387
|
+
},
|
|
388
|
+
});
|
|
387
389
|
|
|
388
390
|
if (response?.data) {
|
|
389
391
|
setFileManagerData(response.data);
|
|
@@ -391,9 +393,11 @@ Client example:
|
|
|
391
393
|
}
|
|
392
394
|
}
|
|
393
395
|
</script>
|
|
394
|
-
```
|
|
395
|
-
|
|
396
|
-
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
`onUploadProgress` receives `{ loaded, total, percent }`; there is no `percentage` field. A payload containing a `File` or non-empty `FileList` becomes multipart. PulsePoint emits all non-file fields before file parts so companion values are available to a server that starts handling a streamed file before the browser has finished sending the body. Object values are JSON-stringified, nullish values are omitted, and every file in a `FileList` is appended under the original field name.
|
|
399
|
+
|
|
400
|
+
Use this pattern for real file managers:
|
|
397
401
|
|
|
398
402
|
- Keep upload and delete actions in the owning route `index.py`, not in `main.py`.
|
|
399
403
|
- Use `page()` to render the initial manager payload.
|
|
@@ -44,7 +44,7 @@ For authored route, layout, loading, not-found, and error HTML files, keep exact
|
|
|
44
44
|
|
|
45
45
|
- Keep any `<!-- @import ... -->` directives above that root. They are header directives for the file, not children of the root element.
|
|
46
46
|
- Keep any owned plain `<script>` inside that root, not after it.
|
|
47
|
-
- Do not handwrite `pp-component
|
|
47
|
+
- Do not handwrite `pp-component`; keep owned component logic in a plain `<script>` inside the single root.
|
|
48
48
|
|
|
49
49
|
This is a runtime requirement, not a style preference.
|
|
50
50
|
|
|
@@ -129,11 +129,11 @@ Keep route-specific auth checks, input validation, and final response shape here
|
|
|
129
129
|
|
|
130
130
|
async function uploadSelectedFiles(fileList) {
|
|
131
131
|
for (const file of Array.from(fileList ?? [])) {
|
|
132
|
-
const response = await pp.rpc("upload_asset", { file }, {
|
|
133
|
-
onUploadProgress: (progress) => {
|
|
134
|
-
console.log(progress.
|
|
135
|
-
},
|
|
136
|
-
});
|
|
132
|
+
const response = await pp.rpc("upload_asset", { file }, {
|
|
133
|
+
onUploadProgress: (progress) => {
|
|
134
|
+
console.log(progress.percent ?? 0);
|
|
135
|
+
},
|
|
136
|
+
});
|
|
137
137
|
|
|
138
138
|
if (response?.data) {
|
|
139
139
|
setFileManagerData(response.data);
|
|
@@ -142,9 +142,11 @@ Keep route-specific auth checks, input validation, and final response shape here
|
|
|
142
142
|
}
|
|
143
143
|
</script>
|
|
144
144
|
</section>
|
|
145
|
-
```
|
|
146
|
-
|
|
147
|
-
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
`onUploadProgress` receives `{ loaded, total, percent }`; `total` and `percent` may be `null` when the browser cannot compute the upload length. A multipart RPC writes companion non-file values before all `File`/`FileList` parts, so route parameters are available even when the server begins processing a streamed upload before the full request body arrives. A `FileList` is sent as repeated parts under one field name.
|
|
148
|
+
|
|
149
|
+
Prefer this state-driven shape over manual `innerHTML` list painting. In Caspian pages, manual DOM writes are easy to lose when PulsePoint rerenders the owner template.
|
|
148
150
|
|
|
149
151
|
## Persistence Rules
|
|
150
152
|
|
package/dist/docs/index.md
CHANGED
|
@@ -109,7 +109,7 @@ Preferred lookup order:
|
|
|
109
109
|
|
|
110
110
|
1. Read `node_modules/caspian-utils/dist/docs/index.md` to discover available local docs.
|
|
111
111
|
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.
|
|
112
|
-
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
|
|
112
|
+
3. Before authoring or editing any `src/app/**` or component HTML template, apply the single-root invariant: one authored root only, any owned plain `<script>` inside that root, and no handwritten `pp-component`.
|
|
113
113
|
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.
|
|
114
114
|
5. When Prisma is enabled, route all Python database work through the generated Prisma Python ORM. Use `database.md` before schema, migration, seed, or ORM decisions, and use `fetch-data.md` before wiring route data or RPC flows.
|
|
115
115
|
6. If the task asks for WebSockets, live bidirectional channels, socket origin checks, or native browser `WebSocket` clients, confirm `caspian.config.json` has `websocket: true`, read `websockets.md`, then inspect `main.py`, `src/lib/**`, the owning `src/app/**` route, and `settings/bs-config.json`. If `websocket` is false and the user wants it, ask first, then enable the config flag and follow the update workflow.
|
|
@@ -117,7 +117,7 @@ When a folder represents a section with child routes, such as `dashboard`, `acco
|
|
|
117
117
|
|
|
118
118
|
If the shared wrapper should not add a URL segment, use a parenthesized route-group folder such as `(marketing)/layout.html` instead of a normal folder name. Use a normal folder such as `dashboard/` only when that segment should be part of the public URL.
|
|
119
119
|
|
|
120
|
-
When authoring route or layout HTML, follow the authoring contract documented in [routing.md](./routing.md) and [pulsepoint.md](./pulsepoint.md): keep one authored root, keep `<!-- @import ... -->` directives above that root, use a plain `<script>` inside the root when needed, and do not handwrite `pp-component
|
|
120
|
+
When authoring route or layout HTML, follow the authoring contract documented in [routing.md](./routing.md) and [pulsepoint.md](./pulsepoint.md): keep one authored root, keep `<!-- @import ... -->` directives above that root, use a plain `<script>` inside the root when needed, and do not handwrite `pp-component`.
|
|
121
121
|
|
|
122
122
|
See `routing.md` for the full App Router-style rules for dynamic segments, route groups, and nested layouts.
|
|
123
123
|
|
|
@@ -133,7 +133,7 @@ The common Caspian pattern is a Python file such as `Button.py` with `@component
|
|
|
133
133
|
|
|
134
134
|
One Python file can also export multiple related `@component` functions. When that happens, import those tags from that exact file path in HTML, for example `<!-- @import { Breadcrumb, BreadcrumbItem, BreadcrumbList } from "../components/Breadcrumb.py" -->`, and render them as `<x-breadcrumb />`, `<x-breadcrumb-item />`, and `<x-breadcrumb-list />` instead of assuming each tag has its own sibling `.py` file.
|
|
135
135
|
|
|
136
|
-
For component HTML files, follow the component authoring rules in [components.md](./components.md): one top-level parent node, no sibling roots, plain `<script>` inside that root when needed, and no handwritten `pp-component
|
|
136
|
+
For component HTML files, follow the component authoring rules in [components.md](./components.md): one top-level parent node, no sibling roots, a plain `<script>` inside that root when needed, and no handwritten `pp-component`.
|
|
137
137
|
|
|
138
138
|
The directories listed in `componentScanDirs` determine where component tooling scans. When that list includes `src/`, `src/components/` is a conventionally clean location, not a hard-coded runtime requirement.
|
|
139
139
|
|
|
@@ -332,7 +332,7 @@ Notable internal files include:
|
|
|
332
332
|
- `.venv/Lib/site-packages/casp/state_manager.py` for request-scoped server state and persistence caveats
|
|
333
333
|
- `.venv/Lib/site-packages/casp/component_decorator.py` for `@component`, `render_html(...)`, and component loading
|
|
334
334
|
- `.venv/Lib/site-packages/casp/components_compiler.py` for `@import` parsing, `x-*` resolution, root validation, and `pp-component` injection
|
|
335
|
-
-
|
|
335
|
+
- `main.py` for final inert-template deferral and `public/js/pp-reactive-v2.js` for safe plain-script capture and component-scope evaluation
|
|
336
336
|
- `.venv/Lib/site-packages/casp/caspian_config.py` for config loading and route inventory parsing
|
|
337
337
|
- `.venv/Lib/site-packages/casp/streaming.py` for `SSE` and streamed response helpers
|
|
338
338
|
|
|
@@ -361,7 +361,7 @@ If an AI agent is deciding where to make changes, use these rules first.
|
|
|
361
361
|
- When deciding between `src/components/` and `src/lib/`, use `src/components/` for anything rendered as reusable UI and `src/lib/` for helpers, services, validators, adapters, and shared business logic.
|
|
362
362
|
- Use [mcp.md](./mcp.md) only when `caspian.config.json` enables MCP and the task involves FastMCP tool definitions, nested config discovery, or local MCP commands.
|
|
363
363
|
- Keep `<!-- @import ... -->` comments above the single authored root element in route, layout, and component HTML files. Do not nest them inside `<html>`, `<body>`, `<section>`, or any other parent wrapper.
|
|
364
|
-
- Follow the single-root authoring contract in [routing.md](./routing.md), [components.md](./components.md), and [pulsepoint.md](./pulsepoint.md): one authored root, any owned `<script>` inside that root, and no handwritten `pp-component`
|
|
364
|
+
- Follow the single-root authoring contract in [routing.md](./routing.md), [components.md](./components.md), and [pulsepoint.md](./pulsepoint.md): one authored root, any owned plain `<script>` inside that root, and no handwritten `pp-component` in source templates.
|
|
365
365
|
- Put shared helpers and reusable libraries in `src/lib/`.
|
|
366
366
|
- Use `settings/bs-config.ts` when uploaded public assets should not trigger BrowserSync reloads during the local stack.
|
|
367
367
|
- Put app-owned FastMCP code in `src/lib/mcp/` only when `caspian.config.json` enables MCP.
|
|
@@ -20,10 +20,9 @@ Use [pulsepoint.md](./pulsepoint.md) for the full authoring contract. Use this f
|
|
|
20
20
|
## Source Of Truth
|
|
21
21
|
|
|
22
22
|
- `public/js/pp-reactive-v2.js` is the shipped browser runtime.
|
|
23
|
-
- `main.py` owns
|
|
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/
|
|
26
|
-
- `.venv/Lib/site-packages/casp/html_attrs.py` owns Python-side class and attribute helpers such as `merge_classes(...)`.
|
|
23
|
+
- `main.py` owns component transformation and final inert-template deferral.
|
|
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/html_attrs.py` owns Python-side class and attribute helpers such as `merge_classes(...)`.
|
|
27
26
|
|
|
28
27
|
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
28
|
|
|
@@ -32,7 +31,7 @@ If an inspected browser DOM disagrees with authored template source, remember th
|
|
|
32
31
|
| PulsePoint feature | Authoring surface | Runtime owner | Verify before changing |
|
|
33
32
|
| --- | --- | --- | --- |
|
|
34
33
|
| Component roots | `src/app/**/index.html`, `layout.html`, component `.html` files | `components_compiler.py`, `public/js/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 | `
|
|
34
|
+
| Component scripts | plain, untyped `<script>` inside the authored root | `main.py`, `public/js/pp-reactive-v2.js` | the server preserves the plain script inside an inert component template; before materialization or morph insertion, PulsePoint captures and empties its source to prevent native execution, then evaluates it in component scope; one owned script per root |
|
|
36
35
|
| Template expressions | text and attributes with `{...}` | `public/js/pp-reactive-v2.js` | top-level script bindings are exported, nested bindings are not; every destructuring shape is supported at the top level (array, object, nested, defaults, rest, holes), so any tuple-returning hook reaches template scope |
|
|
37
36
|
| State | `pp.state(initial)` | `public/js/pp-reactive-v2.js` | setters accept values or updater functions, state belongs to the component instance |
|
|
38
37
|
| Effects | `pp.effect(...)`, `pp.layoutEffect(...)` | `public/js/pp-reactive-v2.js` | callbacks may return cleanup functions, promises are not awaited |
|
|
@@ -51,10 +50,10 @@ If an inspected browser DOM disagrees with authored template source, remember th
|
|
|
51
50
|
| Conditional UI | `hidden="{!cond}"`, a ternary inside `{...}`, or `pp-for` over a 0/1-length array | `public/js/pp-reactive-v2.js` | **there is no `pp-if`, `pp-show`, or `pp-else`, and no JSX `{cond && <div/>}`**; `hidden` is a bound boolean attribute (Tailwind preflight makes `[hidden]` `display:none !important`, so it beats a `flex`/`grid` utility; without preflight a `display` rule wins); a hidden subtree still renders, so guard with `?.`. See [pulsepoint.md](./pulsepoint.md) "Conditional rendering" |
|
|
52
51
|
| Lists | `<template pp-for="item in items">` | `public/js/pp-reactive-v2.js` | `pp-for` belongs on `<template>`, accepts arrays and synchronous iterables, captures rendered row values for events/props, and uses plain `key`, not `pp-key` |
|
|
53
52
|
| Events | native `onclick`, `oninput`, `onchange`, `onsubmit` | `public/js/pp-reactive-v2.js` | first-party events belong in `on*` attributes; event scope exposes `event`, `e`, `$event`, `target`, `currentTarget`, and `el`; normal form submits should use `Object.fromEntries(new FormData(event.currentTarget).entries())` instead of per-input refs; avoid id-driven `querySelector`/`addEventListener` for normal UI |
|
|
54
|
-
| RPC | `pp.rpc(...)` in scripts, `@rpc()` in Python | `public/js/pp-reactive-v2.js`, `casp/rpc.py` | use `pp.rpc`, not legacy `pp.fetchFunction`; protected actions use `@rpc(require_auth=True)` |
|
|
55
|
-
| Upload progress | `pp.rpc(..., { onUploadProgress })` | `public/js/pp-reactive-v2.js`, `casp/rpc.py` | XHR path is used for progress callbacks; the callback receives `{ loaded, total, percent }
|
|
56
|
-
| Streaming | `pp.rpc(..., { onStream })` | `public/js/pp-reactive-v2.js`, `casp/rpc.py`, `casp/streaming.py` | server generators become SSE responses |
|
|
57
|
-
| SPA navigation | `
|
|
53
|
+
| RPC | `pp.rpc(...)` in scripts, `@rpc()` in Python | `public/js/pp-reactive-v2.js`, `casp/rpc.py` | use `pp.rpc`, not legacy `pp.fetchFunction`; options include abort, URL/CSRF URL, credentials, streaming, and upload callbacks; protected actions use `@rpc(require_auth=True)` |
|
|
54
|
+
| Upload progress | `pp.rpc(..., { onUploadProgress })` | `public/js/pp-reactive-v2.js`, `casp/rpc.py` | XHR path is used for progress callbacks; the callback receives `{ loaded, total, percent }`, never `percentage`; multipart scalar/object fields precede file parts, and a `FileList` repeats its field name; replace state from returned payload |
|
|
55
|
+
| Streaming | `pp.rpc(..., { onStream })` | `public/js/pp-reactive-v2.js`, `casp/rpc.py`, `casp/streaming.py` | server generators become SSE responses; an `abortPrevious` stream stays cancellable until its final chunk |
|
|
56
|
+
| SPA navigation | links after `pp.mount()`; `a[pp-spa="false"]` opts out | `public/js/pp-reactive-v2.js`, `main.py` | interception is enabled automatically without a body opt-in; same-origin eligible links intercept; root-layout mismatches hard reload |
|
|
58
57
|
| Scroll restoration | `pp-reset-scroll="true"` | `public/js/pp-reactive-v2.js` | push navigation resets window; mark only content panes that should reset |
|
|
59
58
|
| Tailwind merge | `{twMerge(...)}`, Python `merge_classes(...)` | `html_attrs.py`, `public/js/pp-reactive-v2.js` | Python emits frontend-ready expressions when Tailwind is enabled |
|
|
60
59
|
| Markup deferral | `<template pp-component>` / `<template pp-owner>` wrappers | render pipeline (`main.py`), `public/js/pp-reactive-v2.js` (`materializeTemplateComponentBoundaries`) | top-level roots ship inside an inert `<template>`; runtime materializes them on mount and SPA navigation before scanning. Browser never validates `<template>` contents, so `{...}` is safe in any attribute/position (SVG geometry, `src`/`href`, form `value`/date, table/select text) — no per-tag workarounds. See [pulsepoint.md](./pulsepoint.md) "Component markup is deferred inside an inert `<template>`" |
|
|
@@ -76,7 +75,7 @@ If an inspected browser DOM disagrees with authored template source, remember th
|
|
|
76
75
|
- **Templates are plain HTML, not JSX.** PulsePoint borrows React's *hook API* and *component decomposition*, never its markup syntax. Quote every brace attribute (`class="{…}"`, never `class={…}` — the unquoted form is invalid HTML and blanks the page with no console error), use `hidden="{…}"` instead of `{cond && <div/>}`, and `<template pp-for="…">` instead of `{list.map(...)}`. See [pulsepoint.md](./pulsepoint.md) "PulsePoint Is Not JSX".
|
|
77
76
|
- Author one root element or one imported `x-*` root per route, layout, or component template.
|
|
78
77
|
- Keep any owned plain `<script>` inside that same root.
|
|
79
|
-
- Do not handwrite `pp-component`, `
|
|
78
|
+
- Do not handwrite `pp-component`, `data-pp-ref`, `pp-owner`, `pp-event-owner`, or other runtime-managed attributes.
|
|
80
79
|
- Use `pp.rpc(...)` for current browser-to-server calls.
|
|
81
80
|
- Use native `on*` attributes for button clicks, form submits, input changes, filters, toggles, and menus instead of adding ids and manual listeners.
|
|
82
81
|
- For ordinary form submits, bind `onsubmit` on the form and read named fields with `Object.fromEntries(new FormData(event.currentTarget).entries())`; reserve `pp-ref` for imperative access rather than routine payload collection.
|
|
@@ -182,7 +181,7 @@ Grouped shell scroll reset:
|
|
|
182
181
|
Use these prompts after docs or runtime changes to confirm AI can route correctly:
|
|
183
182
|
|
|
184
183
|
- "Create an interactive filter in a Caspian route template."
|
|
185
|
-
- "Explain
|
|
184
|
+
- "Explain how a plain component `<script>` stays inert while its deferred root is materialized."
|
|
186
185
|
- "Debug a dashboard sidebar losing scroll during child-route navigation."
|
|
187
186
|
- "Add an upload widget with progress and a reactive file list."
|
|
188
187
|
- "Use context to share theme state between parent and child components."
|
package/dist/docs/pulsepoint.md
CHANGED
|
@@ -31,7 +31,7 @@ Do not assume React, Vue, Svelte, JSX, Alpine, HTMX, or older PulsePoint docs un
|
|
|
31
31
|
Apply these before generating any template, even without reading the rest of this page:
|
|
32
32
|
|
|
33
33
|
1. One authored top-level root per route, layout, or component template; the owned plain `<script>` lives inside that root, never as a sibling.
|
|
34
|
-
2. Never handwrite `pp-component`, `
|
|
34
|
+
2. Never handwrite `pp-component`, `data-pp-ref`, or other runtime-managed attributes; the render pipeline injects them. Keep owned component logic in a plain, untyped `<script>` inside the root.
|
|
35
35
|
3. Bind first-party events with native `on*` attributes in the HTML; never wire normal UI with ids, `querySelector`, `addEventListener`, or manual `innerHTML`.
|
|
36
36
|
4. For ordinary form submits, use `onsubmit="{handler(event)}"` plus `Object.fromEntries(new FormData(event.currentTarget).entries())`; refs are for imperative access only.
|
|
37
37
|
5. Keep reactive values in `pp.state(...)`; keep template-facing bindings at the top level of the script.
|
|
@@ -93,9 +93,8 @@ Before writing a template, ask: *"Would this file be valid HTML if I deleted eve
|
|
|
93
93
|
| `pp-ref="name"` / `pp-ref="{expr}"` | Native elements and `x-*` component tags | Imperative element access |
|
|
94
94
|
| `pp-style="{cssText}"` | Any element | Dynamic inline style, as a **CSS string** |
|
|
95
95
|
| `pp-spread="{...obj}"` | Any element | Spread an object into attributes |
|
|
96
|
-
|
|
|
97
|
-
|
|
|
98
|
-
| `pp-spa="true"` / `pp-spa="false"` | `<body>` enables SPA navigation; `pp-spa="false"` on an `<a>` opts that link out | SPA navigation interception |
|
|
96
|
+
| `<token.provider value="{v}">` (lowercase) | Anywhere in markup | Context provider |
|
|
97
|
+
| `pp-spa="false"` | An `<a>` that must use normal browser navigation | Opt one link out of the SPA interception enabled automatically by `pp.mount()` |
|
|
99
98
|
| `pp-reset-scroll="true"` | A scroll container, or `<body>` | Reset that container's scroll on navigation |
|
|
100
99
|
| `pp-scroll-key="stable-name"` | A scroll container | Stable scroll-restoration identity |
|
|
101
100
|
| `pp-loading-content="true"` | The region swapped during navigation | Marks the navigation content region |
|
|
@@ -106,11 +105,11 @@ There is **no** `pp-if`, `pp-show`, `pp-else`, `pp-model`, `pp-bind`, `pp-class`
|
|
|
106
105
|
|
|
107
106
|
### Runtime-managed — never author these
|
|
108
107
|
|
|
109
|
-
The render pipeline or the browser runtime writes these. Handwriting them corrupts instance tracking:
|
|
110
|
-
|
|
111
|
-
`pp-component`, `
|
|
112
|
-
|
|
113
|
-
`pp-
|
|
108
|
+
The render pipeline or the browser runtime writes these. Handwriting them corrupts instance tracking or reconciliation:
|
|
109
|
+
|
|
110
|
+
`pp-component`, `pp-owner`, `pp-event-owner`, `pp-ref-owner`, `pp-ref-forward`, `<pp-context-provider>` (the generated tag), `data-pp-context-token`, `data-pp-context-value`, `data-pp-fragment-root`, `data-pp-script-source`, `data-pp-ref`, `data-pp-input-value`, `data-pp-default-value`, `data-pp-checked-value`, `data-pp-default-checked`, `data-pp-select-value`, `pp-keep`, `pp-keep-run`, and `pp-keep-content`.
|
|
111
|
+
|
|
112
|
+
The server also emits `meta[name="pp-root-layout"]` for SPA compatibility checks. These names may appear in rendered or transient runtime HTML, but none belongs in authored route or component templates.
|
|
114
113
|
|
|
115
114
|
### Component-script hooks (the whole list)
|
|
116
115
|
|
|
@@ -131,15 +130,15 @@ Inside an `on*` attribute the runtime provides `event`, plus the aliases `e`, `$
|
|
|
131
130
|
When documenting or generating PulsePoint code, follow this order:
|
|
132
131
|
|
|
133
132
|
- `public/js/pp-reactive-v2.js` is the shipped browser runtime contract AI should follow.
|
|
134
|
-
- `main.py` is the render-pipeline source of truth for
|
|
133
|
+
- `main.py` is the render-pipeline source of truth for component transformation, runtime-attribute injection, and final inert-template deferral.
|
|
135
134
|
- If you are working inside PulsePoint or Caspian runtime development code and there is an authoring source tree behind the shipped files, use it only as an implementation detail. Do not assume that source tree exists in generated apps or shipped framework output.
|
|
136
135
|
|
|
137
136
|
Important current facts:
|
|
138
137
|
|
|
139
138
|
- `public/js/pp-reactive-v2.js` exposes the global `pp` runtime and auto-mounts on DOM ready.
|
|
140
|
-
- `main.py` renders the final HTML,
|
|
141
|
-
- `.venv/Lib/site-packages/casp/components_compiler.py` injects `pp-component` on the final resolved root after component expansion.
|
|
142
|
-
-
|
|
139
|
+
- `main.py` renders the final HTML, transforms components, and defers outermost component roots inside inert templates before returning the response.
|
|
140
|
+
- `.venv/Lib/site-packages/casp/components_compiler.py` injects `pp-component` on the final resolved root after component expansion.
|
|
141
|
+
- `public/js/pp-reactive-v2.js` captures and empties plain component scripts before materialization or morph insertion, then evaluates their source in component scope.
|
|
143
142
|
- Authored route and component templates compose reusable server components as HTML-first `x-*` tags before the browser runtime mounts.
|
|
144
143
|
|
|
145
144
|
If docs, generated examples, or older notes disagree with `public/js/pp-reactive-v2.js` plus `main.py`, follow the code that actually runs.
|
|
@@ -359,7 +358,7 @@ Treat this section as the canonical authored-vs-runtime contract for Caspian tem
|
|
|
359
358
|
PulsePoint authoring is split into two layers:
|
|
360
359
|
|
|
361
360
|
- The authored layer: route, layout, and component templates under `src/` with plain HTML plus a plain `<script>`.
|
|
362
|
-
- The runtime layer: the browser sees `pp-component` roots
|
|
361
|
+
- The runtime layer: the browser sees `pp-component` roots with plain owned scripts whose source PulsePoint captures before the roots become live.
|
|
363
362
|
|
|
364
363
|
For authored Caspian templates:
|
|
365
364
|
|
|
@@ -367,7 +366,6 @@ For authored Caspian templates:
|
|
|
367
366
|
- In source, that parent may be a native HTML element or a single imported `x-*` component tag, but after component expansion it must resolve to one final HTML root.
|
|
368
367
|
- Put the component logic inside a plain `<script>` inside that same root.
|
|
369
368
|
- Do not handwrite `pp-component="..."`.
|
|
370
|
-
- Do not handwrite `type="text/pp"`.
|
|
371
369
|
|
|
372
370
|
Treat that single-root rule as a hard invariant for AI-generated templates. A sibling `<script>` after the root or any second top-level element will break Caspian's `pp-component` injection and fail the render.
|
|
373
371
|
|
|
@@ -398,7 +396,7 @@ Authored example:
|
|
|
398
396
|
</section>
|
|
399
397
|
```
|
|
400
398
|
|
|
401
|
-
When that template reaches the browser, Caspian
|
|
399
|
+
When that template reaches the browser, Caspian has injected the component id and deferred the root inside an inert template. PulsePoint captures the plain script source before materialization so native execution cannot occur, then evaluates that source in component scope.
|
|
402
400
|
|
|
403
401
|
Invalid authored shape:
|
|
404
402
|
|
|
@@ -429,7 +427,8 @@ Important:
|
|
|
429
427
|
## Component roots and scripts
|
|
430
428
|
|
|
431
429
|
- Each runtime component root should have at most one owned script.
|
|
432
|
-
-
|
|
430
|
+
- The owned script remains a plain, untyped `<script>`.
|
|
431
|
+
- Before a deferred root or later morph becomes live, PulsePoint captures and empties that script; the captured source is evaluated only through the component runtime.
|
|
433
432
|
- The script lookup walks the current root and skips nested `pp-component` boundaries, so a parent does not consume a child component's script.
|
|
434
433
|
- If multiple matching runtime scripts exist in the same root, the first matching owned script wins. Generate one script per root.
|
|
435
434
|
- Authored route, layout, and component templates still need one top-level parent node so Caspian can inject the component boundary correctly after component expansion.
|
|
@@ -444,18 +443,16 @@ Important:
|
|
|
444
443
|
- `pp.props.children` contains the root's initial inner HTML before the owned script is removed from the render template.
|
|
445
444
|
- Props are not auto-injected as standalone top-level template or handler variables. Read them through `pp.props` or explicitly destructure them in the component script.
|
|
446
445
|
|
|
447
|
-
Bindings exported to the template:
|
|
448
|
-
|
|
449
|
-
- Top-level function declarations.
|
|
450
|
-
- Top-level identifier declarations such as `const title = "Pulse"`.
|
|
451
|
-
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
-
|
|
457
|
-
- Nested declarations inside functions, conditions, loops, and callbacks are not auto-exported.
|
|
458
|
-
- There is no standalone `props` variable injected by the runtime. Use `pp.props`.
|
|
446
|
+
Bindings exported to the template:
|
|
447
|
+
|
|
448
|
+
- Top-level function declarations.
|
|
449
|
+
- Top-level identifier declarations such as `const title = "Pulse"`.
|
|
450
|
+
- Every identifier in a top-level destructuring pattern, regardless of initializer: arrays, objects, nested patterns, defaults, rest elements, and holes. This includes `const { title, subtitle } = pp.props`, `const [count, setCount] = pp.state(0)`, and tuples returned by any other hook or helper.
|
|
451
|
+
|
|
452
|
+
Bindings that should not be assumed:
|
|
453
|
+
|
|
454
|
+
- Nested declarations inside functions, conditions, loops, and callbacks are not auto-exported.
|
|
455
|
+
- There is no standalone `props` variable injected by the runtime. Use `pp.props`.
|
|
459
456
|
|
|
460
457
|
Example:
|
|
461
458
|
|
|
@@ -476,7 +473,7 @@ Example:
|
|
|
476
473
|
</div>
|
|
477
474
|
```
|
|
478
475
|
|
|
479
|
-
That is the authored form. In browser-inspected runtime HTML, Caspian will already have added the component id
|
|
476
|
+
That is the authored form. In browser-inspected runtime HTML, Caspian will already have added the component id; the owned script remains plain and is removed after PulsePoint captures its source.
|
|
480
477
|
|
|
481
478
|
## Hooks and runtime API
|
|
482
479
|
|
|
@@ -1195,7 +1192,8 @@ Example:
|
|
|
1195
1192
|
The render pipeline wraps each top-level `pp-component` root in an inert `<template pp-component="…">`, and the runtime materializes it back into live DOM on `mount()` (and on every SPA navigation) before it scans for roots. The browser never parses, validates, or fetches the contents of a `<template>`, so raw `{...}` placeholders never reach live DOM at first paint.
|
|
1196
1193
|
|
|
1197
1194
|
- Because of this, `{...}` is safe in **any** attribute or position — including slots the browser would otherwise validate eagerly: SVG geometry (`d`, `viewBox`, `points`, `transform`), URL attributes (`src`, `srcset`, `href`, `poster`), form `value` on `date`/`number`/`color` inputs, and text placed directly inside `<table>` or `<select>`.
|
|
1198
|
-
-
|
|
1195
|
+
- Before materialization, PulsePoint captures and empties each plain component script so inserting the fragment cannot trigger native browser execution; scripts introduced by later morphs receive the same treatment.
|
|
1196
|
+
- Do not add per-tag workarounds to dodge first-paint validation (static-path `hidden` toggles instead of binding `d`, `data-*` URL holders, `hidden`-gated `<img src>`, or an SSR-resolved initial value). The deferral removes the whole class at once.
|
|
1199
1197
|
- `pp-style` and the `<input>`/`<select>`/`checked`/`defaultvalue`/`<textarea>` value rewrites still apply — but for different reasons that deferral does not replace: authoring-source tooling (`style="{...}"` breaks HTML/CSS linters) and attribute-vs-property correctness for controlled form fields.
|
|
1200
1198
|
|
|
1201
1199
|
## Refs
|
|
@@ -1212,7 +1210,7 @@ The render pipeline wraps each top-level `pp-component` root in an inert `<templ
|
|
|
1212
1210
|
- Plain `pp-ref` bindings are preserved across rerenders, including no-op rerenders that skip DOM diffing.
|
|
1213
1211
|
- Ref callbacks may be called with `null` during cleanup.
|
|
1214
1212
|
- The runtime generates `data-pp-ref` internally. Do not author it.
|
|
1215
|
-
- Do not author `pp-event-owner`, `pp-owner`, or
|
|
1213
|
+
- Do not author `pp-event-owner`, `pp-owner`, or the other runtime-managed names listed above.
|
|
1216
1214
|
|
|
1217
1215
|
Example:
|
|
1218
1216
|
|
|
@@ -1342,10 +1340,10 @@ Example:
|
|
|
1342
1340
|
- 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(...)`.
|
|
1343
1341
|
- Do not replace a normal `onclick` with a `data-action`, `data-target`, or similar attribute plus a script that scans the DOM. PulsePoint event attributes are the event contract for first-party Caspian UI.
|
|
1344
1342
|
|
|
1345
|
-
## SPA, loading, and navigation helpers
|
|
1346
|
-
|
|
1347
|
-
- `body
|
|
1348
|
-
- `a[pp-spa="false"]` disables interception for that link.
|
|
1343
|
+
## SPA, loading, and navigation helpers
|
|
1344
|
+
|
|
1345
|
+
- `pp.mount()` enables client-side navigation interception automatically; no `<body pp-spa="true">` opt-in is required.
|
|
1346
|
+
- `a[pp-spa="false"]` disables interception for that link.
|
|
1349
1347
|
- External links, downloads, `_blank`, and modifier-key clicks bypass SPA interception.
|
|
1350
1348
|
- `pp-loading-content="true"` marks the page region that gets swapped or faded during navigation.
|
|
1351
1349
|
- Route-specific loading states are looked up with `pp-loading-url`.
|
|
@@ -1361,18 +1359,22 @@ Example:
|
|
|
1361
1359
|
|
|
1362
1360
|
RPC notes:
|
|
1363
1361
|
|
|
1364
|
-
- `pp.rpc(name, data?, optionsOrAbort?)` posts to the current route.
|
|
1365
|
-
- Passing `true` as the third argument means `abortPrevious: true`.
|
|
1366
|
-
- The options object supports `abortPrevious`, `onStream`, `onStreamError`, `onStreamComplete`, `onUploadProgress`, and `onUploadComplete`.
|
|
1367
|
-
-
|
|
1368
|
-
-
|
|
1369
|
-
-
|
|
1370
|
-
-
|
|
1371
|
-
-
|
|
1362
|
+
- `pp.rpc(name, data?, optionsOrAbort?)` posts to the current route.
|
|
1363
|
+
- Passing `true` as the third argument means `abortPrevious: true`.
|
|
1364
|
+
- The complete options object supports `abortPrevious`, `url`, `csrfUrl`, `credentials`, `onStream`, `onStreamError`, `onStreamComplete`, `onUploadProgress`, and `onUploadComplete`.
|
|
1365
|
+
- `url` overrides the current-route RPC URL. `csrfUrl` overrides the URL used for the preliminary CSRF-cookie bootstrap GET. `credentials` is a standard Fetch `RequestCredentials` value; same-origin calls default to `"same-origin"` and cross-origin overrides default to `"include"`.
|
|
1366
|
+
- When `abortPrevious` is enabled, a later aborting call cancels the active request and the cancelled promise resolves to `{ cancelled: true }`. A streamed response remains the active request until its final chunk, so it can still be cancelled after the response headers and early chunks have arrived.
|
|
1367
|
+
- File uploads switch to the XHR path when upload progress callbacks are needed.
|
|
1368
|
+
- A payload containing a `File` or non-empty `FileList` becomes multipart. The runtime writes every non-file field before the first file so a streaming server upload handler can read companion arguments even when the caller's object listed a file first. Objects are JSON-stringified, nullish fields are omitted, and a `FileList` becomes repeated parts under its original field name.
|
|
1369
|
+
- The `onUploadProgress` callback receives `{ loaded, total, percent }`. `percent` is a 0-100 number, and both `total` and `percent` are `null` when the browser cannot compute the upload length. Do not document or generate `event.percentage`.
|
|
1370
|
+
- `onUploadComplete` belongs to the same XHR progress path and runs only after a successful upload response (and any accepted redirect).
|
|
1371
|
+
- For file managers, use upload callbacks for progress UI but replace the asset list from returned RPC state with `pp.state(...)` and `pp-for` instead of manual DOM repainting. See [file-uploads.md](./file-uploads.md).
|
|
1372
|
+
- Streamed `text/event-stream` responses are supported when a stream handler is provided. Each `onStream` chunk is JSON-parsed when the event data looks like JSON; otherwise the raw string is passed through. If the response streams but no `onStream` handler was given, the runtime warns and discards the stream.
|
|
1373
|
+
- Same-origin `X-PP-Redirect` headers (and redirect-status `Location` headers) are honored through `pp.redirect()`. Invalid or cross-origin server redirect targets are ignored.
|
|
1372
1374
|
|
|
1373
1375
|
Notes:
|
|
1374
1376
|
|
|
1375
|
-
- `pp.redirect()` uses SPA navigation for same-origin URLs
|
|
1377
|
+
- After the runtime mounts, `pp.redirect()` uses SPA navigation for same-origin URLs. Cross-origin targets use normal browser navigation.
|
|
1376
1378
|
- Root-layout mismatches during SPA navigation trigger a hard reload.
|
|
1377
1379
|
- `pp.mount()` bootstraps every `[pp-component]` it finds, so generated code should call it only through the global runtime if you are manually mounting at all.
|
|
1378
1380
|
|
|
@@ -1430,7 +1432,7 @@ When you inspect rendered HTML in the browser, you should expect to see runtime-
|
|
|
1430
1432
|
Normal runtime output includes:
|
|
1431
1433
|
|
|
1432
1434
|
- `pp-component="..."` on mounted roots.
|
|
1433
|
-
-
|
|
1435
|
+
- plain owned component `<script>` elements during deferred bootstrap; the runtime removes them from the rendered component template after capturing their source.
|
|
1434
1436
|
- internal attributes such as captured ref tokens and event-owner bookkeeping.
|
|
1435
1437
|
|
|
1436
1438
|
These are runtime details.
|
|
@@ -1449,9 +1451,9 @@ Use these rules when generating or editing PulsePoint runtime code:
|
|
|
1449
1451
|
- For simple forms, bind `onsubmit` in the authored HTML and read named fields with `Object.fromEntries(new FormData(event.currentTarget).entries())`; do not generate per-input refs or effect-managed submit listeners just to gather values.
|
|
1450
1452
|
- Treat `pp.rpc()` as the default browser-to-server path for CRUD operations and interactive backend reads.
|
|
1451
1453
|
- Use `public/js/pp-reactive-v2.js` as the shipped runtime contract AI should follow.
|
|
1452
|
-
- Keep `main.py` in view because it injects
|
|
1454
|
+
- Keep `main.py` in view because it injects runtime-facing component attributes and defers roots before the browser sees live component DOM.
|
|
1453
1455
|
- 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.
|
|
1454
|
-
- In authored Caspian templates, do not handwrite `pp-component
|
|
1456
|
+
- In authored Caspian templates, do not handwrite `pp-component`; let the render pipeline inject it, and keep owned logic in a plain `<script>`.
|
|
1455
1457
|
- 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`.
|
|
1456
1458
|
- 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.
|
|
1457
1459
|
- Prefer PulsePoint state and template directives over manual DOM mutation for reactive updates.
|
|
@@ -1462,7 +1464,7 @@ Use these rules when generating or editing PulsePoint runtime code:
|
|
|
1462
1464
|
- Avoid generating ids, `data-*` state, `querySelector`, `getElementById`, `addEventListener`, manual `innerHTML`, or custom event buses for normal Caspian UI behavior.
|
|
1463
1465
|
- Avoid data-attribute click wiring such as `data-action="save"` plus a delegated listener. Use `onclick="save()"` or `onsubmit="{save(event)}"` in authored HTML and keep reactive state in `pp.state(...)`.
|
|
1464
1466
|
- If you are explicitly editing raw runtime HTML or internals, keep `pp-component` unique per live instance.
|
|
1465
|
-
- In authored templates, use a plain `<script>` inside the root.
|
|
1467
|
+
- In authored templates, use a plain, untyped `<script>` inside the root. PulsePoint safely captures it during deferred bootstrap and morph insertion.
|
|
1466
1468
|
- Keep template-facing variables at top level.
|
|
1467
1469
|
- Follow the HTML-first context pattern: `pp.createContext(...)`, a lowercase provider tag such as `<themecontext.provider value="{...}">`, and `pp.context(token)`.
|
|
1468
1470
|
- Do not invent `pp.provideContext`, `pp-context`, or other legacy context helpers.
|
|
@@ -1492,20 +1494,14 @@ JSX constructs in markup — these are the highest-frequency generation failure,
|
|
|
1492
1494
|
|
|
1493
1495
|
Also avoid:
|
|
1494
1496
|
|
|
1495
|
-
- React, Vue, Svelte, Alpine, HTMX, or JSX-first patterns as the default Caspian frontend approach
|
|
1496
|
-
- 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
|
|
1497
|
-
- `pp-context`
|
|
1498
|
-
- `pp-key`
|
|
1499
|
-
-
|
|
1500
|
-
- `pp-
|
|
1501
|
-
- `pp
|
|
1502
|
-
-
|
|
1503
|
-
- `pp-dynamic-script`
|
|
1504
|
-
- `pp-dynamic-meta`
|
|
1505
|
-
- `pp-dynamic-link`
|
|
1506
|
-
- handwritten `pp-component="..."` or `type="text/pp"` in authored route, layout, or component templates
|
|
1507
|
-
- `pp.fetchFunction()` as the current raw runtime helper name
|
|
1508
|
-
- made-up hooks, directives, or globals not present in the current bundled runtime
|
|
1497
|
+
- React, Vue, Svelte, Alpine, HTMX, or JSX-first patterns as the default Caspian frontend approach
|
|
1498
|
+
- 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
|
|
1499
|
+
- `pp-context`
|
|
1500
|
+
- `pp-key`
|
|
1501
|
+
- any runtime-managed name listed in [Runtime-managed — never author these](#runtime-managed--never-author-these)
|
|
1502
|
+
- handwritten `pp-component="..."` in authored route, layout, or component templates
|
|
1503
|
+
- `pp.fetchFunction()` as the current raw runtime helper name
|
|
1504
|
+
- made-up hooks, directives, or globals not present in the current bundled runtime
|
|
1509
1505
|
|
|
1510
1506
|
## Review notes
|
|
1511
1507
|
|
|
@@ -1513,8 +1509,9 @@ These are current runtime caveats that matter for authors and AI tools:
|
|
|
1513
1509
|
|
|
1514
1510
|
- `pp-component` is the registry key for instances, state, parent tracking, and templates. Treat it as unique per mounted root.
|
|
1515
1511
|
- `public/js/pp-reactive-v2.js` is the runtime surface that ships and should be assumed to exist.
|
|
1516
|
-
- Caspian
|
|
1517
|
-
-
|
|
1512
|
+
- Caspian injects `pp-component` and preserves owned scripts as plain elements inside deferred roots.
|
|
1513
|
+
- PulsePoint captures and empties plain scripts before a deferred root or later morph becomes live, preventing native browser execution while preserving component-scope evaluation.
|
|
1514
|
+
- Nested roots without their own plain owned `<script>` block are not fully isolated during parent template compilation.
|
|
1518
1515
|
- The global `pp` singleton auto-mounts on DOM ready, and `pp.mount()` is idempotent.
|
|
1519
1516
|
- `pp.effect` and `pp.layoutEffect` are cleanup-style hooks. Their callbacks are not promise-aware.
|
|
1520
1517
|
- Callback refs may return synchronous cleanup functions, which run instead of a later `callback(null)` detach call.
|
package/dist/docs/routing.md
CHANGED
|
@@ -185,7 +185,7 @@ Place those import comments at the top of the file, above the authored root elem
|
|
|
185
185
|
|
|
186
186
|
Keep route templates as composition, not as the place where all section markup accumulates. A route with tabs, dashboard panels, forms, tables, or repeated cards should import focused components for those responsibilities and assemble them with `x-*` tags. For example, a dashboard route can import `<x-dashboard-header />`, `<x-metric-strip />`, `<x-activity-tab />`, and `<x-settings-tab />` rather than placing every tab panel's full markup in `index.html` or one giant Python component.
|
|
187
187
|
|
|
188
|
-
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
|
|
188
|
+
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`. 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.
|
|
189
189
|
|
|
190
190
|
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(...)`. For simple form submits, prefer `onsubmit="{submitForm(event)}"` and `Object.fromEntries(new FormData(event.currentTarget).entries())` over per-input `pp-ref` payload collection.
|
|
191
191
|
|
|
@@ -389,7 +389,7 @@ Resolved SEO fields are exposed to layouts as `{{ metadata.* }}`, while values r
|
|
|
389
389
|
|
|
390
390
|
Use `layout.html` for the shared wrapper markup of a subtree. Keep the visible shell here, not in `layout.py`.
|
|
391
391
|
|
|
392
|
-
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
|
|
392
|
+
Follow the same authoring contract used by route templates: one authored parent node, top-of-file `<!-- @import ... -->` directives above that root, a plain `<script>` inside the root when needed, and no handwritten `pp-component`. See [pulsepoint.md](./pulsepoint.md) for the canonical authored-vs-runtime explanation.
|
|
393
393
|
|
|
394
394
|
Place child routes with a plain HTML `<slot />` tag. Caspian replaces that layout slot with the current child route or nested layout while rendering.
|
|
395
395
|
|