caspian-utils 0.0.19 → 0.0.21
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/components.md +40 -3
- package/dist/docs/index.md +3 -2
- package/dist/docs/metadata.md +2 -0
- package/dist/docs/routing.md +68 -0
- package/package.json +1 -1
package/dist/docs/components.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: Components
|
|
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.
|
|
3
|
+
description: Use this page when the task mentions `@component`, reusable UI, HTML-first `x-*` component tags, component imports, same-name `.html` templates, `merge_classes(...)`, `twMerge(...)`, 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.
|
|
@@ -31,7 +31,7 @@ As the app grows, treat `src/components/` as the default home for reusable appli
|
|
|
31
31
|
|
|
32
32
|
## Basic Component
|
|
33
33
|
|
|
34
|
-
This is the simplest pattern: accept props,
|
|
34
|
+
This is the simplest pattern: accept props, assemble the final class value, and return HTML.
|
|
35
35
|
|
|
36
36
|
```python
|
|
37
37
|
from casp.component_decorator import component
|
|
@@ -53,7 +53,44 @@ Notes:
|
|
|
53
53
|
|
|
54
54
|
- `children` receives the inner content passed between opening and closing tags.
|
|
55
55
|
- `**props` lets the component accept additional HTML attributes such as `id`, `data-*`, and `aria-*`.
|
|
56
|
-
- The runtime normalizes `class`, `className`, and `class_name` into
|
|
56
|
+
- The runtime normalizes `class`, `className`, and `class_name` into one `class` value before the component is called.
|
|
57
|
+
- Pass the `merge_classes(...)` result straight into `get_attributes(...)` or the rendered `class` attribute; do not wrap it in another helper.
|
|
58
|
+
|
|
59
|
+
## Tailwind Merge Contract
|
|
60
|
+
|
|
61
|
+
When `caspian.config.json` has `tailwindcss: true`, Caspian uses a frontend-first Tailwind merge contract.
|
|
62
|
+
|
|
63
|
+
- In Python components, use `merge_classes(...)` to assemble class defaults plus incoming class props.
|
|
64
|
+
- `merge_classes(...)` emits a frontend-ready `{twMerge(...)}` expression instead of doing Python-side Tailwind conflict resolution.
|
|
65
|
+
- In authored PulsePoint markup and scripts, use global `twMerge(...)` directly for attribute expressions and script-local derived values.
|
|
66
|
+
|
|
67
|
+
Python example:
|
|
68
|
+
|
|
69
|
+
```python
|
|
70
|
+
from casp.component_decorator import component
|
|
71
|
+
from casp.html_attrs import get_attributes, merge_classes
|
|
72
|
+
|
|
73
|
+
@component
|
|
74
|
+
def IconButton(**props) -> str:
|
|
75
|
+
incoming_class = props.pop("class", "")
|
|
76
|
+
final_class = merge_classes("size-4 rounded-md", incoming_class)
|
|
77
|
+
|
|
78
|
+
attributes = get_attributes({
|
|
79
|
+
"class": final_class,
|
|
80
|
+
}, props)
|
|
81
|
+
|
|
82
|
+
return f'<button {attributes}></button>'
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Authored PulsePoint examples:
|
|
86
|
+
|
|
87
|
+
```html
|
|
88
|
+
<p class="{twMerge(baseClass, inputClass)}">Merged badge preview</p>
|
|
89
|
+
|
|
90
|
+
<script>
|
|
91
|
+
const merged = twMerge(baseClass, inputClass);
|
|
92
|
+
</script>
|
|
93
|
+
```
|
|
57
94
|
|
|
58
95
|
## Import And Use Components
|
|
59
96
|
|
package/dist/docs/index.md
CHANGED
|
@@ -26,6 +26,7 @@ Before making feature, tooling, or file-placement decisions in a Caspian project
|
|
|
26
26
|
When generating or editing a Caspian app, treat these as the default choices unless the task explicitly requires something else:
|
|
27
27
|
|
|
28
28
|
- Use PulsePoint for reactive frontend behavior.
|
|
29
|
+
- When `caspian.config.json` has `tailwindcss: true`, use Python `merge_classes(...)` plus browser `twMerge(...)` as the only supported Tailwind class-merging path.
|
|
29
30
|
- Use `@rpc()` plus `pp.rpc()` for browser-triggered reads, writes, streaming, and uploads.
|
|
30
31
|
- Use `Validate` and `Rule` from `casp.validate` for server-side input validation and sanitization.
|
|
31
32
|
|
|
@@ -42,8 +43,8 @@ The packaged Caspian docs referenced by this index live here:
|
|
|
42
43
|
- `mcp.md` - MCP-specific layout, launch flow, and AI routing rules for projects where `caspian.config.json` enables MCP
|
|
43
44
|
- `database.md` - Prisma schema, migration, seed, and client-generation workflow for projects where `caspian.config.json` enables Prisma, plus Python-side helper caveats
|
|
44
45
|
- `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, HTML-first `x-*` component tags,
|
|
46
|
-
- `pulsepoint.md` - Default reactive frontend runtime contract for component scripts, state, effects, directives,
|
|
46
|
+
- `components.md` - Create reusable Python components, template-backed UI, HTML-first `x-*` component tags, the single-parent root rule for component HTML files, and the Python-side `merge_classes(...)` contract when Tailwind CSS is enabled
|
|
47
|
+
- `pulsepoint.md` - Default reactive frontend runtime contract for component scripts, state, effects, directives, client-side behaviors, and direct browser `twMerge(...)` usage when Tailwind CSS is enabled
|
|
47
48
|
- `fetch-data.md` - Initial server-side data loading and browser-triggered RPC flows with `pp.rpc()`, streaming, uploads, and auth-aware actions
|
|
48
49
|
- `file-uploads.md` - Route-local file uploads and file-manager flows with `@rpc()`, `pp.rpc()`, Prisma metadata, public asset storage, and BrowserSync ignore rules
|
|
49
50
|
- `state.md` - Request-scoped server state with `StateManager`, session-backed JSON persistence, and listener callbacks for transient flows
|
package/dist/docs/metadata.md
CHANGED
|
@@ -176,6 +176,7 @@ Use stable, publicly reachable image paths for social cards so crawlers can fetc
|
|
|
176
176
|
Keep visual layout data and SEO metadata separate.
|
|
177
177
|
|
|
178
178
|
- Values returned from `layout()` are exposed as `[[ layout.* ]]`.
|
|
179
|
+
- The second dict returned from `page()` as `(page_html, layout_props_dict)` is also exposed to wrapping layouts as `[[ layout.* ]]`.
|
|
179
180
|
- SEO values are exposed as `[[ metadata.* ]]`.
|
|
180
181
|
- Do not return `title` or `description` from `layout()` expecting SEO changes.
|
|
181
182
|
- The layout engine explicitly strips `title` and `description` from layout props to avoid mixing visual props with metadata.
|
|
@@ -195,6 +196,7 @@ If an AI agent is deciding where to put SEO fields, apply these rules first.
|
|
|
195
196
|
- Prefer module-level `metadata = Metadata(...)` for static routes.
|
|
196
197
|
- Instantiate `Metadata(...)` inside `page()` when metadata depends on params, fetched records, or generated content.
|
|
197
198
|
- Put shared defaults in `layout.py` and let leaf pages override only what they need.
|
|
199
|
+
- If a single route only needs to tweak a wrapping layout, return `(render_page(__file__, ...), {"dashboard_body_class": ...})` from `page()` instead of moving that prop into metadata.
|
|
198
200
|
- Use `extra` for Open Graph and Twitter card tags.
|
|
199
201
|
- Access `extra` values in templates with bracket syntax such as `metadata['og:image']`.
|
|
200
202
|
- Keep `layout()` return data in `[[ layout.* ]]` and keep SEO fields in `Metadata(...)`.
|
package/dist/docs/routing.md
CHANGED
|
@@ -186,6 +186,37 @@ async def page():
|
|
|
186
186
|
|
|
187
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`.
|
|
188
188
|
|
|
189
|
+
`page()` may also return a 2-item tuple: `(page_html, layout_props_dict)`.
|
|
190
|
+
|
|
191
|
+
- The first item is the rendered page HTML, usually `render_page(__file__, page_context)`.
|
|
192
|
+
- The second item must be a dict. Its keys are merged into the wrapping layout context and become available to parent layouts as `[[ layout.* ]]`.
|
|
193
|
+
|
|
194
|
+
Use that tuple form when one route needs to influence a wrapper without turning that value into a section-wide default. A common example is a dashboard page that needs to lock the root body with `overflow-hidden` while the rest of the app keeps normal scrolling.
|
|
195
|
+
|
|
196
|
+
Example root layout:
|
|
197
|
+
|
|
198
|
+
```html
|
|
199
|
+
<body class="[[ layout.dashboard_body_class | default('') ]]">
|
|
200
|
+
[[ children | safe ]]
|
|
201
|
+
</body>
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
Example route:
|
|
205
|
+
|
|
206
|
+
```python
|
|
207
|
+
from casp.layout import render_page
|
|
208
|
+
|
|
209
|
+
async def page():
|
|
210
|
+
return (
|
|
211
|
+
render_page(__file__),
|
|
212
|
+
{"dashboard_body_class": "w-screen h-screen overflow-hidden"},
|
|
213
|
+
)
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
The key name is arbitrary, but it must match exactly between the dict returned from `page()` and the `[[ layout.some_key ]]` lookup in `layout.html`.
|
|
217
|
+
|
|
218
|
+
Use distinct names for those layout props. In the current router, the second dict is merged into the full layout context after path params and `request`, so a key such as `slug` or `request` can shadow an existing value.
|
|
219
|
+
|
|
189
220
|
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).
|
|
190
221
|
|
|
191
222
|
For static and dynamic metadata rules, inheritance order, and social card fields, see [metadata.md](./metadata.md).
|
|
@@ -289,12 +320,47 @@ def layout(context_data):
|
|
|
289
320
|
|
|
290
321
|
return {
|
|
291
322
|
"user": user,
|
|
323
|
+
"dashboard_body_class": "w-screen h-screen overflow-hidden",
|
|
292
324
|
"theme": "dark",
|
|
293
325
|
}
|
|
294
326
|
```
|
|
295
327
|
|
|
296
328
|
`context_data` includes URL parameters such as dynamic route values.
|
|
297
329
|
|
|
330
|
+
In the common case, return a dict and let the sibling `layout.html` read those values through `[[ layout.* ]]`.
|
|
331
|
+
|
|
332
|
+
`layout()` currently supports these result shapes:
|
|
333
|
+
|
|
334
|
+
- `dict`: load the sibling `layout.html` and expose the dict as `[[ layout.* ]]`
|
|
335
|
+
- `str`: use that string as the layout content
|
|
336
|
+
- `(layout_html, props_dict)`: use the first item as the layout content and expose the second dict as `[[ layout.* ]]`
|
|
337
|
+
- `None`: fall back to the sibling `layout.html` with no extra layout props
|
|
338
|
+
|
|
339
|
+
If you intentionally want to render `layout.html` immediately with direct local variables instead of the `layout.*` namespace, call `render_layout(__file__, {...})` and reference those keys directly in the template.
|
|
340
|
+
|
|
341
|
+
Example:
|
|
342
|
+
|
|
343
|
+
```python
|
|
344
|
+
from casp.layout import render_layout
|
|
345
|
+
|
|
346
|
+
def layout():
|
|
347
|
+
return render_layout(__file__, {"my_class": "size-8"})
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
In that pattern, the matching `layout.html` reads `[[ my_class ]]`, not `[[ layout.my_class ]]`, because the template string was already rendered before the nested layout pipeline continues.
|
|
351
|
+
|
|
352
|
+
If you need both a custom layout string and standard `[[ layout.* ]]` props, return a tuple:
|
|
353
|
+
|
|
354
|
+
```python
|
|
355
|
+
from casp.layout import render_layout
|
|
356
|
+
|
|
357
|
+
def layout():
|
|
358
|
+
return (
|
|
359
|
+
render_layout(__file__),
|
|
360
|
+
{"dashboard_body_class": "w-screen h-screen overflow-hidden"},
|
|
361
|
+
)
|
|
362
|
+
```
|
|
363
|
+
|
|
298
364
|
`layout()` currently runs synchronously in `casp.layout`. If you need async I/O, load it in `page()` instead of `layout.py`.
|
|
299
365
|
|
|
300
366
|
Use [metadata.md](./metadata.md) when a layout also needs SEO defaults. Return dictionaries from `layout()` for visual or template props, and use `Metadata(...)` for title, description, and social tags.
|
|
@@ -344,6 +410,8 @@ If an AI agent is choosing where to add or update route code, apply these rules
|
|
|
344
410
|
- Add `index.py` only when the same route needs metadata or server behavior; do not place route HTML in `index.py`.
|
|
345
411
|
- Use [cache.md](./cache.md) when an `index.py` route should opt into page-level HTML caching.
|
|
346
412
|
- Use `layout.html` for shared wrappers and `layout.py` for layout-level synchronous props or metadata.
|
|
413
|
+
- When one route needs to change a parent layout, return `(render_page(__file__, ...), {"dashboard_body_class": ...})` from `page()` and read that value as `[[ layout.dashboard_body_class ]]` in the wrapping `layout.html`.
|
|
414
|
+
- Use `layout.py` for layout props that should apply across an entire subtree. Use `render_layout(__file__, {...})` only when the layout should consume direct local variables such as `[[ my_class ]]` instead of the standard `[[ layout.* ]]` namespace.
|
|
347
415
|
- Keep `<!-- @import ... -->` directives at the top of `index.html` and `layout.html`, above the single authored root element.
|
|
348
416
|
- Use [metadata.md](./metadata.md) when a route or layout needs SEO fields.
|
|
349
417
|
- Use `[segment]` for single dynamic parameters.
|