caspian-utils 0.0.17 → 0.0.19

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.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  title: Components
3
- description: Use this page when the task mentions `@component`, reusable UI, component imports, same-name `.html` templates, or where shared components belong in a Caspian project.
3
+ description: Use this page when the task mentions `@component`, reusable UI, HTML-first `x-*` component tags, component imports, same-name `.html` templates, or where shared components belong in a Caspian project.
4
4
  related:
5
5
  title: Related docs
6
6
  description: Use the structure guide for file placement, the routing guide for route templates, the PulsePoint guide for browser-side scripts, and the data guide for component-owned RPC flows.
@@ -12,9 +12,11 @@ related:
12
12
  - /docs/index
13
13
  ---
14
14
 
15
- Components in Caspian are Python functions decorated with `@component`.
15
+ Components in Caspian are implemented as Python functions decorated with `@component`.
16
16
 
17
- Import them into a template with an `@import` comment, then render them with JSX-style tags such as `<MyComponent />` or `<MyComponent>...</MyComponent>`.
17
+ In authored HTML, import them with an `@import` comment and render them with HTML-first kebab-cased `x-*` tags such as `<x-my-component />` or `<x-my-component>...</x-my-component>`.
18
+
19
+ Treat that `x-*` form as the current Caspian component contract for authored templates.
18
20
 
19
21
  Component tooling scans Python files under the paths listed in `caspian.config.json`. When `componentScanDirs` includes `src/`, `src/components/` is the clean default location for reusable UI, even though any scanned path can work.
20
22
 
@@ -60,17 +62,17 @@ Place component imports at the top of the HTML template, above the authored root
60
62
  ```html
61
63
  <!-- @import Container from "../components" -->
62
64
 
63
- <Container class="py-10">
65
+ <x-container class="py-10">
64
66
  <h1>Dashboard</h1>
65
- </Container>
67
+ </x-container>
66
68
  ```
67
69
 
68
- The import comment is the bridge between the Python component file and the JSX-style tag.
70
+ The import comment is the bridge between the Python component export and the `x-*` tag you author in HTML.
69
71
 
70
72
  Treat `<!-- @import ... -->` as a file-level directive, not as rendered markup. It does not count as the template root, and it should not be nested inside the root wrapper element.
71
73
 
72
74
  - `<!-- @import Container from "../components" -->` resolves `Container.py` from that folder.
73
- - The component function name should match the tag name you render, such as `Container` for `<Container />`.
75
+ - The exported component name maps to the tag you render by kebab-casing it and prefixing `x-`, such as `Container` for `<x-container />` or `CommandDialog` for `<x-command-dialog />`.
74
76
  - Grouped imports and aliases are also supported, for example `<!-- @import { Button, Card as UserCard } from "../components/ui" -->`.
75
77
  - If one Python file exports several `@component` functions, point `from` at that exact file instead of assuming one file per tag.
76
78
  - In that case, prefer one grouped import so every rendered tag resolves back to the same Python module.
@@ -81,9 +83,9 @@ Good:
81
83
  <!-- @import { Button, Input, Label } from "../../../lib/maddex" -->
82
84
 
83
85
  <section class="auth-panel auth-panel-compact fade-up">
84
- <Label>Email</Label>
85
- <Input type="email" />
86
- <Button>Continue</Button>
86
+ <x-label>Email</x-label>
87
+ <x-input type="email" />
88
+ <x-button>Continue</x-button>
87
89
  </section>
88
90
  ```
89
91
 
@@ -106,17 +108,17 @@ If `Breadcrumb.py` defines `Breadcrumb`, `BreadcrumbList`, `BreadcrumbItem`, `Br
106
108
  <!-- @import { Breadcrumb, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator } from "../maddex/Breadcrumb.py" -->
107
109
 
108
110
  <div class="dashboard-topbar">
