caspian-utils 0.1.26 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/docs/auth.md +7 -6
- package/dist/docs/components.md +281 -213
- package/dist/docs/core-runtime-map.md +4 -4
- package/dist/docs/fetch-data.md +19 -1
- package/dist/docs/project-structure.md +62 -62
- package/dist/docs/pulsepoint-runtime-map.md +63 -17
- package/dist/docs/pulsepoint.md +183 -5
- package/package.json +1 -1
|
@@ -30,7 +30,7 @@ Use it when you already know a behavior is controlled by `main.py` or `.venv/Lib
|
|
|
30
30
|
|
|
31
31
|
| Concern | Core file | Read first | Why it matters |
|
|
32
32
|
| --- | --- | --- | --- |
|
|
33
|
-
| App bootstrap and request flow | [main.py](../../../../main.py) plus imported runtime helpers such as `casp.runtime_security` | [project-structure.md](./project-structure.md), [routing.md](./routing.md), [auth.md](./auth.md) | FastAPI app creation, middleware wiring, route registration, cache check and save, exception handlers, and final HTML transforms live here. Package-owned helpers imported by `main.py` may own safe public-file serving, baseline
|
|
33
|
+
| App bootstrap and request flow | [main.py](../../../../main.py) plus imported runtime helpers such as `casp.runtime_security` | [project-structure.md](./project-structure.md), [routing.md](./routing.md), [auth.md](./auth.md) | FastAPI app creation, middleware wiring, route registration, cache check and save, exception handlers, and final HTML transforms live here. Package-owned helpers imported by `main.py` may own safe public-file serving, baseline response headers including the Content-Security-Policy, production-safe error messages, fail-closed environment resolution, or production session-secret enforcement. |
|
|
34
34
|
| Browser runtime, SPA navigation, and scroll restoration | [public/js/pp-reactive-v2.js](../../../../public/js/pp-reactive-v2.js) | [pulsepoint.md](./pulsepoint.md), [fetch-data.md](./fetch-data.md) | The shipped `pp` runtime, same-origin SPA interception, per-history-entry scroll state, `pp-reset-scroll` behavior, and browser RPC helpers live here. |
|
|
35
35
|
|
|
36
36
|
Important current `main.py` behaviors AI should keep in mind:
|
|
@@ -41,8 +41,8 @@ Important current `main.py` behaviors AI should keep in mind:
|
|
|
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` and should reject `..` traversal before serving from `public/**`.
|
|
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.
|
|
44
|
-
- Baseline
|
|
45
|
-
- Middleware is added in source order as `RPCMiddleware`, `AuthMiddleware`, `CSRFMiddleware`, `SessionMiddleware`, `SecurityHeadersMiddleware`,
|
|
44
|
+
- Baseline response headers are built by `casp.runtime_security` and attached through `SecurityHeadersMiddleware`. That set now includes a Content-Security-Policy from `build_content_security_policy()`, which the `CONTENT_SECURITY_POLICY` environment variable replaces wholesale. The policy must keep `'unsafe-eval'` and `'unsafe-inline'` in `script-src`, because the PulsePoint runtime compiles templates with `new Function`; read the current helper before tightening it. Outside production the helper also adds loopback origins to `connect-src` so the BrowserSync live-reload client, which polls a different port than the proxied page, is not blocked; an environment override replaces that too.
|
|
45
|
+
- Middleware is added in source order as `RPCMiddleware`, `AuthMiddleware`, `CSRFMiddleware`, `SessionMiddleware`, `BodySizeLimitMiddleware`, `RateLimitMiddleware`, `SecurityHeadersMiddleware`, plus `RequestDiagnosticsMiddleware` outside production. The effective request order is reversed at runtime, so security headers and the rate limit run first and `RPCMiddleware` runs last. Verify the current list in `main.py` rather than trusting this order.
|
|
46
46
|
- `public/js/pp-reactive-v2.js` saves scroll positions per history entry, resets window scroll on push navigation, and uses `pp-reset-scroll="true"` to opt specific containers or the whole body into reset behavior.
|
|
47
47
|
|
|
48
48
|
## Caspian Core Feature Map
|
|
@@ -92,7 +92,7 @@ Interactive CRUD page:
|
|
|
92
92
|
| --- | --- | --- |
|
|
93
93
|
| [.venv/Lib/site-packages/casp/layout.py](../../../../.venv/Lib/site-packages/casp/layout.py) | `render_page(...)`, `render_layout(...)`, nested layout discovery, metadata merge, sync or async `layout()` results, and parser-based `<slot />` replacement | [routing.md](./routing.md), [metadata.md](./metadata.md) |
|
|
94
94
|
| [.venv/Lib/site-packages/casp/auth.py](../../../../.venv/Lib/site-packages/casp/auth.py) | `AuthSettings`, route privacy checks, session payloads, OAuth providers, CSRF helper behavior, and redirect logic | [auth.md](./auth.md) |
|
|
95
|
-
| [.venv/Lib/site-packages/casp/runtime_security.py](../../../../.venv/Lib/site-packages/casp/runtime_security.py) | safe public-file serving, baseline
|
|
95
|
+
| [.venv/Lib/site-packages/casp/runtime_security.py](../../../../.venv/Lib/site-packages/casp/runtime_security.py) | safe public-file serving (with optional attachment mode for user uploads), baseline response headers including the CSP, production-safe error messages, fail-closed `APP_ENV` resolution via `is_production_environment()`, and production session-secret enforcement used by `main.py` | [project-structure.md](./project-structure.md), [auth.md](./auth.md) |
|
|
96
96
|
| [.venv/Lib/site-packages/casp/rpc.py](../../../../.venv/Lib/site-packages/casp/rpc.py) | `@rpc()` registration, rate limits, request handling, auth-aware action checks, and streamed RPC responses | [fetch-data.md](./fetch-data.md) |
|
|
97
97
|
| [.venv/Lib/site-packages/casp/streaming.py](../../../../.venv/Lib/site-packages/casp/streaming.py) | `SSE`, `ServerSentEvent`, and generator-to-event-stream wrapping | [fetch-data.md](./fetch-data.md) |
|
|
98
98
|
| [.venv/Lib/site-packages/casp/state_manager.py](../../../../.venv/Lib/site-packages/casp/state_manager.py) | request-scoped state, session bucket persistence, listener lifecycle, and wire-request reset behavior | [state.md](./state.md) |
|
package/dist/docs/fetch-data.md
CHANGED
|
@@ -356,7 +356,25 @@ Use [auth.md](./auth.md) when the action should also participate in centralized
|
|
|
356
356
|
|
|
357
357
|
According to the upstream Caspian RPC docs, actions are private by default until decorated with `@rpc()`, and the framework includes CSRF protection plus origin validation for exposed actions.
|
|
358
358
|
|
|
359
|
-
For reverse-proxy deployments, RPC origin validation
|
|
359
|
+
For reverse-proxy deployments, RPC origin validation resolves the public app origin from `APP_BASE_URL` and `CORS_ALLOWED_ORIGINS`. Forwarded host and protocol headers are consulted **only** when `TRUST_FORWARDED_HEADERS` is enabled, because a direct client can set those headers itself; the same flag decides whether the forwarded address is used as the rate-limit bucket key.
|
|
360
|
+
|
|
361
|
+
### Only Declared Parameters Are Accepted
|
|
362
|
+
|
|
363
|
+
The RPC payload is attacker-controlled, so the runtime filters it against the function signature before the call. A key that does not name a declared parameter is dropped rather than passed through.
|
|
364
|
+
|
|
365
|
+
This makes a parameter client-settable only when you declare it. Anything the server should decide — the acting user, an ownership check, a privilege flag — must be derived inside the function rather than accepted as an argument with a default:
|
|
366
|
+
|
|
367
|
+
```python
|
|
368
|
+
@rpc(require_auth=True)
|
|
369
|
+
def update_post(post_id: int, title: str = ""):
|
|
370
|
+
# Correct: identity comes from the session, not the payload.
|
|
371
|
+
author = auth.get_payload()
|
|
372
|
+
|
|
373
|
+
# If this were `def update_post(..., author_id: str = "")`, the browser
|
|
374
|
+
# could set author_id and edit another user's post.
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
Declaring `**kwargs` opts the function into the entire payload, so use it deliberately. A sent-but-undeclared key is silently ignored, which also means a client-side typo shows up as a missing value rather than a 500.
|
|
360
378
|
|
|
361
379
|
## Serialization Rules
|
|
362
380
|
|
|
@@ -31,7 +31,7 @@ In that layout, the default stack is Python components for reusable UI, PulsePoi
|
|
|
31
31
|
|
|
32
32
|
For public pages that can safely reuse rendered HTML, Caspian also supports route-level page caching through `casp.cache_handler`.
|
|
33
33
|
|
|
34
|
-
Before an AI agent decides which Caspian features are available in a workspace, it should read `./caspian.config.json` almost immediately. That file is the feature gate for project capabilities such as `backendOnly`, `tailwindcss`, `mcp`, `prisma`, `typescript`, `websocket`, and `componentScanDirs`.
|
|
34
|
+
Before an AI agent decides which Caspian features are available in a workspace, it should read `./caspian.config.json` almost immediately. That file is the feature gate for project capabilities such as `backendOnly`, `tailwindcss`, `mcp`, `prisma`, `typescript`, `websocket`, and `componentScanDirs`.
|
|
35
35
|
|
|
36
36
|
Treat `caspian.config.json` as the single source of truth for optional feature enablement. Use feature-specific files and docs only after the matching flag is confirmed as 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`.
|
|
37
37
|
|
|
@@ -40,10 +40,10 @@ Treat `caspian.config.json` as the single source of truth for optional feature e
|
|
|
40
40
|
- `src/` contains routes, page templates, styles, reusable components, and shared libraries.
|
|
41
41
|
- `src/components/` contains reusable application UI components and optional same-name HTML templates.
|
|
42
42
|
- `src/lib/` contains reusable non-UI code such as helpers, services, validators, adapters, and shared support modules.
|
|
43
|
-
- `src/lib/auth/auth_config.py` contains auth-specific configuration for the app.
|
|
44
|
-
- `src/lib/mcp/` contains the app-owned FastMCP server and nested FastMCP config when MCP is enabled.
|
|
45
|
-
- `src/lib/websocket/` contains reusable socket helpers when WebSockets are enabled and the project includes shared session, auth, connection, or broadcast utilities.
|
|
46
|
-
- `prisma/` contains the Prisma schema and seed scripts.
|
|
43
|
+
- `src/lib/auth/auth_config.py` contains auth-specific configuration for the app.
|
|
44
|
+
- `src/lib/mcp/` contains the app-owned FastMCP server and nested FastMCP config when MCP is enabled.
|
|
45
|
+
- `src/lib/websocket/` contains reusable socket helpers when WebSockets are enabled and the project includes shared session, auth, connection, or broadcast utilities.
|
|
46
|
+
- `prisma/` contains the Prisma schema and seed scripts.
|
|
47
47
|
- `public/` contains static assets served directly.
|
|
48
48
|
- `settings/` contains BrowserSync, build, restart, and generated-project helper files.
|
|
49
49
|
- `main.py` is the application entry point.
|
|
@@ -78,15 +78,15 @@ my-app/
|
|
|
78
78
|
lib/
|
|
79
79
|
auth/
|
|
80
80
|
auth_config.py
|
|
81
|
-
mcp/
|
|
82
|
-
fastmcp.json
|
|
83
|
-
mcp_server.py
|
|
84
|
-
websocket/
|
|
85
|
-
websocket_security.py
|
|
86
|
-
prisma/
|
|
87
|
-
__init__.py
|
|
88
|
-
db.py
|
|
89
|
-
models.py
|
|
81
|
+
mcp/
|
|
82
|
+
fastmcp.json
|
|
83
|
+
mcp_server.py
|
|
84
|
+
websocket/
|
|
85
|
+
websocket_security.py
|
|
86
|
+
prisma/
|
|
87
|
+
__init__.py
|
|
88
|
+
db.py
|
|
89
|
+
models.py
|
|
90
90
|
.venv/
|
|
91
91
|
Lib/
|
|
92
92
|
site-packages/
|
|
@@ -97,7 +97,7 @@ my-app/
|
|
|
97
97
|
docs/
|
|
98
98
|
```
|
|
99
99
|
|
|
100
|
-
Optional directories such as `src/lib/mcp/` and `src/lib/websocket/` appear only when the relevant feature flag is enabled in `caspian.config.json` and the project needs that app-owned surface.
|
|
100
|
+
Optional directories such as `src/lib/mcp/` and `src/lib/websocket/` appear only when the relevant feature flag is enabled in `caspian.config.json` and the project needs that app-owned surface.
|
|
101
101
|
|
|
102
102
|
## Directory Breakdown
|
|
103
103
|
|
|
@@ -107,11 +107,11 @@ This is the main application area. It contains route files, templates, styles, a
|
|
|
107
107
|
|
|
108
108
|
### `src/app/`
|
|
109
109
|
|
|
110
|
-
This directory handles file-based routing. Route templates and route-specific backend logic live here.
|
|
111
|
-
|
|
112
|
-
For any route that renders UI, keep that markup in `src/app/**/index.html`. If the route is UI-only, `index.html` alone is enough. Add `src/app/**/index.py` only as a companion when the same route needs metadata, `page()`, `@rpc()` actions, auth checks, caching, redirects, or other server-side behavior. Keep shared wrappers in `layout.html` and use `layout.py` only for shared props or metadata. Use a lone `index.py` only for non-visual routes such as redirect-only or action-only handlers.
|
|
113
|
-
|
|
114
|
-
Keep backend logic in the owning route when it is route-specific. Move Python code into `src/lib/**` only when it is shared across routes, components, integrations, or features.
|
|
110
|
+
This directory handles file-based routing. Route templates and route-specific backend logic live here.
|
|
111
|
+
|
|
112
|
+
For any route that renders UI, keep that markup in `src/app/**/index.html`. If the route is UI-only, `index.html` alone is enough. Add `src/app/**/index.py` only as a companion when the same route needs metadata, `page()`, `@rpc()` actions, auth checks, caching, redirects, or other server-side behavior. Keep shared wrappers in `layout.html` and use `layout.py` only for shared props or metadata. Use a lone `index.py` only for non-visual routes such as redirect-only or action-only handlers.
|
|
113
|
+
|
|
114
|
+
Keep backend logic in the owning route when it is route-specific. Move Python code into `src/lib/**` only when it is shared across routes, components, integrations, or features.
|
|
115
115
|
|
|
116
116
|
When a folder represents a section with child routes, such as `dashboard`, `account`, `settings`, or `docs`, create `layout.html` in that folder and let the child routes live beneath it. See [routing.md](./routing.md) for the canonical section layout pattern.
|
|
117
117
|
|
|
@@ -125,11 +125,11 @@ See `routing.md` for the full App Router-style rules for dynamic segments, route
|
|
|
125
125
|
|
|
126
126
|
Use this folder for reusable UI components that should be imported into route templates or other component templates.
|
|
127
127
|
|
|
128
|
-
As the app grows, default to `src/components/` for application-level UI that will be shared across routes or features. Keep page-only markup close to the route in `src/app/`, but move shared cards, forms, shells, navigation, and other reusable visual building blocks into `src/components/`.
|
|
129
|
-
|
|
130
|
-
For page-specific chunks that are still substantial, a route-local component folder is also acceptable. Use this when a component is owned by one route but deserves its own focused file, such as one tab panel, one settings form, one analytics section, or one table toolbar. The route should still read as a short assembly of named `x-*` tags.
|
|
131
|
-
|
|
132
|
-
The common Caspian pattern is a Python file such as `Button.py` with `@component`, optionally paired with a same-name HTML file such as `Button.html` when the component has richer markup or PulsePoint behavior. In authored HTML, that component is consumed as `<x-button />`.
|
|
128
|
+
As the app grows, default to `src/components/` for application-level UI that will be shared across routes or features. Keep page-only markup close to the route in `src/app/`, but move shared cards, forms, shells, navigation, and other reusable visual building blocks into `src/components/`.
|
|
129
|
+
|
|
130
|
+
For page-specific chunks that are still substantial, a route-local component folder is also acceptable. Use this when a component is owned by one route but deserves its own focused file, such as one tab panel, one settings form, one analytics section, or one table toolbar. The route should still read as a short assembly of named `x-*` tags.
|
|
131
|
+
|
|
132
|
+
The common Caspian pattern is a Python file such as `Button.py` with `@component`, optionally paired with a same-name HTML file such as `Button.html` when the component has richer markup or PulsePoint behavior. In authored HTML, that component is consumed as `<x-button />`.
|
|
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
|
|
|
@@ -137,7 +137,7 @@ For component HTML files, follow the component authoring rules in [components.md
|
|
|
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
|
|
|
140
|
-
### `src/lib/`
|
|
140
|
+
### `src/lib/`
|
|
141
141
|
|
|
142
142
|
Use this folder for shared helpers, reusable validators, RPC-facing service wrappers, data-access helpers, formatting utilities, and other app-level support code that is not itself a reusable rendered component.
|
|
143
143
|
|
|
@@ -147,15 +147,15 @@ For file upload and manager flows, keep route-owned `@rpc()` actions in `src/app
|
|
|
147
147
|
|
|
148
148
|
When a project includes an app-owned Python database layer under `src/lib/prisma/`, reuse that package for Python-side data access and keep any additional shared database helpers in `src/lib/`.
|
|
149
149
|
|
|
150
|
-
When MCP is enabled for the project, this folder also contains the app-owned FastMCP server under `src/lib/mcp/`.
|
|
151
|
-
|
|
152
|
-
Do not add `src/lib/security/runtime_security.py` for normal app work. Runtime security helpers for safe public-file serving, production session-secret enforcement, production-safe error messages, and baseline
|
|
150
|
+
When MCP is enabled for the project, this folder also contains the app-owned FastMCP server under `src/lib/mcp/`.
|
|
151
|
+
|
|
152
|
+
Do not add `src/lib/security/runtime_security.py` for normal app work. Runtime security helpers for safe public-file serving, production session-secret enforcement, production-safe error messages, fail-closed environment resolution, and baseline response headers including the Content-Security-Policy are package-owned by `casp.runtime_security`.
|
|
153
153
|
|
|
154
154
|
### Shared Database Helpers
|
|
155
155
|
|
|
156
156
|
If your Python routes or RPC actions need reusable database access code, keep that helper layer under `src/lib/` and extend the existing `src/lib/prisma/` package.
|
|
157
157
|
|
|
158
|
-
In a Prisma-enabled Caspian project, schema and seed files typically live under `prisma/`, while the Python-side adapter lives in application-owned code under `src/lib/prisma/` when that layer exists. When `caspian.config.json` has `prisma: true`, Python-side database reads and writes should use that generated Prisma Python ORM instead of a custom fetch, raw driver, JSON active store, or second app-owned database abstraction.
|
|
158
|
+
In a Prisma-enabled Caspian project, schema and seed files typically live under `prisma/`, while the Python-side adapter lives in application-owned code under `src/lib/prisma/` when that layer exists. When `caspian.config.json` has `prisma: true`, Python-side database reads and writes should use that generated Prisma Python ORM instead of a custom fetch, raw driver, JSON active store, or second app-owned database abstraction.
|
|
159
159
|
|
|
160
160
|
### `src/lib/auth/`
|
|
161
161
|
|
|
@@ -194,31 +194,31 @@ If the local BrowserSync stack is running, keep that upload directory in `settin
|
|
|
194
194
|
|
|
195
195
|
The application entry point for the project.
|
|
196
196
|
|
|
197
|
-
In the current Caspian app shape, this file is where startup wiring happens:
|
|
198
|
-
|
|
199
|
-
- load environment variables
|
|
200
|
-
- call `configure_auth(build_auth_settings())`
|
|
201
|
-
- register OAuth providers with `Auth.set_providers(...)`
|
|
202
|
-
- create the FastAPI app
|
|
203
|
-
- register routes and RPC handlers
|
|
204
|
-
- add response headers middleware, `SessionMiddleware`, CSRF middleware, auth middleware, and RPC middleware
|
|
205
|
-
|
|
206
|
-
Use `main.py` for auth bootstrap and middleware-order changes. Use `src/lib/auth/auth_config.py` for auth policy values such as public routes, redirects, and RBAC maps.
|
|
207
|
-
|
|
208
|
-
### `.venv/Lib/site-packages/casp/runtime_security.py`
|
|
209
|
-
|
|
210
|
-
The installed Caspian package owns runtime security helpers that `main.py` can import as `casp.runtime_security`. Keep this file in the package instead of copying it into `src/lib/`, because normal app users should not need to edit it.
|
|
211
|
-
|
|
212
|
-
This helper is not a registry for third-party browser resources. Do not add Google, YouTube, CDN, image-host, API, or iframe origins here just because the browser or server uses them. If an app later chooses to enforce a Content Security Policy, document and implement that as an explicit project policy rather than assuming `runtime_security.py` owns a domain allowlist.
|
|
213
|
-
|
|
214
|
-
Use this package module when the task is about:
|
|
215
|
-
|
|
216
|
-
- safe serving of files from `public/**`
|
|
217
|
-
- production session-secret enforcement
|
|
218
|
-
- user-facing vs production-safe exception messaging helpers
|
|
219
|
-
- baseline response headers such as permissions, referrer, MIME sniffing, framing, or HSTS behavior
|
|
220
|
-
|
|
221
|
-
Because this file is framework-owned, edit it only for Caspian runtime work or when documentation must match the installed package. App-specific auth policy still belongs in `src/lib/auth/auth_config.py`, and app-specific upload or storage behavior should live in route-owned code or other `src/lib/**` helpers.
|
|
197
|
+
In the current Caspian app shape, this file is where startup wiring happens:
|
|
198
|
+
|
|
199
|
+
- load environment variables
|
|
200
|
+
- call `configure_auth(build_auth_settings())`
|
|
201
|
+
- register OAuth providers with `Auth.set_providers(...)`
|
|
202
|
+
- create the FastAPI app
|
|
203
|
+
- register routes and RPC handlers
|
|
204
|
+
- add response headers middleware, `SessionMiddleware`, CSRF middleware, auth middleware, and RPC middleware
|
|
205
|
+
|
|
206
|
+
Use `main.py` for auth bootstrap and middleware-order changes. Use `src/lib/auth/auth_config.py` for auth policy values such as public routes, redirects, and RBAC maps.
|
|
207
|
+
|
|
208
|
+
### `.venv/Lib/site-packages/casp/runtime_security.py`
|
|
209
|
+
|
|
210
|
+
The installed Caspian package owns runtime security helpers that `main.py` can import as `casp.runtime_security`. Keep this file in the package instead of copying it into `src/lib/`, because normal app users should not need to edit it.
|
|
211
|
+
|
|
212
|
+
This helper is not a registry for third-party browser resources. Do not add Google, YouTube, CDN, image-host, API, or iframe origins here just because the browser or server uses them. If an app later chooses to enforce a Content Security Policy, document and implement that as an explicit project policy rather than assuming `runtime_security.py` owns a domain allowlist.
|
|
213
|
+
|
|
214
|
+
Use this package module when the task is about:
|
|
215
|
+
|
|
216
|
+
- safe serving of files from `public/**`
|
|
217
|
+
- production session-secret enforcement
|
|
218
|
+
- user-facing vs production-safe exception messaging helpers
|
|
219
|
+
- baseline response headers such as permissions, referrer, MIME sniffing, framing, or HSTS behavior
|
|
220
|
+
|
|
221
|
+
Because this file is framework-owned, edit it only for Caspian runtime work or when documentation must match the installed package. App-specific auth policy still belongs in `src/lib/auth/auth_config.py`, and app-specific upload or storage behavior should live in route-owned code or other `src/lib/**` helpers.
|
|
222
222
|
|
|
223
223
|
### `caspian.config.json`
|
|
224
224
|
|
|
@@ -268,7 +268,7 @@ Keep visible wrapper markup in `layout.html`, not in `layout.py`.
|
|
|
268
268
|
|
|
269
269
|
### `src/app/layout.py`
|
|
270
270
|
|
|
271
|
-
The backend companion for a layout. Use this file for shared props, metadata defaults, and other server-side preparation for the sibling `layout.html`.
|
|
271
|
+
The backend companion for a layout. Use this file for shared props, metadata defaults, and other server-side preparation for the sibling `layout.html`.
|
|
272
272
|
|
|
273
273
|
Do not store layout HTML in `layout.py`. Keep the authored wrapper in `layout.html` and let `layout.py` return props or metadata.
|
|
274
274
|
|
|
@@ -330,17 +330,17 @@ If an AI agent is deciding where to make changes, use these rules first.
|
|
|
330
330
|
- Treat `package.json` scripts as opt-in operations. Do not run `npm run dev` or `npm run build` unless the user explicitly asks, the task genuinely requires that exact script, or deployment prep needs `npm run build`.
|
|
331
331
|
- Treat `__pycache__/` directories, `.pyc` files, `public/css/styles.css`, `settings/component-map.json`, and `settings/files-list.json` as generated artifacts when the local stack is intentionally running. They are not authored source files.
|
|
332
332
|
- Inspect `settings/component-map.json` and `settings/files-list.json` when you need the generated component or route inventory, but do not hand-edit them. The workspace regenerates them from `settings/component-map.ts` and `settings/files-list.ts`.
|
|
333
|
-
- Put route templates and route-specific backend logic in `src/app/`.
|
|
334
|
-
- Put only genuinely shared helpers, services, adapters, and validation logic in `src/lib/`.
|
|
333
|
+
- Put route templates and route-specific backend logic in `src/app/`.
|
|
334
|
+
- Put only genuinely shared helpers, services, adapters, and validation logic in `src/lib/`.
|
|
335
335
|
- As the app grows, keep route-owned code in `src/app/`, reusable rendered UI in `src/components/`, and reusable non-UI support code in `src/lib/`.
|
|
336
336
|
- When the user asks for a dashboard, admin area, account area, or any grouped set of child routes, create a parent folder in `src/app/` with `layout.html` and place the child routes beneath it. Use `(group)/layout.html` only when that parent should not appear in the URL.
|
|
337
337
|
- Read [file-uploads.md](./file-uploads.md) when the task involves upload widgets, media libraries, or file-manager flows.
|
|
338
338
|
- Check [routing.md](./routing.md) when you need URL segment rules, layout nesting behavior, or dynamic route conventions.
|
|
339
|
-
- Put reusable component files in `src/components/` and check [components.md](./components.md) for `@component`, `render_html(__file__)`, `x-*` component tags, import comments, and single-root template rules.
|
|
340
|
-
- Split UI by component responsibility before files become large. A tab panel, settings form, table section, toolbar, or repeated card/list item can be its own component even when it is only used by one route.
|
|
341
|
-
- 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.
|
|
339
|
+
- Put reusable component files in `src/components/` and check [components.md](./components.md) for `@component`, `render_html(__file__)`, `x-*` component tags, import comments, and single-root template rules.
|
|
340
|
+
- Split UI by component responsibility before files become large. A tab panel, settings form, table section, toolbar, or repeated card/list item can be its own component even when it is only used by one route.
|
|
341
|
+
- 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.
|
|
342
342
|
- 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.
|
|
343
|
-
- 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.
|
|
343
|
+
- 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.
|
|
344
344
|
- 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` or `type="text/pp"` in source templates.
|
|
345
345
|
- Put shared helpers and reusable libraries in `src/lib/`.
|
|
346
346
|
- Use `settings/bs-config.ts` when uploaded public assets should not trigger BrowserSync reloads during the local stack.
|
|
@@ -31,23 +31,31 @@ If an inspected browser DOM disagrees with authored template source, remember th
|
|
|
31
31
|
|
|
32
32
|
| PulsePoint feature | Authoring surface | Runtime owner | Verify before changing |
|
|
33
33
|
| --- | --- | --- | --- |
|
|
34
|
-
| Component roots | `src/app/**/index.html`, `layout.html`, component `.html` files | `components_compiler.py`, `pp-reactive-v2.js` | one authored root, final expanded root receives one `pp-component`, no sibling scripts |
|
|
35
|
-
| Component scripts | plain `<script>` inside the authored root | `scripts_type.py`, `pp-reactive-v2.js` | authored scripts are plain, runtime scripts become `type="text/pp"`, one owned script per root |
|
|
36
|
-
| Template expressions | text and attributes with `{...}` | `pp-reactive-v2.js` | top-level script bindings are exported, nested bindings are not
|
|
37
|
-
| State | `pp.state(initial)` | `pp-reactive-v2.js` | setters accept values or updater functions, state belongs to the component instance |
|
|
38
|
-
| Effects | `pp.effect(...)`, `pp.layoutEffect(...)` | `pp-reactive-v2.js` | callbacks may return cleanup functions, promises are not awaited |
|
|
39
|
-
| Refs | `pp.ref(...)`, `pp-ref` on native or `x-*` tags | `components_compiler.py`, `TemplateCompiler.ts`, `Component.ts`, `RefBindingManager.ts`, shipped in `pp-reactive-v2.js` | component-tag refs are reserved by the Python compiler, stamped with runtime-owned `pp-ref-owner`, captured in the parent's scope, and bound to the child's concrete root; do not author `data-pp-ref`, `pp-ref-owner`, or `pp-ref-forward`; callback refs may return synchronous cleanup functions that run on replacement or detach |
|
|
40
|
-
| Context | `pp.createContext(...)`, lowercase `<themecontext.provider>`, `pp.context(token)` | `TemplateCompiler.ts`, `NestedBoundaryManager.ts`, `pp-reactive-v2.js` | authored provider tags are HTML-first and lowercase; `TemplateCompiler.transformContextProviderTags(...)` rewrites `*.provider` to runtime-owned `pp-context-provider`; ancestry is logical component ancestry; do not invent `pp-context` or `pp.provideContext` |
|
|
41
|
-
| Portals | `pp.portal(ref, target?)` | `pp-reactive-v2.js` | context should preserve logical ancestry through the registry |
|
|
42
|
-
|
|
|
43
|
-
|
|
|
44
|
-
|
|
|
45
|
-
|
|
|
46
|
-
|
|
|
47
|
-
|
|
|
48
|
-
|
|
|
49
|
-
|
|
|
50
|
-
|
|
|
34
|
+
| 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 | `scripts_type.py`, `public/js/pp-reactive-v2.js` | authored scripts are plain, runtime scripts become `type="text/pp"`, one owned script per root |
|
|
36
|
+
| Template expressions | text and attributes with `{...}` | `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
|
+
| State | `pp.state(initial)` | `public/js/pp-reactive-v2.js` | setters accept values or updater functions, state belongs to the component instance |
|
|
38
|
+
| Effects | `pp.effect(...)`, `pp.layoutEffect(...)` | `public/js/pp-reactive-v2.js` | callbacks may return cleanup functions, promises are not awaited |
|
|
39
|
+
| Refs | `pp.ref(...)`, `pp-ref` on native or `x-*` tags | `components_compiler.py`, `TemplateCompiler.ts`, `Component.ts`, `RefBindingManager.ts`, shipped in `public/js/pp-reactive-v2.js` | component-tag refs are reserved by the Python compiler, stamped with runtime-owned `pp-ref-owner`, captured in the parent's scope, and bound to the child's concrete root; do not author `data-pp-ref`, `pp-ref-owner`, or `pp-ref-forward`; callback refs may return synchronous cleanup functions that run on replacement or detach |
|
|
40
|
+
| Context | `pp.createContext(...)`, lowercase `<themecontext.provider>`, `pp.context(token)` | `TemplateCompiler.ts`, `NestedBoundaryManager.ts`, `public/js/pp-reactive-v2.js` | authored provider tags are HTML-first and lowercase; `TemplateCompiler.transformContextProviderTags(...)` rewrites `*.provider` to runtime-owned `pp-context-provider`; ancestry is logical component ancestry; do not invent `pp-context` or `pp.provideContext` |
|
|
41
|
+
| Portals | `pp.portal(ref, target?)` | `public/js/pp-reactive-v2.js` | context should preserve logical ancestry through the registry |
|
|
42
|
+
| Props into a component | `x-*` attributes, Python `get_attributes({...}, props)` + `{{ attributes }}` | `html_attrs.py`, `components_compiler.py`, `public/js/pp-reactive-v2.js` | `pp.props` is read from the **rendered root's attributes**, so a prop accepted in Python but not re-emitted by `get_attributes` is silently `undefined` — no server error, no console warning. Brace expressions (`volume="{vol}"`) keep their real type; literals arrive as strings (`volume="0"` makes `volume === 0` false); valueless attributes arrive as `true`; JS reserved words such as `class` are dropped. See [components.md](./components.md#every-prop-a-template-reads-must-be-forwarded-to-the-root) |
|
|
43
|
+
| Stable ids | `pp.id()` | `public/js/pp-reactive-v2.js` | derived from component id plus hook slot; stable across rerenders, unique per instance; use for `id`/`for` pairing and `aria-*` instead of counters |
|
|
44
|
+
| External stores | `pp.syncExternalStore(subscribe, getSnapshot)` | `public/js/pp-reactive-v2.js` | `subscribe` must be stable — wrap in `pp.callback(..., [])`; runtime re-reads the snapshot right after subscribing to close the render-to-subscribe gap; cleanup runs on dispose |
|
|
45
|
+
| Imperative handles | `pp.imperativeHandle(ref, createHandle, deps?)` | `public/js/pp-reactive-v2.js` | parent passes its own `pp.ref(...)` down as a normal prop (object props pass by reference); do not author `pp-ref-forward`; handle publishes in the layout-effect phase and clears on unmount |
|
|
46
|
+
| Transitions | `pp.transition()` | `public/js/pp-reactive-v2.js` | returns `[isPending, startTransition]`; not concurrent — rendering is still synchronous; handles sync and promise scopes; overlapping scopes stay pending until the last settles |
|
|
47
|
+
| Deferred values | `pp.deferredValue(value, initialValue?)` | `public/js/pp-reactive-v2.js` | lags one commit behind the source |
|
|
48
|
+
| Optimistic updates | `pp.optimistic(passthrough, reducer?)` | `public/js/pp-reactive-v2.js` | pending actions are dropped when `passthrough` changes, so a confirmed server value never double-counts the guess; without a reducer each action replaces the value |
|
|
49
|
+
| Error boundaries | `pp.errorBoundary()` | `public/js/pp-reactive-v2.js` | returns `[error, reset]`; render **and** effect/cleanup throws walk logical component ancestry to the nearest boundary; a boundary also catches its own throws; latches until `reset()`; caps at 5 captures per arming; event-handler errors are not routed — use `try`/`catch` |
|
|
50
|
+
| 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` |
|
|
51
|
+
| 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 |
|
|
52
|
+
| 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)` |
|
|
53
|
+
| 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 }` and `percent` can be `null` when length is not computable; replace state from returned payload |
|
|
54
|
+
| Streaming | `pp.rpc(..., { onStream })` | `public/js/pp-reactive-v2.js`, `casp/rpc.py`, `casp/streaming.py` | server generators become SSE responses |
|
|
55
|
+
| SPA navigation | `body[pp-spa="true"]`, links | `public/js/pp-reactive-v2.js`, `main.py` | same-origin eligible links intercept; root-layout mismatches hard reload |
|
|
56
|
+
| Scroll restoration | `pp-reset-scroll="true"` | `public/js/pp-reactive-v2.js` | push navigation resets window; mark only content panes that should reset |
|
|
57
|
+
| Tailwind merge | `{twMerge(...)}`, Python `merge_classes(...)` | `html_attrs.py`, `public/js/pp-reactive-v2.js` | Python emits frontend-ready expressions when Tailwind is enabled |
|
|
58
|
+
| 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>`" |
|
|
51
59
|
|
|
52
60
|
## AI Decision Rules
|
|
53
61
|
|
|
@@ -69,6 +77,10 @@ If an inspected browser DOM disagrees with authored template source, remember th
|
|
|
69
77
|
- 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.
|
|
70
78
|
- Use lowercase provider tags such as `<themecontext.provider>` and `pp.context(...)` for context.
|
|
71
79
|
- Use `pp-for` only on `<template>` and plain `key` for keyed lists.
|
|
80
|
+
- Use `pp.id()` for any generated `id`, `for`, `aria-labelledby`, or `aria-describedby` value instead of a loop index or module-level counter.
|
|
81
|
+
- Use `pp.syncExternalStore(...)` for browser or global sources the component does not own (`matchMedia`, `localStorage`, `navigator.onLine`, a shared store) instead of `pp.state` plus `pp.effect`.
|
|
82
|
+
- Wrap a component subtree that can fail in a parent with `pp.errorBoundary()` rather than leaving a render throw to the console.
|
|
83
|
+
- In a Python component, list every prop the template's `{...}` expressions reference in `get_attributes({...}, props)`. If a binding "does nothing" — an icon toggle that never switches, a `hidden` that never applies — check that prop is on the rendered root before debugging the expression.
|
|
72
84
|
- Prefer PulsePoint state and directives over manual `innerHTML` repainting.
|
|
73
85
|
- Keep direct DOM APIs inside `pp.ref(...)` plus `pp.effect(...)` only when a third-party or browser API integration actually requires them.
|
|
74
86
|
|
|
@@ -112,6 +124,40 @@ Upload progress:
|
|
|
112
124
|
</section>
|
|
113
125
|
```
|
|
114
126
|
|
|
127
|
+
Error boundary with fallback and retry:
|
|
128
|
+
|
|
129
|
+
```html
|
|
130
|
+
<section>
|
|
131
|
+
<script>
|
|
132
|
+
const [error, reset] = pp.errorBoundary();
|
|
133
|
+
</script>
|
|
134
|
+
|
|
135
|
+
<div hidden="{!error}">
|
|
136
|
+
<p>Could not load this panel: {error?.message}</p>
|
|
137
|
+
<button onclick="{reset()}">Retry</button>
|
|
138
|
+
</div>
|
|
139
|
+
|
|
140
|
+
<div hidden="{!!error}">
|
|
141
|
+
<x-revenue-chart />
|
|
142
|
+
</div>
|
|
143
|
+
</section>
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
Accessible field pairing with stable ids:
|
|
147
|
+
|
|
148
|
+
```html
|
|
149
|
+
<div>
|
|
150
|
+
<label for="{emailId}">Email</label>
|
|
151
|
+
<input id="{emailId}" type="email" aria-describedby="{hintId}" />
|
|
152
|
+
<p id="{hintId}">We never share this.</p>
|
|
153
|
+
|
|
154
|
+
<script>
|
|
155
|
+
const emailId = pp.id();
|
|
156
|
+
const hintId = pp.id();
|
|
157
|
+
</script>
|
|
158
|
+
</div>
|
|
159
|
+
```
|
|
160
|
+
|
|
115
161
|
Grouped shell scroll reset:
|
|
116
162
|
|
|
117
163
|
```html
|