caspian-utils 0.0.20 → 0.0.22

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.
@@ -236,9 +236,10 @@ Use this pattern for real file managers:
236
236
 
237
237
  - Keep upload and delete actions in the owning route `index.py`, not in `main.py`.
238
238
  - Use `page()` to render the initial manager payload.
239
- - Store durable metadata in Prisma and store the browser-accessible blob separately under `public/assets/file-manager/`.
239
+ - Store durable metadata in Prisma and store the browser-accessible blob separately under a public upload directory such as `public/uploads/`.
240
+ - Create `public/uploads` on demand in the shared helper if the directory does not exist yet.
240
241
  - Use `pp.state(...)` plus `pp-for` for the list UI instead of manual `innerHTML` writes.
241
- - Add `public/assets/file-manager` to `PUBLIC_IGNORE_DIRS` in `settings/bs-config.ts` so runtime uploads do not trigger BrowserSync reloads during `npm run dev`.
242
+ - Add the public-root-relative directory name `uploads` to `PUBLIC_IGNORE_DIRS` in `settings/bs-config.ts` when `public/uploads/` is the active upload root so runtime uploads do not trigger BrowserSync reloads during `npm run dev`.
242
243
 
243
244
  Read [file-uploads.md](./file-uploads.md) for the complete file-manager pattern.
244
245
 
@@ -23,7 +23,8 @@ Treat uploads as normal route behavior. Keep the owning browser UI in the route
23
23
  - Keep first-render file manager data in `page()` so the initial HTML already contains the current asset list and storage summary.
24
24
  - Keep upload and delete actions in the route's `index.py`; do not move ordinary upload flows into `main.py`.
25
25
  - Keep reusable file-manager helpers in `src/lib/`.
26
- - Store uploaded blobs under a project-owned public directory such as `public/storage/...` when the files should be browser-accessible.
26
+ - Store uploaded blobs under a project-owned public directory such as `public/uploads/...` when the files should be browser-accessible.
27
+ - Create the upload directory on demand in the shared helper when it does not exist yet; do not assume the folder is committed.
27
28
  - Store durable metadata in Prisma, not in JSON manifests or ad hoc metadata files.
28
29
  - Use `pp.state(...)` plus `pp-for` to render and update the file list from returned server payloads.
29
30
  - Use `onUploadProgress` only for progress UI; let the RPC return refreshed manager data for the authoritative post-upload state.
@@ -36,7 +37,7 @@ Treat uploads as normal route behavior. Keep the owning browser UI in the route
36
37
  | Upload and delete `@rpc()` actions | `src/app/**/index.py` | Keep these route-local so they stay close to the owning page. |
37
38
  | Shared storage, normalization, and persistence helpers | `src/lib/**` | Reuse helpers across routes without pushing route behavior into app bootstrap. |
38
39
  | Upload metadata model | `prisma/schema.prisma` | Persist owner, file name, MIME type, path, size, collection, and timestamps in Prisma. |
39
- | Browser-accessible uploaded blobs | `public/storage/**` or another app-owned public directory | Keep the public path predictable and derived from stored metadata. |
40
+ | Browser-accessible uploaded blobs | `public/uploads/**` or another app-owned public directory | Keep the public path predictable and derived from stored metadata, and create the directory on demand if it may not exist yet. |
40
41
  | BrowserSync upload ignore | `settings/bs-config.ts` | Keep the active public upload directory in `PUBLIC_IGNORE_DIRS`. |
41
42
 
42
43
  ## Route Flow
@@ -143,15 +144,17 @@ Typical metadata fields include:
143
144
 
144
145
  ## BrowserSync And Uploaded Public Files
145
146
 
146
- If runtime uploads write into `public/assets/file-manager/`, BrowserSync should ignore that directory during local development. Otherwise every upload can trigger a full browser reload.
147
+ If runtime uploads write into `public/uploads/`, BrowserSync should ignore that directory during local development. Otherwise every upload can trigger a full browser reload.
147
148
 
148
- Use a workspace-relative ignore entry in `settings/bs-config.ts`:
149
+ The helper that stores uploaded files should create `public/uploads` before writing the first file when the directory is not present yet.
150
+
151
+ Use a public-root-relative ignore entry in `settings/bs-config.ts`:
149
152
 
150
153
  ```ts
151
- const PUBLIC_IGNORE_DIRS = ["public/assets/file-manager"];
154
+ const PUBLIC_IGNORE_DIRS = ["uploads"];
152
155
  ```
153
156
 
154
- Match those entries against workspace-relative paths so nested uploads such as `public/assets/file-manager/user-1/media/example.png` are ignored too.
157
+ `settings/bs-config.ts` matches those entries against paths relative to the `public/` root, so nested uploads such as `uploads/user-1/media/example.png` are ignored too.
155
158
 
156
159
  ## What To Avoid
157
160
 
@@ -167,5 +170,5 @@ Match those entries against workspace-relative paths so nested uploads such as `
167
170
  - Use `fetch-data.md` for the route-render versus RPC split.
168
171
  - Use `database.md` when Prisma models or relations must change for upload metadata.
169
172
  - Use `validation.md` for MIME, extension, and other boundary checks, then keep explicit size and auth checks in the owning RPC action.
170
- - Use `project-structure.md` for placement rules, especially `src/app/` versus `src/lib/` and `public/assets/file-manager/`.
173
+ - Use `project-structure.md` for placement rules, especially `src/app/` versus `src/lib/` and `public/uploads/`.
171
174
  - Use `commands.md` and `settings/bs-config.ts` when uploads should not trigger BrowserSync reloads during `npm run dev`.
@@ -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(...)`.
@@ -172,7 +172,9 @@ This folder contains your database model definitions in `schema.prisma` and any
172
172
 
173
173
  Store static assets here, including images, fonts, and generated frontend assets that should be served directly.
174
174
 
175
- Runtime-uploaded public blobs can also live here. Confirm the actual upload path in the project code and keep that directory aligned with any BrowserSync ignore rules.
175
+ Runtime-uploaded public blobs can also live here. Confirm the actual upload path in the project code, commonly `public/uploads/`, and keep that directory aligned with any BrowserSync ignore rules.
176
+
177
+ If the upload directory is created only at runtime, create it on demand in the owning upload helper instead of assuming it is already committed.
176
178
 
177
179
  If the local BrowserSync stack is running, keep that upload directory in `settings/bs-config.ts` `PUBLIC_IGNORE_DIRS` so new uploads do not force a full browser reload.
178
180
 
@@ -205,7 +207,9 @@ Read the actual values in `caspian.config.json` instead of inferring feature sta
205
207
 
206
208
  The BrowserSync watcher configuration for the local stack lives here.
207
209
 
208
- When runtime uploads write into `public/assets/file-manager/`, keep `public/assets/file-manager` in `PUBLIC_IGNORE_DIRS` and match it against workspace-relative paths so nested uploaded files do not trigger reloads.
210
+ When runtime uploads write into `public/uploads/`, keep the public-root-relative entry `uploads` in `PUBLIC_IGNORE_DIRS`.
211
+
212
+ `settings/bs-config.ts` matches ignore entries against paths relative to the `public/` root, so nested uploaded files do not trigger reloads.
209
213
 
210
214
  ### `src/lib/auth/auth_config.py`
211
215
 
@@ -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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "caspian-utils",
3
- "version": "0.0.20",
3
+ "version": "0.0.22",
4
4
  "description": "Caspian tooling",
5
5
  "main": "index.js",
6
6
  "scripts": {