109
- <Breadcrumb class="dashboard-breadcrumbs" aria-label="Dashboard breadcrumbs">
110
- <BreadcrumbList class="dashboard-breadcrumbs__list">
111
- <BreadcrumbItem class="dashboard-breadcrumbs__item">
112
- <BreadcrumbLink href="/dashboard">Dashboard</BreadcrumbLink>
113
- </BreadcrumbItem>
114
- <BreadcrumbSeparator class="dashboard-breadcrumbs__separator" />
115
- <BreadcrumbItem class="dashboard-breadcrumbs__item">
116
- <BreadcrumbPage class="dashboard-breadcrumbs__page">Reports</BreadcrumbPage>
117
- </BreadcrumbItem>
118
- </BreadcrumbList>
119
- </Breadcrumb>
111
+ <x-breadcrumb class="dashboard-breadcrumbs" aria-label="Dashboard breadcrumbs">
112
+ <x-breadcrumb-list class="dashboard-breadcrumbs__list">
113
+ <x-breadcrumb-item class="dashboard-breadcrumbs__item">
114
+ <x-breadcrumb-link href="/dashboard">Dashboard</x-breadcrumb-link>
115
+ </x-breadcrumb-item>
116
+ <x-breadcrumb-separator class="dashboard-breadcrumbs__separator" />
117
+ <x-breadcrumb-item class="dashboard-breadcrumbs__item">
118
+ <x-breadcrumb-page class="dashboard-breadcrumbs__page">Reports</x-breadcrumb-page>
119
+ </x-breadcrumb-item>
120
+ </x-breadcrumb-list>
121
+ </x-breadcrumb>
120
122
  </div>
