caspian-utils 0.0.29 → 0.0.31
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 +53 -48
- package/dist/docs/core-runtime-map.md +11 -7
- package/dist/docs/file-conventions.md +259 -0
- package/dist/docs/index.md +46 -44
- package/dist/docs/project-structure.md +21 -10
- package/dist/docs/routing.md +2 -2
- package/package.json +1 -1
package/dist/docs/auth.md
CHANGED
|
@@ -165,8 +165,9 @@ return AuthSettings(
|
|
|
165
165
|
|
|
166
166
|
The installed auth code reads several values from `.env` when explicit values are not passed.
|
|
167
167
|
|
|
168
|
-
- `AUTH_SECRET` backs `AuthSettings.secret_key`.
|
|
169
|
-
- `
|
|
168
|
+
- `AUTH_SECRET` backs `AuthSettings.secret_key`.
|
|
169
|
+
- App-owned startup helpers may also validate `AUTH_SECRET` and refuse production startup when the value is missing or still on a default placeholder.
|
|
170
|
+
- `AUTH_COOKIE_NAME` backs `AuthSettings.cookie_name`.
|
|
170
171
|
- `SESSION_LIFETIME_HOURS` controls `SessionMiddleware.max_age` in the current `main.py` bootstrap.
|
|
171
172
|
- `APP_ENV=production` enables secure session cookies and the `Secure` flag on the current CSRF cookie.
|
|
172
173
|
- `CASPIAN_BROWSER_SYNC_PORT` can override the development cookie scope suffix used by the current `main.py` bootstrap.
|
|
@@ -224,28 +225,29 @@ This does two things:
|
|
|
224
225
|
- applies the centralized settings once at startup
|
|
225
226
|
- registers OAuth providers once so `AuthMiddleware` can delegate signin and callback paths through `auth.auth_providers(...)`
|
|
226
227
|
|
|
227
|
-
The current `main.py` also wires the session middleware directly:
|
|
228
|
-
|
|
229
|
-
```python
|
|
230
|
-
from starlette.middleware.sessions import SessionMiddleware
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
228
|
+
The current `main.py` also wires the session middleware directly, and some apps delegate the secret lookup to an app-owned helper:
|
|
229
|
+
|
|
230
|
+
```python
|
|
231
|
+
from starlette.middleware.sessions import SessionMiddleware
|
|
232
|
+
from src.lib.security.runtime_security import get_session_secret
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
SESSION_LIFETIME_HOURS = int(os.getenv("SESSION_LIFETIME_HOURS", 7))
|
|
236
|
+
IS_PRODUCTION = os.getenv("APP_ENV") == "production"
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
app.add_middleware(
|
|
240
|
+
SessionMiddleware,
|
|
241
|
+
secret_key=get_session_secret(is_production=IS_PRODUCTION),
|
|
242
|
+
session_cookie=os.getenv("AUTH_COOKIE_NAME", "session"),
|
|
243
|
+
max_age=SESSION_LIFETIME_HOURS * 3600,
|
|
244
|
+
same_site="lax",
|
|
243
245
|
https_only=IS_PRODUCTION,
|
|
244
246
|
path="/",
|
|
245
247
|
)
|
|
246
248
|
```
|
|
247
249
|
|
|
248
|
-
Keep this wiring in `main.py`. Keep only the policy values in `src/lib/auth/auth_config.py`.
|
|
250
|
+
Keep this wiring in `main.py`. Keep only the policy values in `src/lib/auth/auth_config.py`. If the app uses an app-owned security helper for session-secret enforcement, safe public-file serving, or browser security headers, keep that helper in `src/lib/**` and keep the middleware attachment itself in `main.py`.
|
|
249
251
|
|
|
250
252
|
## Session Lifetime Rules
|
|
251
253
|
|
|
@@ -258,37 +260,40 @@ The effective authenticated session lasts only while both are valid.
|
|
|
258
260
|
|
|
259
261
|
- If the session cookie expires first, the session disappears even if the auth payload expiration was longer.
|
|
260
262
|
- If the auth payload expires first, `auth.is_authenticated()` clears it even if the session cookie still exists.
|
|
261
|
-
- In the
|
|
263
|
+
- In apps where the request flow does not call `auth.refresh_session()`, `token_auto_refresh=True` still does nothing by itself.
|
|
262
264
|
|
|
263
265
|
Keep `SESSION_LIFETIME_HOURS` and `default_token_validity` aligned unless you intentionally want one boundary to end earlier than the other.
|
|
264
266
|
|
|
265
267
|
## Middleware Order
|
|
266
268
|
|
|
267
|
-
The current `main.py` adds middleware in this source order:
|
|
268
|
-
|
|
269
|
-
```python
|
|
270
|
-
app.add_middleware(RPCMiddleware)
|
|
271
|
-
app.add_middleware(AuthMiddleware)
|
|
272
|
-
app.add_middleware(CSRFMiddleware)
|
|
273
|
-
app.add_middleware(SessionMiddleware, ...)
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
- `
|
|
289
|
-
- `
|
|
290
|
-
|
|
291
|
-
|
|
269
|
+
The current `main.py` adds middleware in this source order:
|
|
270
|
+
|
|
271
|
+
```python
|
|
272
|
+
app.add_middleware(RPCMiddleware)
|
|
273
|
+
app.add_middleware(AuthMiddleware)
|
|
274
|
+
app.add_middleware(CSRFMiddleware)
|
|
275
|
+
app.add_middleware(SessionMiddleware, ...)
|
|
276
|
+
app.add_middleware(SecurityHeadersMiddleware)
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
Because Starlette runs the last-added middleware first, the effective request order is:
|
|
280
|
+
|
|
281
|
+
1. `SecurityHeadersMiddleware`
|
|
282
|
+
2. `SessionMiddleware`
|
|
283
|
+
3. `CSRFMiddleware`
|
|
284
|
+
4. `AuthMiddleware`
|
|
285
|
+
5. `RPCMiddleware`
|
|
286
|
+
6. route handler or RPC endpoint
|
|
287
|
+
|
|
288
|
+
Current behavior by layer:
|
|
289
|
+
|
|
290
|
+
- `SecurityHeadersMiddleware` can attach browser security headers such as CSP, `X-Content-Type-Options`, framing policy, and HSTS while preserving any headers already set by the response.
|
|
291
|
+
- `SessionMiddleware` provides `request.session` for the rest of the stack.
|
|
292
|
+
- `CSRFMiddleware` ensures `request.session["csrf_token"]` exists and emits a scoped CSRF cookie based on `pp_csrf`, for example `pp_csrf_5091` in development or plain `pp_csrf` when no dev scope is active.
|
|
293
|
+
- `AuthMiddleware` sets request context with `Auth.set_request(request)`, initializes `StateManager`, runs provider callbacks, skips configured static asset paths, and enforces public, auth, private, and role-based route redirects.
|
|
294
|
+
- `RPCMiddleware` handles `POST` requests with `X-PP-RPC: true` and forwards them to Caspian's RPC handler after auth and session setup are already available.
|
|
295
|
+
|
|
296
|
+
Keep `SessionMiddleware` immediately inside any outer response-header wrapper such as `SecurityHeadersMiddleware`. If CSRF, auth, or RPC handling runs before the session layer, `request.session` will not be available.
|
|
292
297
|
|
|
293
298
|
## Current `AuthMiddleware` Flow
|
|
294
299
|
|
|
@@ -760,7 +765,7 @@ Prefer `AuthSettings`, `configure_auth(...)`, and `auth.settings` in new code.
|
|
|
760
765
|
- Expired or malformed payloads are removed during `auth.is_authenticated()` checks.
|
|
761
766
|
- `auth.get_payload()` returns `None` for missing payloads, a dict for dict payloads, and `{"value": ...}` for non-dict payloads.
|
|
762
767
|
- OAuth callback helpers return `None` on failure, so calling routes should be prepared for a normal page render or explicit error state when provider login fails.
|
|
763
|
-
- The current app bootstrap
|
|
768
|
+
- The current app bootstrap may wrap auth and session handling in an outer `SecurityHeadersMiddleware`; auth-aware code still depends on `SessionMiddleware`, `CSRFMiddleware`, `AuthMiddleware`, and `RPCMiddleware` staying in the correct relative order so sessions and CSRF state exist before route checks or RPC handling.
|
|
764
769
|
- Password reset, email verification, and other account-recovery workflows are application responsibilities layered on top of `casp.auth`, not built-in auth runtime features.
|
|
765
770
|
- Route lists and RBAC maps are exact-path checks, not wildcard or prefix rules.
|
|
766
771
|
|
|
@@ -803,7 +808,7 @@ If an AI agent is deciding how to handle auth in Caspian, apply these rules firs
|
|
|
803
808
|
- Do not treat `token_auto_refresh` as a route-privacy switch. In the current app it only affects sliding-session refresh if `auth.refresh_session()` is called.
|
|
804
809
|
- Use `@require_auth`, `@require_role`, and `@guest_only` for page-level access rules.
|
|
805
810
|
- Use `@rpc(require_auth=True, allowed_roles=[...])` for protected browser-triggered actions.
|
|
806
|
-
- Keep `SessionMiddleware`
|
|
811
|
+
- Keep `SessionMiddleware` immediately inside any outer response-header wrapper so auth, CSRF, and RPC handlers can read `request.session`.
|
|
807
812
|
- Use [state.md](./state.md) only for transient auth-adjacent request state, not as the auth session store.
|
|
808
813
|
- Align `SESSION_LIFETIME_HOURS` with `default_token_validity` unless you intentionally want different cookie and auth-payload expiry windows.
|
|
809
814
|
- Prefer exact route strings in `public_routes`, `auth_routes`, `private_routes`, and `role_based_routes`.
|
|
@@ -811,6 +816,6 @@ If an AI agent is deciding how to handle auth in Caspian, apply these rules firs
|
|
|
811
816
|
- Keep auth secrets and OAuth provider credentials in `.env`.
|
|
812
817
|
- Set `AUTH_COOKIE_NAME` explicitly so the session middleware cookie name and auth settings stay aligned.
|
|
813
818
|
- Use `.venv/Lib/site-packages/casp/auth.py` only when the task is about framework auth internals or debugging installed behavior.
|
|
814
|
-
- Use `main.py` when the task is about auth bootstrap, session middleware, or middleware execution order.
|
|
819
|
+
- Use `main.py` plus any app-owned runtime security helper when the task is about auth bootstrap, session middleware, browser security headers, or middleware execution order.
|
|
815
820
|
- Pair auth work with `fetch-data.md` for RPC forms and `validation.md` for credential validation.
|
|
816
821
|
- Check `routing.md` and `project-structure.md` before creating new auth routes or shared auth helpers.
|
|
@@ -29,16 +29,19 @@ Use it when you already know a behavior is controlled by `main.py` or `.venv/Lib
|
|
|
29
29
|
|
|
30
30
|
| Concern | Core file | Read first | Why it matters |
|
|
31
31
|
| --- | --- | --- | --- |
|
|
32
|
-
| App bootstrap and request flow | [main.py](../../../../main.py) | [project-structure.md](./project-structure.md), [routing.md](./routing.md), [auth.md](./auth.md) | FastAPI app creation,
|
|
32
|
+
| App bootstrap and request flow | [main.py](../../../../main.py) plus any imported app-owned runtime helpers | [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. App-owned helper modules imported by `main.py` may own safe static-file serving, browser security headers, CSP defaults, or production session-secret enforcement. |
|
|
33
33
|
| 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. |
|
|
34
34
|
|
|
35
35
|
Important current `main.py` behaviors AI should keep in mind:
|
|
36
36
|
|
|
37
|
-
- 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`.
|
|
38
|
-
- `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.
|
|
39
|
-
- The render pipeline is `transform_components(...)`, then `render_with_nested_layouts(...)`, then `transform_scripts(...)`.
|
|
40
|
-
- Route-level generators returned from `page()` are wrapped in `SSE(...)` before the response is sent.
|
|
41
|
-
-
|
|
37
|
+
- 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`.
|
|
38
|
+
- `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.
|
|
39
|
+
- The render pipeline is `transform_components(...)`, then `render_with_nested_layouts(...)`, then `transform_scripts(...)`.
|
|
40
|
+
- Route-level generators returned from `page()` are wrapped in `SSE(...)` before the response is sent.
|
|
41
|
+
- Safe public-file helpers may be factored into an app-owned runtime module and should reject `..` traversal before serving from `public/**`.
|
|
42
|
+
- Session middleware secrets may be resolved through an app-owned helper so production can fail fast when `AUTH_SECRET` is missing or still on a default placeholder.
|
|
43
|
+
- Browser security headers and CSP defaults may be built in an app-owned helper and attached through `SecurityHeadersMiddleware`.
|
|
44
|
+
- Middleware is added in source order as `RPCMiddleware`, `AuthMiddleware`, `CSRFMiddleware`, `SessionMiddleware`, `SecurityHeadersMiddleware`, so the effective request order is reversed at runtime.
|
|
42
45
|
- `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.
|
|
43
46
|
|
|
44
47
|
## Caspian Core Feature Map
|
|
@@ -53,6 +56,7 @@ Use this table when the task names a framework feature but the owning file is no
|
|
|
53
56
|
| Metadata | route or layout `metadata`, runtime metadata helpers | `casp.layout`, `main.py` | [metadata.md](./metadata.md), [routing.md](./routing.md) |
|
|
54
57
|
| Component imports and `x-*` tags | `src/components/**`, `src/app/**/*.html` | `casp.components_compiler`, `casp.component_decorator` | [components.md](./components.md), [routing.md](./routing.md) |
|
|
55
58
|
| Auth and route protection | `src/lib/auth/auth_config.py`, `main.py`, route decorators | `casp.auth`, `main.py` middleware | [auth.md](./auth.md) |
|
|
59
|
+
| Browser security headers, CSP defaults, and safe public-file serving | app-owned helpers imported by `main.py` | app-owned runtime modules | [auth.md](./auth.md), [project-structure.md](./project-structure.md) |
|
|
56
60
|
| RPC and server actions | route or component Python modules with `@rpc()` | `casp.rpc`, `main.py` middleware | [fetch-data.md](./fetch-data.md), [file-uploads.md](./file-uploads.md) |
|
|
57
61
|
| Streaming | route `page()` generators, RPC generators | `casp.streaming`, `casp.rpc`, `main.py` | [fetch-data.md](./fetch-data.md) |
|
|
58
62
|
| Server state | request handlers and RPC actions | `casp.state_manager`, `main.py` middleware | [state.md](./state.md) |
|
|
@@ -106,7 +110,7 @@ Use these behavior checkpoints when AI needs the fastest verification path for a
|
|
|
106
110
|
|
|
107
111
|
| Runtime area | Verify these behaviors |
|
|
108
112
|
| --- | --- |
|
|
109
|
-
| `main.py` routing and request flow | route registration, path and query injection, static asset handling, and exception rendering |
|
|
113
|
+
| `main.py` routing and request flow | route registration, path and query injection, static asset handling, session-middleware wiring, browser security headers, and exception rendering |
|
|
110
114
|
| `public/js/pp-reactive-v2.js` browser runtime | SPA interception, history-vs-push scroll behavior, `pp-reset-scroll` semantics, and browser-side `pp.rpc()` behavior |
|
|
111
115
|
| `casp.auth` | auth settings, signin and signout flow, provider wiring, and page protection behavior |
|
|
112
116
|
| `casp.rpc` and streamed RPC responses | middleware interception, CSRF and session expectations, registry behavior, and helper-level RPC contracts |
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: File Conventions
|
|
3
|
+
description: Use this page when deciding what belongs in `index.html`, `index.py`, `layout.html`, `layout.py`, `loading.html`, `not-found.html`, or `error.html` in a Caspian app.
|
|
4
|
+
related:
|
|
5
|
+
title: Related docs
|
|
6
|
+
description: Use the routing guide for URL and subtree behavior, the structure guide for placement, the metadata guide for SEO fields, the cache guide for route-level caching, and the runtime maps when the task crosses into framework internals.
|
|
7
|
+
links:
|
|
8
|
+
- /docs/routing
|
|
9
|
+
- /docs/project-structure
|
|
10
|
+
- /docs/metadata
|
|
11
|
+
- /docs/cache
|
|
12
|
+
- /docs/pulsepoint
|
|
13
|
+
- /docs/core-runtime-map
|
|
14
|
+
- /docs/pulsepoint-runtime-map
|
|
15
|
+
- /docs/index
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
This page is the quick decision guide for the special file conventions under `src/app`.
|
|
19
|
+
|
|
20
|
+
Use it when a task names `index.html`, `index.py`, `layout.html`, `layout.py`, `loading.html`, `not-found.html`, or `error.html`, or when the question is where a page shell, route logic, loading UI, 404 page, or 500 page should live.
|
|
21
|
+
|
|
22
|
+
Treat `caspian.config.json` and the actual project tree as the source of truth for which features exist in the current workspace. For runtime ownership, verify these rules against:
|
|
23
|
+
|
|
24
|
+
- `main.py` for global `not-found.html` and `error.html` handling
|
|
25
|
+
- `.venv/Lib/site-packages/casp/layout.py` for `render_page(...)`, `render_layout(...)`, and nested layout behavior
|
|
26
|
+
- `.venv/Lib/site-packages/casp/loading.py` plus `public/js/pp-reactive-v2.js` for `loading.html` collection and SPA loading behavior
|
|
27
|
+
- `.venv/Lib/site-packages/casp/caspian_config.py` for how `index.*`, `layout.html`, and `loading.html` are indexed under `src/app`
|
|
28
|
+
|
|
29
|
+
## Quick Map
|
|
30
|
+
|
|
31
|
+
| File | Purpose | Add it when | Verify against |
|
|
32
|
+
| --- | --- | --- | --- |
|
|
33
|
+
| `index.html` | Authored visible page template for a route | The route renders UI | `src/app/**`, `routing.md`, `pulsepoint.md` |
|
|
34
|
+
| `index.py` | Backend companion for route logic and metadata | The route needs `page()`, metadata, auth checks, redirects, caching, or route-owned `@rpc()` actions | `main.py`, `.venv/Lib/site-packages/casp/layout.py` |
|
|
35
|
+
| `layout.html` | Shared shell for a route subtree | Multiple child routes share wrapper markup | `.venv/Lib/site-packages/casp/layout.py`, `routing.md` |
|
|
36
|
+
| `layout.py` | Shared synchronous props and metadata defaults for a subtree | The shared shell needs Python-provided values or metadata | `.venv/Lib/site-packages/casp/layout.py`, `metadata.md` |
|
|
37
|
+
| `loading.html` | Route-scope loading UI used during SPA navigation | A section or page needs an immediate loading state before the next route finishes rendering | `.venv/Lib/site-packages/casp/loading.py`, `public/js/pp-reactive-v2.js` |
|
|
38
|
+
| `not-found.html` | Global 404 page | The app needs a branded fallback for unmatched URLs | `main.py` |
|
|
39
|
+
| `error.html` | Global 500 page | The app needs a safe fallback for unhandled exceptions | `main.py` |
|
|
40
|
+
|
|
41
|
+
## Authored HTML Rule
|
|
42
|
+
|
|
43
|
+
For authored route, layout, loading, not-found, and error HTML files, keep exactly one top-level parent HTML element or one imported `x-*` component root.
|
|
44
|
+
|
|
45
|
+
- Keep any `<!-- @import ... -->` directives above that root.
|
|
46
|
+
- Keep any owned plain `<script>` inside that root, not after it.
|
|
47
|
+
- Do not handwrite `pp-component` or `type="text/pp"`.
|
|
48
|
+
|
|
49
|
+
This is a runtime requirement, not a style preference.
|
|
50
|
+
|
|
51
|
+
## `index.html`
|
|
52
|
+
|
|
53
|
+
`index.html` is the authored page template for a route.
|
|
54
|
+
|
|
55
|
+
Use it for:
|
|
56
|
+
|
|
57
|
+
- visible page markup
|
|
58
|
+
- HTML-first `x-*` component usage
|
|
59
|
+
- PulsePoint state, refs, effects, and directives that belong to that route
|
|
60
|
+
- route-local plain `<script>` blocks that stay inside the same root
|
|
61
|
+
|
|
62
|
+
For any route that renders UI, keep the visible markup here even when the route also has an `index.py` companion.
|
|
63
|
+
|
|
64
|
+
Example:
|
|
65
|
+
|
|
66
|
+
```html
|
|
67
|
+
<!-- @import Button from "../../components/Button.py" -->
|
|
68
|
+
|
|
69
|
+
<section class="space-y-4 p-6">
|
|
70
|
+
<h1 class="text-2xl font-semibold">Dashboard</h1>
|
|
71
|
+
|
|
72
|
+
<x-button onclick="setFilter('open')">
|
|
73
|
+
Show Open
|
|
74
|
+
</x-button>
|
|
75
|
+
|
|
76
|
+
<script>
|
|
77
|
+
const [filter, setFilter] = pp.state("all");
|
|
78
|
+
</script>
|
|
79
|
+
</section>
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Use `index.html` by itself when the route is UI-only.
|
|
83
|
+
|
|
84
|
+
## `index.py`
|
|
85
|
+
|
|
86
|
+
`index.py` is the backend companion for a route.
|
|
87
|
+
|
|
88
|
+
Add it when the route needs:
|
|
89
|
+
|
|
90
|
+
- `page()`
|
|
91
|
+
- metadata
|
|
92
|
+
- auth checks or redirects
|
|
93
|
+
- route-level `Cache(...)`
|
|
94
|
+
- route-owned `@rpc()` actions
|
|
95
|
+
- server-side context before rendering the sibling template
|
|
96
|
+
|
|
97
|
+
For UI routes, `index.py` does not replace `index.html`. It prepares data and calls `render_page(__file__, ...)` so Caspian renders the sibling template.
|
|
98
|
+
|
|
99
|
+
Example:
|
|
100
|
+
|
|
101
|
+
```python
|
|
102
|
+
from casp.layout import Metadata, render_page
|
|
103
|
+
|
|
104
|
+
metadata = Metadata(
|
|
105
|
+
title="Dashboard | Caspian",
|
|
106
|
+
description="Overview page for the dashboard.",
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
async def page(params: dict, request=None):
|
|
111
|
+
return render_page(__file__, {
|
|
112
|
+
"slug": params.get("slug"),
|
|
113
|
+
"has_request": request is not None,
|
|
114
|
+
})
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Current route-parameter behavior:
|
|
118
|
+
|
|
119
|
+
- path params arrive as a single `params` dict
|
|
120
|
+
- query params can be injected by name
|
|
121
|
+
- `request` is injected by keyword when declared
|
|
122
|
+
|
|
123
|
+
When one page needs to influence a wrapping layout, `page()` can return `(page_html, layout_props_dict)`.
|
|
124
|
+
|
|
125
|
+
## `layout.html`
|
|
126
|
+
|
|
127
|
+
`layout.html` is the shared wrapper for a route subtree.
|
|
128
|
+
|
|
129
|
+
Use it for:
|
|
130
|
+
|
|
131
|
+
- shared shell markup such as sidebars, headers, docs rails, or dashboard frames
|
|
132
|
+
- the `[[children|safe]]` insertion point for child routes
|
|
133
|
+
- shared layout props consumed as `[[ layout.* ]]`
|
|
134
|
+
- shared metadata fields consumed as `[[ metadata.* ]]`
|
|
135
|
+
|
|
136
|
+
Example:
|
|
137
|
+
|
|
138
|
+
```html
|
|
139
|
+
<div class="docs-shell">
|
|
140
|
+
<aside class="docs-nav">Docs navigation</aside>
|
|
141
|
+
|
|
142
|
+
<main class="docs-content" pp-reset-scroll="true">
|
|
143
|
+
[[children|safe]]
|
|
144
|
+
</main>
|
|
145
|
+
</div>
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
Use nested `layout.html` files for sections like `dashboard/`, `docs/`, `account/`, or route groups such as `(marketing)/`.
|
|
149
|
+
|
|
150
|
+
In grouped shells with separate shell and content scrolling, put `pp-reset-scroll="true"` on the content pane that should reset on child-route navigation. Leave persistent shell scrollers such as sidebars unmarked when they should keep their own scroll position.
|
|
151
|
+
|
|
152
|
+
## `layout.py`
|
|
153
|
+
|
|
154
|
+
`layout.py` is the Python companion for `layout.html`.
|
|
155
|
+
|
|
156
|
+
Use it for:
|
|
157
|
+
|
|
158
|
+
- shared synchronous props
|
|
159
|
+
- metadata defaults for everything below that folder
|
|
160
|
+
- small shared layout decisions that belong to the subtree rather than one page
|
|
161
|
+
|
|
162
|
+
Return `render_layout(__file__), props` so the sibling `layout.html` stays the authored wrapper.
|
|
163
|
+
|
|
164
|
+
Example:
|
|
165
|
+
|
|
166
|
+
```python
|
|
167
|
+
from casp.layout import Metadata, render_layout
|
|
168
|
+
|
|
169
|
+
metadata = Metadata(
|
|
170
|
+
title="Docs Section | Caspian",
|
|
171
|
+
description="Shared metadata for the docs subtree.",
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def layout():
|
|
176
|
+
return render_layout(__file__), {
|
|
177
|
+
"shell_class": "docs-shell",
|
|
178
|
+
"content_class": "docs-shell__content",
|
|
179
|
+
}
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
Important runtime detail: `layout()` is synchronous in the installed runtime. Put async I/O in route `page()` functions or route-owned `@rpc()` actions instead of awaiting inside `layout.py`.
|
|
183
|
+
|
|
184
|
+
## `loading.html`
|
|
185
|
+
|
|
186
|
+
`loading.html` provides route-scope loading UI for SPA navigation.
|
|
187
|
+
|
|
188
|
+
The runtime indexes `src/app/**/loading.html` separately from routes and layouts. During navigation, the browser runtime looks for the closest matching loading file by URL scope and falls back up the path toward `/`.
|
|
189
|
+
|
|
190
|
+
Examples:
|
|
191
|
+
|
|
192
|
+
- `src/app/loading.html` can act as the root fallback loader
|
|
193
|
+
- `src/app/dashboard/loading.html` applies to `/dashboard` and its descendants unless a closer loading file exists
|
|
194
|
+
- `src/app/(marketing)/loading.html` applies to the grouped subtree even though `(marketing)` does not appear in the URL
|
|
195
|
+
|
|
196
|
+
To preserve the surrounding shell during navigation, put `pp-loading-content="true"` on the live container that should be swapped during loading, usually the main content pane in a layout.
|
|
197
|
+
|
|
198
|
+
Example layout shell:
|
|
199
|
+
|
|
200
|
+
```html
|
|
201
|
+
<main class="docs-content" pp-loading-content="true" pp-reset-scroll="true">
|
|
202
|
+
[[children|safe]]
|
|
203
|
+
</main>
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
Example loader:
|
|
207
|
+
|
|
208
|
+
```html
|
|
209
|
+
<div class="space-y-4 p-6">
|
|
210
|
+
<div class="h-4 rounded bg-muted animate-pulse"></div>
|
|
211
|
+
<div class="h-24 rounded bg-muted/70 animate-pulse"></div>
|
|
212
|
+
</div>
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
If no `pp-loading-content="true"` container exists, the browser runtime falls back to `document.body`.
|
|
216
|
+
|
|
217
|
+
## `not-found.html`
|
|
218
|
+
|
|
219
|
+
`src/app/not-found.html` is the global 404 page.
|
|
220
|
+
|
|
221
|
+
When `main.py` catches a `404`, it looks for this exact root-level file, renders it through the nested layout pipeline, and returns it with a 404 status.
|
|
222
|
+
|
|
223
|
+
Important scope rule:
|
|
224
|
+
|
|
225
|
+
- this is a global root-level file, not a per-folder `not-found.html` convention in the current runtime
|
|
226
|
+
|
|
227
|
+
The runtime passes `request` into the template context and sets default page metadata for the response.
|
|
228
|
+
|
|
229
|
+
Use this file for branded invalid-URL fallbacks instead of leaving the app on the generic FastAPI or Starlette message.
|
|
230
|
+
|
|
231
|
+
## `error.html`
|
|
232
|
+
|
|
233
|
+
`src/app/error.html` is the global 500 page for unhandled exceptions.
|
|
234
|
+
|
|
235
|
+
When `main.py` catches a general exception, it looks for this exact root-level file, renders it through the nested layout pipeline, and returns it with a 500 status.
|
|
236
|
+
|
|
237
|
+
The runtime provides these template values:
|
|
238
|
+
|
|
239
|
+
- `request`
|
|
240
|
+
- `error_message`
|
|
241
|
+
- `error_trace`
|
|
242
|
+
|
|
243
|
+
In production, `error_trace` is suppressed. Keep this page safe for users and avoid exposing sensitive internals outside development environments.
|
|
244
|
+
|
|
245
|
+
If rendering `error.html` fails, the runtime falls back to a minimal plain HTML 500 response.
|
|
246
|
+
|
|
247
|
+
## Decision Rule
|
|
248
|
+
|
|
249
|
+
Use this order when deciding where a concern belongs:
|
|
250
|
+
|
|
251
|
+
1. Put visible route markup in `index.html`.
|
|
252
|
+
2. Add `index.py` only when the same route needs backend work.
|
|
253
|
+
3. Put shared subtree wrapper markup in `layout.html`.
|
|
254
|
+
4. Add `layout.py` only when that shared shell needs synchronous Python props or metadata.
|
|
255
|
+
5. Add `loading.html` when SPA navigation needs an immediate scoped loading state.
|
|
256
|
+
6. Use root `not-found.html` for unmatched URLs.
|
|
257
|
+
7. Use root `error.html` for unhandled exceptions.
|
|
258
|
+
|
|
259
|
+
Use [routing.md](./routing.md) for the broader file-based routing model, [metadata.md](./metadata.md) for page and layout metadata, [cache.md](./cache.md) for route caching in `index.py`, and [pulsepoint.md](./pulsepoint.md) for the authored template contract and browser runtime behavior.
|
package/dist/docs/index.md
CHANGED
|
@@ -8,10 +8,11 @@ related:
|
|
|
8
8
|
- /docs/ai-validation-checklist
|
|
9
9
|
- /docs/installation
|
|
10
10
|
- /docs/commands
|
|
11
|
-
- /docs/core-runtime-map
|
|
12
|
-
- /docs/pulsepoint-runtime-map
|
|
13
|
-
- /docs/mcp
|
|
11
|
+
- /docs/core-runtime-map
|
|
12
|
+
- /docs/pulsepoint-runtime-map
|
|
13
|
+
- /docs/mcp
|
|
14
14
|
- /docs/file-uploads
|
|
15
|
+
- /docs/file-conventions
|
|
15
16
|
- /docs/project-structure
|
|
16
17
|
- /docs/components
|
|
17
18
|
---
|
|
@@ -24,41 +25,41 @@ The docs can mention optional features even when those features are disabled in
|
|
|
24
25
|
|
|
25
26
|
Before making feature, tooling, or file-placement decisions in a Caspian project, read `./caspian.config.json` almost immediately. That file tells you which optional capabilities are enabled, such as Prisma, MCP, TypeScript, Tailwind, backend-only mode, and component scan directories.
|
|
26
27
|
|
|
27
|
-
## Default Stack
|
|
28
|
-
|
|
29
|
-
When generating or editing a Caspian app, treat these as the default choices unless the task explicitly requires something else:
|
|
28
|
+
## Default Stack
|
|
29
|
+
|
|
30
|
+
When generating or editing a Caspian app, treat these as the default choices unless the task explicitly requires something else:
|
|
30
31
|
|
|
31
32
|
- Use PulsePoint for reactive frontend behavior.
|
|
32
33
|
- Treat every authored route, layout, and component HTML file like a React component return value: exactly one top-level parent HTML element or one imported `x-*` root, with any owned plain `<script>` kept inside that same root.
|
|
33
34
|
- When `caspian.config.json` has `tailwindcss: true`, use Python `merge_classes(...)` plus browser `twMerge(...)` as the only supported Tailwind class-merging path.
|
|
34
|
-
- Use `@rpc()` plus `pp.rpc()` for browser-triggered reads, writes, streaming, and uploads.
|
|
35
|
-
- Use `Validate` and `Rule` from `casp.validate` for server-side input validation and sanitization.
|
|
36
|
-
|
|
37
|
-
## AI Doc Shape
|
|
38
|
-
|
|
39
|
-
Each packaged feature doc should help AI answer the same five questions quickly:
|
|
40
|
-
|
|
41
|
-
- When does this doc apply?
|
|
42
|
-
- Which `caspian.config.json` flag, if any, gates the feature?
|
|
43
|
-
- Which app-owned files usually change?
|
|
44
|
-
- Which `main.py` or installed `casp` runtime files own the behavior?
|
|
45
|
-
- Which behavior should be verified before editing or explaining the feature?
|
|
46
|
-
|
|
47
|
-
Keep project-specific facts, temporary file inventory, and current app feature flags out of packaged docs. Put those in `AGENTS.md`, `.github/copilot-instructions.md`, or the project code instead.
|
|
48
|
-
|
|
49
|
-
Use [core-runtime-map.md](./core-runtime-map.md) for Python-side Caspian runtime ownership and [pulsepoint-runtime-map.md](./pulsepoint-runtime-map.md) for browser-side PulsePoint runtime ownership.
|
|
50
|
-
|
|
51
|
-
## Redundancy Rule
|
|
52
|
-
|
|
53
|
-
Prefer one canonical explanation plus links from related docs.
|
|
54
|
-
|
|
55
|
-
- Put the full authored-template and PulsePoint runtime contract in [pulsepoint.md](./pulsepoint.md).
|
|
56
|
-
- Put the full route and layout placement contract in [routing.md](./routing.md).
|
|
57
|
-
- Put the full reusable component contract in [components.md](./components.md).
|
|
58
|
-
- Put the full Python runtime ownership map in [core-runtime-map.md](./core-runtime-map.md).
|
|
59
|
-
- Put the full PulsePoint feature lookup map in [pulsepoint-runtime-map.md](./pulsepoint-runtime-map.md).
|
|
60
|
-
|
|
61
|
-
Other docs should summarize these rules only when the reminder prevents a common mistake, then link to the canonical page instead of restating the whole rule block.
|
|
35
|
+
- Use `@rpc()` plus `pp.rpc()` for browser-triggered reads, writes, streaming, and uploads.
|
|
36
|
+
- Use `Validate` and `Rule` from `casp.validate` for server-side input validation and sanitization.
|
|
37
|
+
|
|
38
|
+
## AI Doc Shape
|
|
39
|
+
|
|
40
|
+
Each packaged feature doc should help AI answer the same five questions quickly:
|
|
41
|
+
|
|
42
|
+
- When does this doc apply?
|
|
43
|
+
- Which `caspian.config.json` flag, if any, gates the feature?
|
|
44
|
+
- Which app-owned files usually change?
|
|
45
|
+
- Which `main.py` or installed `casp` runtime files own the behavior?
|
|
46
|
+
- Which behavior should be verified before editing or explaining the feature?
|
|
47
|
+
|
|
48
|
+
Keep project-specific facts, temporary file inventory, and current app feature flags out of packaged docs. Put those in `AGENTS.md`, `.github/copilot-instructions.md`, or the project code instead.
|
|
49
|
+
|
|
50
|
+
Use [core-runtime-map.md](./core-runtime-map.md) for Python-side Caspian runtime ownership and [pulsepoint-runtime-map.md](./pulsepoint-runtime-map.md) for browser-side PulsePoint runtime ownership.
|
|
51
|
+
|
|
52
|
+
## Redundancy Rule
|
|
53
|
+
|
|
54
|
+
Prefer one canonical explanation plus links from related docs.
|
|
55
|
+
|
|
56
|
+
- Put the full authored-template and PulsePoint runtime contract in [pulsepoint.md](./pulsepoint.md).
|
|
57
|
+
- Put the full route and layout placement contract in [routing.md](./routing.md).
|
|
58
|
+
- Put the full reusable component contract in [components.md](./components.md).
|
|
59
|
+
- Put the full Python runtime ownership map in [core-runtime-map.md](./core-runtime-map.md).
|
|
60
|
+
- Put the full PulsePoint feature lookup map in [pulsepoint-runtime-map.md](./pulsepoint-runtime-map.md).
|
|
61
|
+
|
|
62
|
+
Other docs should summarize these rules only when the reminder prevents a common mistake, then link to the canonical page instead of restating the whole rule block.
|
|
62
63
|
|
|
63
64
|
## Docs Location
|
|
64
65
|
|
|
@@ -72,11 +73,12 @@ The packaged Caspian docs referenced by this index live here:
|
|
|
72
73
|
- `ai-validation-checklist.md` - workflow and representative prompts for checking whether AI can find the correct Caspian docs, core files, and verification checkpoints
|
|
73
74
|
- `installation.md` - First-time setup flow for creating a new Caspian application
|
|
74
75
|
- `commands.md` - Main Caspian CLI workflows for project creation, generation, updates, and config-aware maintenance
|
|
75
|
-
- `core-runtime-map.md` - map of `main.py` plus installed `casp` modules to the packaged docs that explain them and the behaviors AI should trace there
|
|
76
|
-
- `pulsepoint-runtime-map.md` - fast feature-to-runtime lookup for PulsePoint state, effects, refs, context, portals, lists, events, RPC, uploads, streaming, SPA navigation, scroll restoration, and Tailwind merge behavior
|
|
77
|
-
- `mcp.md` - MCP-specific layout, launch flow, and AI routing rules for projects where `caspian.config.json` enables MCP
|
|
76
|
+
- `core-runtime-map.md` - map of `main.py` plus installed `casp` modules to the packaged docs that explain them and the behaviors AI should trace there
|
|
77
|
+
- `pulsepoint-runtime-map.md` - fast feature-to-runtime lookup for PulsePoint state, effects, refs, context, portals, lists, events, RPC, uploads, streaming, SPA navigation, scroll restoration, and Tailwind merge behavior
|
|
78
|
+
- `mcp.md` - MCP-specific layout, launch flow, and AI routing rules for projects where `caspian.config.json` enables MCP
|
|
78
79
|
- `database.md` - Prisma schema, migration, seed, and client-generation workflow for projects where `caspian.config.json` enables Prisma, plus Python-side helper caveats
|
|
79
80
|
- `auth.md` - Session-backed authentication with `casp.auth`, centralized `auth_config.py`, public-vs-private route mode guidance, RPC-first signout guidance, RBAC, and OAuth provider helpers
|
|
81
|
+
- `file-conventions.md` - quick decision guide for `index.html`, `index.py`, `layout.html`, `layout.py`, `loading.html`, `not-found.html`, and `error.html`, plus the owning runtime files to verify
|
|
80
82
|
- `components.md` - Create reusable Python components, template-backed UI, HTML-first `x-*` component tags, the single-parent authored-root rule for component HTML files, and the Python-side `merge_classes(...)` contract when Tailwind CSS is enabled
|
|
81
83
|
- `pulsepoint.md` - Default reactive frontend runtime contract for component scripts, state, effects, directives, SPA navigation scroll restoration, `pp-reset-scroll`, and direct browser `twMerge(...)` usage when Tailwind CSS is enabled
|
|
82
84
|
- `fetch-data.md` - Initial server-side data loading and browser-triggered RPC flows with `pp.rpc()`, streaming, uploads, and auth-aware actions
|
|
@@ -98,13 +100,13 @@ Preferred lookup order:
|
|
|
98
100
|
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.
|
|
99
101
|
3. Before authoring or editing any `src/app/**` or component HTML template, apply the single-root invariant: one authored root only, any owned `<script>` inside that root, and no handwritten `pp-component` or `type="text/pp"`.
|
|
100
102
|
4. Treat `caspian.config.json` as the single source of truth for optional features. Use feature-specific docs only when the matching flag is enabled. If a feature is disabled and the user wants it, ask first, then update `caspian.config.json` and follow the update workflow in `commands.md`.
|
|
101
|
-
5. If the task touches `main.py` or `.venv/Lib/site-packages/casp/**`, read `core-runtime-map.md` to jump to the controlling runtime file and the matching feature doc.
|
|
102
|
-
6. If the task names a PulsePoint feature or directive, read `pulsepoint-runtime-map.md` for the fastest feature-to-runtime lookup, then read `pulsepoint.md` for authoring rules.
|
|
103
|
-
7. After the feature is confirmed, inspect the actual project files that decide behavior, such as `package.json`, `main.py`, `src/app/**`, `src/lib/**`, `settings/**`, `prisma/**`, and the installed `casp` runtime.
|
|
104
|
-
8. Use `commands.md` for scaffold and update workflows, `project-structure.md` for placement decisions, and the feature docs such as `mcp.md`, `database.md`, `auth.md`, `fetch-data.md`, and `file-uploads.md` for task-specific guidance.
|
|
105
|
-
9. Prefer packaged Caspian docs before upstream documentation when generating code, commands, or migration guidance.
|
|
106
|
-
10. Use `ai-validation-checklist.md` when you want to verify that the docs lead AI to the correct files and behavior checkpoints.
|
|
107
|
-
11. Keep `index.md` and cross-links aligned so AI can quickly discover the right doc.
|
|
103
|
+
5. If the task touches `main.py` or `.venv/Lib/site-packages/casp/**`, read `core-runtime-map.md` to jump to the controlling runtime file and the matching feature doc.
|
|
104
|
+
6. If the task names a PulsePoint feature or directive, read `pulsepoint-runtime-map.md` for the fastest feature-to-runtime lookup, then read `pulsepoint.md` for authoring rules.
|
|
105
|
+
7. After the feature is confirmed, inspect the actual project files that decide behavior, such as `package.json`, `main.py`, `src/app/**`, `src/lib/**`, `settings/**`, `prisma/**`, and the installed `casp` runtime.
|
|
106
|
+
8. Use `file-conventions.md` for quick decisions about `index.html`, `index.py`, `layout.html`, `layout.py`, `loading.html`, `not-found.html`, and `error.html`; use `commands.md` for scaffold and update workflows, `project-structure.md` for placement decisions, and the feature docs such as `mcp.md`, `database.md`, `auth.md`, `fetch-data.md`, and `file-uploads.md` for task-specific guidance.
|
|
107
|
+
9. Prefer packaged Caspian docs before upstream documentation when generating code, commands, or migration guidance.
|
|
108
|
+
10. Use `ai-validation-checklist.md` when you want to verify that the docs lead AI to the correct files and behavior checkpoints.
|
|
109
|
+
11. Keep `index.md` and cross-links aligned so AI can quickly discover the right doc.
|
|
108
110
|
|
|
109
111
|
## Maintenance
|
|
110
112
|
|
|
@@ -185,16 +185,27 @@ If the local BrowserSync stack is running, keep that upload directory in `settin
|
|
|
185
185
|
|
|
186
186
|
The application entry point for the project.
|
|
187
187
|
|
|
188
|
-
In the current Caspian app shape, this file is where startup wiring happens:
|
|
189
|
-
|
|
190
|
-
- load environment variables
|
|
191
|
-
- call `configure_auth(build_auth_settings())`
|
|
192
|
-
- register OAuth providers with `Auth.set_providers(...)`
|
|
193
|
-
- create the FastAPI app
|
|
194
|
-
- register routes and RPC handlers
|
|
195
|
-
- add `SessionMiddleware`, CSRF middleware, auth middleware, and RPC middleware
|
|
196
|
-
|
|
197
|
-
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.
|
|
188
|
+
In the current Caspian app shape, this file is where startup wiring happens:
|
|
189
|
+
|
|
190
|
+
- load environment variables
|
|
191
|
+
- call `configure_auth(build_auth_settings())`
|
|
192
|
+
- register OAuth providers with `Auth.set_providers(...)`
|
|
193
|
+
- create the FastAPI app
|
|
194
|
+
- register routes and RPC handlers
|
|
195
|
+
- add security headers middleware, `SessionMiddleware`, CSRF middleware, auth middleware, and RPC middleware
|
|
196
|
+
|
|
197
|
+
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.
|
|
198
|
+
|
|
199
|
+
### `src/lib/security/runtime_security.py`
|
|
200
|
+
|
|
201
|
+
When the app factors request hardening into an app-owned helper module, keep browser security headers, CSP defaults, safe public-file helpers, and production session-secret enforcement there instead of bloating `main.py`.
|
|
202
|
+
|
|
203
|
+
Use this module when the task is about:
|
|
204
|
+
|
|
205
|
+
- safe serving of files from `public/**`
|
|
206
|
+
- browser security headers or content-security-policy defaults
|
|
207
|
+
- production session-secret enforcement
|
|
208
|
+
- user-facing vs production-safe exception messaging helpers
|
|
198
209
|
|
|
199
210
|
### `caspian.config.json`
|
|
200
211
|
|
package/dist/docs/routing.md
CHANGED
|
@@ -33,7 +33,7 @@ Start with these rules:
|
|
|
33
33
|
- Use a standalone `index.py` only for non-visual routes such as redirects or action-only handlers.
|
|
34
34
|
- When a folder owns child routes, use `layout.html` to wrap them. This is the default pattern for dashboards, admin sections, account areas, settings trees, and route groups.
|
|
35
35
|
- In grouped shells with separate shell and content scrolling, put `pp-reset-scroll="true"` on the content pane in `layout.html` when that pane should reset on child-route navigation while the shell sidebar or rail keeps its own scroll.
|
|
36
|
-
- Use `layout.py` when a layout needs shared
|
|
36
|
+
- Use `layout.py` when a layout needs shared props or metadata before rendering. The `layout()` function may be synchronous or async.
|
|
37
37
|
- Keep visible route and layout markup in `index.html` and `layout.html`. Treat `index.py` and `layout.py` as backend companions, not as places to author visible HTML.
|
|
38
38
|
- Treat every authored route and layout template like a React component body: it must have exactly one top-level parent HTML element or one imported `x-*` root, and any owned plain `<script>` must live inside that same root.
|
|
39
39
|
|
|
@@ -86,7 +86,7 @@ When a user asks for a dashboard, admin area, account section, docs section, or
|
|
|
86
86
|
|
|
87
87
|
- Create a parent folder for the section.
|
|
88
88
|
- Add `layout.html` in that folder for the shared shell.
|
|
89
|
-
- Add `layout.py` only when that shared shell needs
|
|
89
|
+
- Add `layout.py` only when that shared shell needs shared props or metadata.
|
|
90
90
|
- Put each child page in its own route folder with `index.html` and an optional `index.py` companion.
|
|
91
91
|
- Use a normal folder name such as `dashboard/` when the section name should appear in the URL.
|
|
92
92
|
- Use a route-group folder such as `(marketing)/` when the folder should organize code and own a layout without adding a URL segment.
|