121
123
  ```
122
124
 
@@ -317,7 +319,7 @@ Keep synchronous components as the default. Switch to `async def` only when the
317
319
 
318
320
  - Put reusable components in `src/components/` and keep route files in `src/app/`.
319
321
  - If the component includes PulsePoint behavior, prefer a thin Python wrapper plus a same-name `.html` template.
320
- - Keep the component file name, exported function name, and tag name aligned, such as `Button.py`, `def Button(...)`, and `<Button />`.
322
+ - Keep the component file name, exported function name, and authored tag aligned, such as `Button.py`, `def Button(...)`, and `<x-button />`.
321
323
  - Accept `children` or `**props` when the component should support nested content.
322
324
  - Keep page-level data loading in `page()` when the data is not intrinsic to the component itself.
323
325
  - If you add `@rpc()` functions inside a component file, keep their names globally unique because component RPCs are not route-scoped.
@@ -17,9 +17,9 @@ related:
17
17
  - /docs/index
18
18
  ---
19
19
 
20
- This page explains how data fetching works in Caspian. Use route functions for initial page data and use RPC actions for browser-triggered reads, writes, streams, and uploads.
20
+ This page explains how data fetching works in Caspian. Use route functions for initial page data and use RPC actions for browser-triggered reads, writes, streams, uploads, and normal CRUD work.
21
21
 
22
- Treat RPC as the default way for browser code to talk to Python in Caspian. Do not reach for ad hoc fetch calls to custom JSON endpoints, alternate transport layers, or older helper names unless the task explicitly requires that shape.
22
+ Treat RPC as the default way for browser code to talk to Python in Caspian. For CRUD operations and any browser-initiated backend reads after first render, default to `@rpc()` on the server and `pp.rpc()` in PulsePoint code. Do not reach for ad hoc fetch calls to custom JSON endpoints, alternate transport layers, or older helper names unless the task explicitly requires that shape.
23
23
 
24
24
  MCP is a separate integration surface. Do not place app-owned FastMCP tools in route `index.py` files or treat `@rpc()` actions as a replacement for MCP tools. Use `mcp.md` and `src/lib/mcp/` only when `caspian.config.json` has `mcp: true`. If `mcp` is false, do not assume those files exist.
25
25
 
@@ -36,15 +36,18 @@ In practice, most pages use both:
36
36
  2. Render that data into `index.html`.
37
37
  3. Call `pp.rpc()` for refreshes, form submits, toggles, infinite scroll, or streamed updates.
38
38
 
39
+ If a route only needs UI and does not need first-render data, metadata, or other backend behavior, skip `index.py` and keep the page in `index.html` alone.
40
+
39
41
  ## Default Data Rule
40
42
 
41
43
  - Use `page()` for async or route-level data required before HTML renders, and use `layout()` only for synchronous shared props or metadata.
42
- - Use `@rpc()` on the server and `pp.rpc()` in PulsePoint code for all browser-triggered data work after first render.
44
+ - When a route renders UI and also needs backend work, keep the HTML in the sibling `index.html`; `index.py` should prepare data and call `render_page(__file__, ...)`, not inline the route markup.
45
+ - Use `@rpc()` on the server and `pp.rpc()` in PulsePoint code for all browser-triggered data work after first render, including CRUD operations and follow-up reads.
43
46
  - Keep custom REST or other endpoint patterns as explicit exceptions, not the baseline Caspian approach.
44
47
 
45
48
  ## Initial Data In `index.py`
46
49
 
47
- Use the route's backend file for data that should exist before the template is rendered.
50
+ Use the route's backend file for data that should exist before the template is rendered. Keep the rendered page markup in the sibling `index.html`; if no backend data or logic is needed, omit `index.py` entirely.
48
51
 
49
52
  Example:
50
53
 
@@ -120,6 +123,7 @@ Call it from the client with `pp.rpc()`:
120
123
 
121
124
  Use RPC for:
122
125
 
126
+ - CRUD reads and writes after the initial render
123
127
  - Button-triggered refreshes
124
128
  - Form submissions and mutations
125
129
  - Polling or background refreshes
@@ -292,7 +296,7 @@ If an AI agent is choosing how to load data in Caspian, apply these rules first.
292
296
  - Put first-render data loading in `src/app/**/index.py`.
293
297
  - Put shared section props in `layout.py` only when multiple child routes need the same synchronous data.
294
298
  - Keep async I/O in `page()` or `@rpc()` because the current layout engine does not await `layout()`.
295
- - Treat RPC as the default read and write layer between PulsePoint code and Python route logic.
299
+ - Treat RPC as the default read and write layer between PulsePoint code and Python route logic, especially for CRUD and interactive backend reads.
296
300
  - Use `@rpc()` for backend functions that should be callable from the browser.
297
301
  - Use `pp.rpc()` for client-side calls; do not prefer older `pp.fetchFunction()` wording.
298
302
  - Prefer route-render data plus RPC over inventing parallel REST endpoints for normal Caspian page interactions.
@@ -42,7 +42,7 @@ The packaged Caspian docs referenced by this index live here:
42
42
  - `mcp.md` - MCP-specific layout, launch flow, and AI routing rules for projects where `caspian.config.json` enables MCP
43
43
  - `database.md` - Prisma schema, migration, seed, and client-generation workflow for projects where `caspian.config.json` enables Prisma, plus Python-side helper caveats
44
44
  - `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
45
- - `components.md` - Create reusable Python components, template-backed UI, JSX-style imports, and the single-parent root rule for component HTML files
45
+ - `components.md` - Create reusable Python components, template-backed UI, HTML-first `x-*` component tags, and the single-parent root rule for component HTML files
46
46
  - `pulsepoint.md` - Default reactive frontend runtime contract for component scripts, state, effects, directives, and client-side behaviors
47
47
  - `fetch-data.md` - Initial server-side data loading and browser-triggered RPC flows with `pp.rpc()`, streaming, uploads, and auth-aware actions
48
48
  - `file-uploads.md` - Route-local file uploads and file-manager flows with `@rpc()`, `pp.rpc()`, Prisma metadata, public asset storage, and BrowserSync ignore rules
@@ -31,6 +31,8 @@ Metadata is typically defined in one of two files:
31
31
  - `layout.py` for defaults shared by everything below that folder
32
32
  - `index.py` for route-specific metadata
33
33
 
34
+ On routes that render UI, keep the page markup in the sibling `index.html`. `index.py` is the metadata and backend companion, not the place to store route HTML. If a route needs no metadata or backend behavior, omit `index.py` and keep the page as `index.html` only.
35
+
34
36
  The current `Metadata` implementation supports three fields:
35
37
 
36
38
  - `title`
@@ -26,7 +26,7 @@ Caspian uses a lean project layout that keeps application code in `src`, databas
26
26
 
27
27
  As an app grows, keep reusable rendered UI in `src/components/`, keep reusable non-UI support code in `src/lib/`, and keep route-owned files in `src/app/`. That split keeps page composition separate from shared component and service code.
28
28
 
29
- In that layout, the default stack is Python components for reusable UI, PulsePoint in templates for reactive browser behavior, RPC for browser-triggered server calls, and `casp.validate` for input validation at route and action boundaries.
29
+ In that layout, the default stack is Python components for reusable UI, PulsePoint in templates for reactive browser behavior, RPC for CRUD operations and browser-triggered backend reads, and `casp.validate` for input validation at route and action boundaries.
30
30
 
31
31
  For public pages that can safely reuse rendered HTML, Caspian also supports route-level page caching through `casp.cache_handler`.
32
32
 
@@ -105,6 +105,8 @@ This is the main application area. It contains route files, templates, styles, a
105
105
 
106
106
  This directory handles file-based routing. Route templates and route-specific backend logic live here.
107
107
 
108
+ 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. Use a lone `index.py` only for non-visual routes such as redirect-only or action-only handlers.
109
+
108
110
  When authoring a route HTML file such as `src/app/**/index.html`, keep the whole template inside exactly one top-level lowercase HTML element. Treat it the same way you would a React component returning one parent element: wrap the route markup and any owned PulsePoint script in the same root.
109
111
 
110
112
  Do not handwrite `pp-component="..."` or `type="text/pp"` in those source templates. Author a plain `<script>` inside the root when you need PulsePoint logic, and let Caspian add the runtime attributes during render.
@@ -117,9 +119,9 @@ Use this folder for reusable UI components that should be imported into route te
117
119
 
118
120
  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/`.
119
121
 
120
- 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.
122
+ 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 />`.
121
123
 
122
- 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" -->`, instead of assuming each tag has its own sibling `.py` file.
124
+ 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.
123
125
 
124
126
  For component HTML files, follow the same one-parent rule as route HTML files: one top-level lowercase HTML element only, with no sibling roots and no top-level script sitting outside that root.
125
127
 
@@ -227,7 +229,9 @@ If this layout imports reusable components, place each `<!-- @import ... -->` co
227
229
 
228
230
  ### `src/app/index.py`
229
231
 
230
- The backend logic for the route. This is where route behavior can load first-render data, expose `@rpc()` actions, and import framework features such as auth helpers, `casp.validate`, and route-level `Cache(...)` declarations.
232
+ The backend logic companion for the route. Use this file when the same route needs first-render data, metadata, `@rpc()` actions, auth helpers, `casp.validate`, route-level `Cache(...)` declarations, redirects, or other server behavior.
233
+
234
+ If the route renders UI, keep the markup in the sibling `index.html` and let `index.py` call `render_page(__file__, ...)` with whatever context the template needs. Do not store route HTML in `index.py`. A lone `index.py` should be reserved for non-visual routes.
231
235
 
232
236
  When a route owns a file manager or upload UI, keep the owning upload and delete `@rpc()` actions in that route's `index.py` and move reusable filesystem or Prisma helpers into `src/lib/`. Do not move ordinary upload behavior into `main.py`.
233
237
 
@@ -235,9 +239,11 @@ When a route owns a file manager or upload UI, keep the owning upload and delete
235
239
 
236
240
  The route template. It supports standard HTML, `<!-- @import ... -->` component imports, and PulsePoint directives, and it should be the default place for reactive frontend behavior in Caspian.
237
241
 
242
+ If a route renders UI and needs no backend behavior, this file alone is sufficient. If the route also has an `index.py` companion, keep the visible page markup here.
243
+
238
244
  Treat import comments as top-of-file directives that belong above the route's single authored root element.
239
245
 
240
- Use `components.md` when the task involves authoring reusable `<MyComponent />` tags backed by Python files.
246
+ Use `components.md` when the task involves authoring reusable `<x-my-component />` tags backed by Python files.
241
247
 
242
248
  ### `src/app/globals.css`
243
249
 
@@ -272,7 +278,7 @@ If an AI agent is deciding where to make changes, use these rules first.
272
278
  - 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/`.
273
279
  - Read [file-uploads.md](./file-uploads.md) when the task involves upload widgets, media libraries, or file-manager flows.
274
280
  - Check [routing.md](./routing.md) when you need URL segment rules, layout nesting behavior, or dynamic route conventions.
275
- - Put reusable component files in `src/components/` and check [components.md](./components.md) for `@component`, `render_html(__file__)`, import comments, and single-root template rules.
281
+ - 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.
276
282
  - 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.
277
283
  - 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.
278
284
  - Keep `<!-- @import ... -->` comments above the single authored root element in route, layout, and component HTML files.
@@ -281,7 +287,7 @@ If an AI agent is deciding where to make changes, use these rules first.
281
287
  - Use `settings/bs-config.ts` when uploaded public assets should not trigger BrowserSync reloads during the local stack.
282
288
  - Put app-owned FastMCP code in `src/lib/mcp/` only when `caspian.config.json` enables MCP.
283
289
  - Use PulsePoint conventions in route templates for reactive frontend behavior.
284
- - Use `@rpc()` in route/backend code and `pp.rpc()` in client code for browser-triggered data flows.
290
+ - Use `@rpc()` in route/backend code and `pp.rpc()` in client code for browser-triggered data flows, especially CRUD operations and interactive backend reads.
285
291
  - Use `casp.cache_handler` when a route's first-render HTML should be reused safely across requests.
286
292
  - Use `casp.validate` at route and RPC boundaries, and move reusable validators into `src/lib/` when multiple routes share them.
287
293
  - Use `database.md` when the task involves Prisma schema changes, migrations, seed logic, or project-specific database helper conventions.
@@ -16,9 +16,9 @@ related:
16
16
 
17
17
  This file documents the PulsePoint contract for the shipped Caspian browser runtime. Treat it as the AI-facing source of truth when generating or reviewing interactive Caspian UI.
18
18
 
19
- If a task involves `pp.state`, `pp.effect`, `pp.layoutEffect`, `pp-ref`, `pp-style`, `pp-spread`, `pp-for`, context, portals, SPA navigation, or component boundary behavior, read this page first and keep generated code aligned with the runtime implemented in this repo.
19
+ If a task involves `pp.state`, `pp.effect`, `pp.layoutEffect`, `pp-ref`, `pp-style`, `pp-spread`, `pp-for`, context, portals, SPA navigation, or component boundary behavior, read this page first and keep generated code aligned with the current Caspian runtime.
20
20
 
21
- Use `components.md` for authoring Python `@component` files and same-name HTML templates. Use this page for the browser-side PulsePoint contract, the authoring rules that feed it, and the React-style mental model used by the shipped runtime.
21
+ Use `components.md` for authoring Python `@component` files, same-name HTML templates, and HTML-first `x-*` component tags. Use this page for the browser-side PulsePoint contract, the authoring rules that feed it, and the React-style mental model used by the shipped runtime.
22
22
 
23
23
  PulsePoint is the default reactive frontend layer for Caspian. In the current runtime it follows a React-like component pattern, but it is HTML-first rather than JSX-first.
24
24
 
@@ -36,6 +36,7 @@ Important current facts:
36
36
 
37
37
  - `public/js/pp-reactive-v2.js` exposes the global `pp` runtime and auto-mounts on DOM ready.
38
38
  - `main.py` renders the final HTML, runs `transform_components(...)`, then runs `transform_scripts(...)` before returning the response.
39
+ - Authored route and component templates compose reusable server components as HTML-first `x-*` tags before the browser runtime mounts.
39
40
 
40
41
  If docs, generated examples, or older notes disagree with `public/js/pp-reactive-v2.js` plus `main.py`, follow the code that actually runs.
41
42
 
@@ -44,12 +45,14 @@ If docs, generated examples, or older notes disagree with `public/js/pp-reactive
44
45
  When a Caspian page needs reactive browser behavior, use PulsePoint.
45
46
 
46
47
  - Use PulsePoint component roots, scripts, directives, and runtime helpers for interactive UI.
48
+ - Use PulsePoint state, effects, refs, and template directives as the default reactivity model in authored Caspian templates.
49
+ - When the browser needs CRUD operations or follow-up reads from the backend, call `pp.rpc()` from PulsePoint code and back it with route or backend `@rpc()` actions.
47
50
  - Keep server-rendered HTML plus PulsePoint enhancement as the baseline architecture.
48
51
  - Only introduce another frontend runtime when the user explicitly asks for it or the project already depends on one.
49
52
 
50
53
  ## Authoring Model
51
54
 
52
- PulsePoint authoring in this repo is split into two layers:
55
+ PulsePoint authoring is split into two layers:
53
56
 
54
57
  - The authored layer: route, layout, and component templates under `src/` with plain HTML plus a plain `<script>`.
55
58
  - The runtime layer: the browser sees `pp-component` roots and `script[type="text/pp"]` after Caspian transforms the HTML.
@@ -413,10 +416,12 @@ These are runtime details.
413
416
  Use these rules when generating or editing PulsePoint runtime code:
414
417
 
415
418
  - Treat PulsePoint as the default reactive frontend for Caspian app code.
419
+ - Treat `pp.rpc()` as the default browser-to-server path for CRUD operations and interactive backend reads.
416
420
  - Use `public/js/pp-reactive-v2.js` as the shipped runtime contract AI should follow.
417
421
  - Keep `main.py` in view because it injects the runtime-facing attributes and rewrites authored scripts before the browser sees them.
418
422
  - 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.
419
423
  - In authored Caspian templates, do not handwrite `pp-component` or `type="text/pp"`; let the render pipeline inject them.
424
+ - Prefer PulsePoint state and template directives over manual DOM mutation for reactive updates.
420
425
  - If you are explicitly editing raw runtime HTML or internals, keep `pp-component` unique per live instance.
421
426
  - In authored templates, use a plain `<script>` inside the root. In runtime HTML, the owned script appears as `script[type="text/pp"]`.
422
427
  - Keep template-facing variables at top level.
@@ -25,8 +25,10 @@ Caspian uses a high-performance file-system router built on top of FastAPI. Your
25
25
  Start with these rules:
26
26
 
27
27
  - Put application routes in `src/app/`.
28
- - Use `index.html` for a template-only route.
29
- - Use `index.py` when the route needs metadata or async server-side logic.
28
+ - For any route that renders a page, put the markup in `index.html`.
29
+ - If a route is UI-only, `index.html` by itself is enough.
30
+ - Add `index.py` only when the same route needs metadata, `page()`, `@rpc()` actions, auth checks, caching, redirects, or other server-side logic.
31
+ - Use a standalone `index.py` only for non-visual routes such as redirects or action-only handlers.
30
32
  - Use `layout.html` to wrap child routes.
31
33
  - Use `layout.py` when a layout needs shared synchronous props or metadata before rendering.
32
34
 
@@ -46,7 +48,7 @@ If you already know the Next.js App Router, use this translation layer:
46
48
  | Next.js concept | Caspian equivalent |
47
49
  | --- | --- |
48
50
  | `app/` | `src/app/` |
49
- | `page.tsx` | `index.html` or `index.py` |
51
+ | `page.tsx` | `index.html` plus optional `index.py` companion |
50
52
  | `layout.tsx` | `layout.html` and optional `layout.py` |
51
53
  | `[id]` | `[id]` |
52
54
  | `[...slug]` | `[...slug]` |
@@ -61,21 +63,33 @@ This means most App Router habits carry over directly:
61
63
 
62
64
  ## Core Concepts
63
65
 
64
- Every route lives inside `src/app`. A route is a folder that contains either an `index.html` file, an `index.py` file, or both as part of the route implementation.
66
+ Every route lives inside `src/app`. For routes that render UI, `index.html` owns the markup and `index.py` is only an optional companion for server logic or metadata.
67
+
68
+ Use this decision rule when creating routes:
69
+
70
+ | Route shape | Files |
71
+ | --- | --- |
72
+ | UI only | `index.html` |
73
+ | UI plus backend logic or metadata | `index.html` and `index.py` |
74
+ | No rendered page, backend only | `index.py` |
65
75
 
66
76
  Examples:
67
77
 
68
- | File | URL |
78
+ | Files | URL |
69
79
  | --- | --- |
70
80
  | `src/app/index.html` | `/` |
71
- | `src/app/about/index.py` | `/about` |
81
+ | `src/app/about/index.html` | `/about` |
82
+ | `src/app/dashboard/index.html` and `src/app/dashboard/index.py` | `/dashboard` |
72
83
  | `src/app/blog/posts/index.html` | `/blog/posts` |
84
+ | `src/app/(auth)/signout/index.py` | `/signout` |
73
85
 
74
86
  ### `index.html`
75
87
 
76
88
  Use `index.html` for the route template. This is the route's view layer.
77
89
 
78
- Route templates can import reusable Python components with `<!-- @import ... -->` comments and render them with JSX-style tags such as `<Button />`. Use [components.md](./components.md) for the component authoring rules.
90
+ If a route renders visible page content, that content belongs here even when the route also has an `index.py` companion.
91
+
92
+ Route templates can import reusable Python components with `<!-- @import ... -->` comments and render them with HTML-first `x-*` tags such as `<x-button />`. Use [components.md](./components.md) for the component authoring rules.
79
93
 
80
94
  Place those import comments at the top of the file, above the authored root element. They are file-level directives, not children of the route root.
81
95
 
@@ -116,7 +130,7 @@ Also bad:
116
130
  ```html
117
131
  <section class="dashboard-shell">
118
132
  <!-- @import StatsCard from "../components" -->
119
- <StatsCard title="Users" value="42" />
133
+ <x-stats-card title="Users" value="42" />
120
134
  </section>
121
135
  ```
122
136
 
@@ -126,7 +140,7 @@ Example authored route template:
126
140
  <!-- @import StatsCard from "../components" -->
127
141
 
128
142
  <section class="dashboard-shell">
129
- <StatsCard title="Users" value="42" />
143
+ <x-stats-card title="Users" value="42" />
130
144
 
131
145
  <script>
132
146
  const [filter, setFilter] = pp.state("all");
@@ -152,7 +166,9 @@ Write the first form. Caspian produces the second form by injecting `pp-componen
152
166
 
153
167
  ### `index.py`
154
168
 
155
- Use `index.py` when the route needs metadata or async server-side logic. Because Caspian runs on FastAPI, the page entry should be async.
169
+ Use `index.py` as the backend companion when a route needs metadata or async server-side logic. For routes that render UI, keep the markup in the sibling `index.html` and let `page()` call `render_page(__file__, ...)`. Do not inline route HTML inside `index.py`.
170
+
171
+ Use `index.py` by itself only for non-visual routes such as redirects or action-only handlers. Because Caspian runs on FastAPI, the page entry should be async when it performs async work.
156
172
 
157
173
  Example:
158
174
 
@@ -168,7 +184,7 @@ async def page():
168
184
  return render_page(__file__)
169
185
  ```
170
186
 
171
- Use this pattern when the route needs to fetch data, compute metadata, or do other non-blocking server work before rendering.
187
+ Use this pattern when the route needs to fetch data, compute metadata, or do other non-blocking server work before rendering the sibling `index.html`.
172
188
 
173
189
  When a route owns a file manager or upload UI, keep the owning upload and delete `@rpc()` actions in that same `index.py` and move reusable filesystem or Prisma helpers into `src/lib/`. Do not move ordinary upload behavior into `main.py`. See [file-uploads.md](./file-uploads.md).
174
190
 
@@ -186,10 +202,12 @@ Wrap a folder name in brackets to make it variable.
186
202
 
187
203
  | File | Example URL |
188
204
  | --- | --- |
189
- | `src/app/users/[id]/index.py` | `/users/123` |
205
+ | `src/app/users/[id]/index.html` | `/users/123` |
190
206
 
191
207
  These segments are compiled into FastAPI path parameters for efficient route matching.
192
208
 
209
+ Add a sibling `index.py` when the dynamic route needs params during render, metadata, or other backend logic.
210
+
193
211
  In the current `main.py` router, path params are collected into a single dict and passed as the first positional argument to `page()`. Matching query params can still be injected by name, and `request` is passed by keyword when declared.
194
212
 
195
213
  Example:
@@ -208,20 +226,22 @@ Use an ellipsis inside brackets to match multiple path parts.
208
226
 
209
227
  | File | Example URL |
210
228
  | --- | --- |
211
- | `src/app/docs/[...slug]/index.py` | `/docs/getting-started/setup` |
229
+ | `src/app/docs/[...slug]/index.html` | `/docs/getting-started/setup` |
212
230
 
213
231
  Use catch-all routes when the number of path segments is not fixed ahead of time.
214
232
 
233
+ Add a sibling `index.py` when that catch-all route also needs backend logic or metadata.
234
+
215
235
  ## Route Groups
216
236
 
217
237
  Wrap a folder name in parentheses to organize code without adding that segment to the URL.
218
238
 
219
239
  Examples:
220
240
 
221
- | File | URL |
241
+ | Files | URL |
222
242
  | --- | --- |
223
- | `src/app/(auth)/login/index.py` | `/login` |
224
- | `src/app/(auth)/register/index.py` | `/register` |
243
+ | `src/app/(auth)/signin/index.html` and `src/app/(auth)/signin/index.py` | `/signin` |
244
+ | `src/app/(auth)/signup/index.html` and `src/app/(auth)/signup/index.py` | `/signup` |
225
245
 
226
246
  Route groups are useful when you want to:
227
247
 
@@ -292,18 +312,25 @@ src/
292
312
  index.html
293
313
  users/
294
314
  [id]/
315
+ index.html
295
316
  index.py
296
317
  docs/
297
318
  [...slug]/
319
+ index.html
298
320
  index.py
299
321
  (auth)/
300
322
  login/
323
+ index.html
301
324
  index.py
302
325
  register/
326
+ index.html
327
+ index.py
328
+ signout/
303
329
  index.py
304
330
  dashboard/
305
331
  layout.html
306
332
  settings/
333
+ index.html
307
334
  index.py
308
335
  ```
309
336
 
@@ -313,7 +340,8 @@ If an AI agent is choosing where to add or update route code, apply these rules
313
340
 
314
341
  - Treat `src/app/` as the routing source of truth.
315
342
  - Use folder names to model URL segments.
316
- - Use `index.html` for route templates and `index.py` for route-level async logic.
343
+ - If a route renders UI, create or update `index.html` for the markup.
344
+ - Add `index.py` only when the same route needs metadata or server behavior; do not place route HTML in `index.py`.
317
345
  - Use [cache.md](./cache.md) when an `index.py` route should opt into page-level HTML caching.
318
346
  - Use `layout.html` for shared wrappers and `layout.py` for layout-level synchronous props or metadata.
319
347
  - Keep `<!-- @import ... -->` directives at the top of `index.html` and `layout.html`, above the single authored root element.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "caspian-utils",
3
- "version": "0.0.17",
3
+ "version": "0.0.19",
4
4
  "description": "Caspian tooling",
5
5
  "main": "index.js",
6
6
  "scripts": {