create-caspian-app 1.3.13 → 1.3.15

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.
@@ -36,7 +36,7 @@ This is the top architectural requirement for this workspace. Treat it as a hard
36
36
  - For current repo behavior, trust `main.py`, `src/lib/**`, `public/js/**`, `prisma/**`, and `src/app/**` over generic Caspian docs.
37
37
  - For framework internals, trust `.venv/Lib/site-packages/casp/**` over generic or older upstream guidance.
38
38
  - When packaged docs conflict with project code or installed runtime, the project code, `caspian.config.json`, and installed runtime win. Keep the packaged docs feature-oriented and point AI back to the project files that decide actual enablement and behavior.
39
- - When `prisma/schema.prisma` changes, follow this order: run `npx prisma migrate dev`; if the change affects seed flow or `prisma/seed.ts`, run `npx prisma generate` and then consider `npx prisma db seed`; then run `npx ppy generate` so the Python ORM stays aligned with the schema. Treat `npx prisma db seed` as a destructive data operation: it may clean tables and replace existing records, including production data if pointed at the wrong database. Before running it, tell the user exactly which command you intend to run, explain that it can delete or overwrite database data, confirm the current datasource when practical, and wait for the user's explicit approval.
39
+ - When `prisma/schema.prisma` changes, exactly two commands are required, in order. **Step 1 — sync the database, pick one:** `npx prisma migrate dev` (development default, creates and applies a migration) or `npx prisma db push` (migration-less direct sync). **Step 2 — always:** `npx ppy generate`, the **only** command that regenerates the Python ORM the app imports (`src/lib/prisma/**`, `settings/prisma-schema.json`). Do not confuse the two generators: `npx prisma generate` builds the **Node/TypeScript** `@prisma/client` used only by `prisma/seed.ts` it writes zero Python and is never a substitute for `npx ppy generate`. If the change affects seed flow or `prisma/seed.ts`, the optional seed steps (`npx prisma generate`, then `npx prisma db seed`) go between step 1 and step 2. Treat `npx prisma db seed` as a destructive data operation: it may clean tables and replace existing records, including production data if pointed at the wrong database. Before running it, tell the user exactly which command you intend to run, explain that it can delete or overwrite database data, confirm the current datasource when practical, and wait for the user's explicit approval.
40
40
  - Reuse the existing Python database layer in `src/lib/prisma/**`; do not create a second app-owned database abstraction unless the user explicitly asks for one.
41
41
  - When `caspian.config.json` has `prisma: true`, all Python-side database reads and writes must go through the generated Prisma Python ORM exposed from `src/lib/prisma/**`. Do not bypass it with ad hoc sqlite/postgres drivers, hand-written fetch helpers, JSON files as active stores, browser-side database fetches, or custom HTTP endpoints that reinvent the ORM. Use raw SQL only through Prisma as a narrow fallback when the generated ORM cannot express the query clearly.
42
42
  - Treat `src/lib/prisma/__init__.py`, `src/lib/prisma/db.py`, `src/lib/prisma/models.py`, and `settings/prisma-schema.json` as generated outputs owned by `npx ppy generate`; do not create or hand-edit them manually.
@@ -211,9 +211,10 @@ This is the top architectural requirement for this workspace. Treat it as a hard
211
211
 
212
212
  - Treat `prisma/schema.prisma` as the data-model source of truth.
213
213
  - Treat `prisma.config.ts` as the datasource and migration or seed configuration source of truth.
214
- - After changing `prisma/schema.prisma`, run `npx prisma migrate dev` first so migrations and the development database stay aligned.
214
+ - After changing `prisma/schema.prisma`, first sync the database: `npx prisma migrate dev` (development default, keeps migration history) or `npx prisma db push` (migration-less direct sync).
215
215
  - If the schema change affects seed data or `prisma/seed.ts`, run `npx prisma generate`, then ask for explicit user approval before running `npx prisma db seed` because the seed script may delete or replace table data.
216
- - Run `npx ppy generate` after every schema change so the Python ORM files and `settings/prisma-schema.json` stay aligned with Prisma.
216
+ - **Always** run `npx ppy generate` after every schema change so the Python ORM files and `settings/prisma-schema.json` stay aligned with Prisma. It is the only command that generates those files.
217
+ - The two generators are not interchangeable: `npx prisma generate` writes the Node/TypeScript `@prisma/client` (consumed only by `prisma/seed.ts`), while `npx ppy generate` writes the Python ORM the app imports from `src.lib.prisma`. Running `npx prisma generate` never refreshes the Python side.
217
218
  - Keep Node-side generation and seeding aligned with `npx prisma generate` and `prisma/seed.ts`.
218
219
  - Keep Python-side database access aligned with `src/lib/prisma/**`, and treat that directory as generated output rather than a manual editing surface.
219
220
 
package/dist/AGENTS.md CHANGED
@@ -72,6 +72,7 @@ Use `.github/copilot-instructions.md` for the repo-wide implementation rules. Th
72
72
  - Use `node_modules/caspian-utils/dist/docs/websockets.md` when the task names WebSockets, live bidirectional channels, socket origin checks, socket auth/session behavior, broadcast managers, or native browser `WebSocket` clients.
73
73
  - Use `node_modules/caspian-utils/dist/docs/file-conventions.md` for the general special-file model, then verify the completed Python migration in `main.py` and `.venv/Lib/site-packages/casp/**`: routes use `index.py`, layouts use `layout.py`, loading UI uses `loading.py`, and global fallback pages use `not_found.py` and `error.py`. This app has no authored `.html` special files.
74
74
  - When `caspian.config.json` has `prisma: true`, database reads and writes from Python routes, layouts, RPC actions, upload flows, auth flows, and helpers must use the generated Prisma Python ORM in `src/lib/prisma/**`. Do not create a separate database fetch layer with raw drivers, hand-written SQL helpers, JSON manifests, app-specific HTTP fetches, or browser-side data fetches to replace the ORM. Use raw SQL only as a narrow Prisma ORM fallback when the generated client cannot express a query clearly.
75
+ - **After any `prisma/schema.prisma` change, exactly two commands are required, in order.** Step 1 — sync the database, pick one: `npx prisma migrate dev` (development default, creates and applies a migration) or `npx prisma db push` (migration-less direct sync). Step 2 — always: `npx ppy generate`, the **only** command that regenerates the Python ORM the app imports (`src/lib/prisma/__init__.py`, `db.py`, `models.py`, `settings/prisma-schema.json`). The two generators are different toolchains from the same schema: `npx prisma generate` builds the Node/TypeScript `@prisma/client` used only by `prisma/seed.ts` and writes zero Python — it is never a substitute for `npx ppy generate`. Never hand-write or patch the generated Python ORM instead of regenerating it; the generated client is ready to import from `src.lib.prisma`. See `node_modules/caspian-utils/dist/docs/database.md` "Two Generators, One Schema".
75
76
  - Treat `npx prisma db seed` as a delicate, potentially destructive operation. In this workspace, seed scripts may clear tables before inserting fresh records. Before running that command, an AI agent must propose the exact command, warn that it can delete or overwrite database data including production data if the datasource is wrong, confirm the datasource when practical, and wait for explicit user approval.
76
77
  - Component-first page composition is the highest-priority authoring rule for this workspace (see `.github/copilot-instructions.md`). Build pages as a short assembly of `x-*` chunk components (top menu, sidebar, header, content sections, cards, forms, footer) and keep each chunk's long markup inside its own focused single-file `html(...)` component, so the page template in `src/app/**/index.py` stays small instead of holding a wall of HTML. Plan the chunk breakdown before writing the route, not as a later cleanup pass.
77
78
  - **PulsePoint is not React and its templates are not JSX.** This workspace's guidance compares PulsePoint to React in exactly two places — the `pp.*` hook API inside `<script>`, and how components are split by responsibility — and that comparison stops at the markup. Template files are plain HTML. Never generate `{cond && (<div/>)}`, `{cond ? <A/> : <B/>}`, `{list.map(item => (<tr/>))}`, `className`, `htmlFor`, camelCase `onClick`, `style={{...}}`, `dangerouslySetInnerHTML`, or `<>…</>`. Use `hidden="{!cond}"` for conditionals, `<template pp-for="item in list">` with `key="{item.id}"` for lists, and **always quote brace attributes** — `class="{...}"`, never `class={...}`. The unquoted form is invalid HTML: the parser splits the value on spaces into junk attributes, the component root never compiles, and the route serves a blank page with no console error (the body's `opacity: 0` reveal never fires). There is no `pp-if`, `pp-show`, `pp-else`, or `pp-key`. Sanity check before finishing any template: it must still be valid HTML with every `{}` deleted. See `node_modules/caspian-utils/dist/docs/pulsepoint.md` sections "PulsePoint Is Not JSX", "Complete Directive And API Surface", and "Conditional rendering".
@@ -108,7 +109,7 @@ Use `.github/copilot-instructions.md` for the repo-wide implementation rules. Th
108
109
  - Its boundary: it does not validate Tailwind/`globals.css`, `x-*` tag resolution, single-root violations, or `public/js/**`. Those still surface only at render time — verify front-end changes by loading the affected route in the browser (BrowserSync URL from `./settings/bs-config.json`).
109
110
  - **Browser console errors reach the `npm run dev` terminal and `.casp/browser-log.jsonl`.** `settings/dev-log-bridge.ts` adds a BrowserSync middleware serving `/__pp-devlog.js` and receiving `POST /__pp-devlog`; `_inject_dev_console_bridge(...)` in `main.py` adds the `<script>` tag, gated on **both** `IS_PRODUCTION` and `CASPIAN_BROWSER_SYNC_PORT`. That variable is normally set only by `settings/python-server.ts` when the dev stack spawns the server, but that is a convention about who sets it, not an enforcement — so the production check is what actually keeps the tag out of production and a static export, mirroring `_dev_cookie_scope`. The variable is deliberately absent from `.env`: `load_dotenv()` defaults to `override=False`, so the process env var always wins, and the dev stack picks a free port by walking upward from 5090 (`settings/bs-config.ts` `getAvailablePort`), making any hand-typed value either ignored or stale. The hook forwards only `[PP-ERROR]` / `[PP-WARN]` console output plus uncaught errors and unhandled rejections — ordinary `console.log` stays in the browser — and prints them with the route, message, and top stack frames, deduplicated within a 1s window. Do not switch this to the BrowserSync socket: mount-time errors fire before that socket connects, and BrowserSync's own `snippetOptions` injection does not fire against this app's proxied responses at all, which is why the tag comes from the render pipeline. `npm run check` only reports — auto-fix with `npm run check:fix` (runs `settings/fix.py`: safe ruff fixes, then the gate). Unused-import (`F401`) removal is guarded because component imports look unused to ruff: Caspian single-file components import children and use them only as `<x-*>` tags (`from .Dialog import DialogContent` → `<x-dialog-content>`), which ruff can't see, and casp resolves the tag from module globals at render time. So `F401` is `unfixable` in `pyproject.toml` (a raw `ruff check --fix` never deletes any import), and `settings/fix.py` removes dead imports only from files with no `<x-*>`-tag import (component files are skipped whole); `settings/check.py` suppresses the matching `F401` reports so the gate fails only on genuinely dead imports. Shared detection lives in `settings/_component_imports.py`. See `tests/README.md`. Tests live in `tests/`; tooling and config live in `pyproject.toml` (`[dependency-groups] dev`, `[tool.pyright]`, `[tool.ruff]`, `[tool.pytest.ini_options]`); install them with `uv sync --group dev`. The type checker is **pyright** (the same engine Pylance uses in the editor), so IDE squiggles and `npm run check` agree instead of disagreeing like the previous `pyrefly` setup. `[tool.pyright]` is `include = ["main.py", "src", "settings/*.py"]` with `exclude = [".venv", "node_modules", "**/__pycache__"]`, so it checks `main.py`, everything under `src` — including the generated `src/lib/prisma/**` ORM, which is analyzed, not excluded — and the `settings/*.py` orchestrator scripts (`check.py`, `fix.py`, `_component_imports.py`). It uses `typeCheckingMode = "basic"` (Pylance's default), and mirrors the old pyrefly suppressions by setting `reportReturnType = "none"` and `reportAssignmentType = "none"`; re-enable those per-rule when tightening. Because this is project-specific, keep it documented here and in `.github/copilot-instructions.md`, not in the packaged docs.
110
111
 
111
- - **The markup formatter proves each block before writing it, and a skip is a result rather than a failure.** `settings/format.py` runs djLint over every `html(r"""...""")` template, then checks the output against `settings/_markup_equivalence.py` — a tokenizer that decides whether the reformatted markup is *guaranteed* to render identically. Only proven blocks are written back; the rest are reported with a reason and left alone. The gap this closes is specific: djLint is a general HTML formatter, so it inserts a newline between a block tag and an adjacent inline or `<x-*>` tag, and that newline renders as a visible space because a custom element's `display` comes from CSS the formatter cannot see. Four rules make the oracle correct rather than merely cautious — whitespace inside a tag never renders; a whitespace run in text collapses to one space but presence-vs-absence between inline elements is significant; a text node's edge whitespace collapses only when its parent is block-level; and `<pre>`/`<textarea>` render verbatim while `<script>`/`<style>` are indentation-insensitive code. Those last two are additionally **masked out before djLint sees them**, so code is preserved by construction, not by proof — djLint otherwise reads `/>` inside a JS regex as a tag delimiter and rewrites `.replace(/>/g, …)` into `.replace( />/g, …)`. Do not resolve a skip by relaxing the oracle: a false positive there silently changes rendering across hundreds of templates at once, which is exactly the failure a bulk reformat cannot afford. Coverage is in `tests/test_format.py`, which pins both directions. Two tag families are registered deliberately: `<x-*>` component tags are given to djLint via `--custom-html` so a component tree nests instead of sitting flat, while the oracle still treats them as **inline** — so indenting tags already on separate lines is accepted and separating two touching tags is still refused; and SVG elements count as block-level in the oracle because an SVG fragment lays out no text, so indenting the children of an inline `<svg>` in a component template cannot change what is drawn (`<text>`, `<tspan>`, `<textPath>` and `<foreignObject>` are excluded, since they do render their content).
112
+ - **The markup formatter proves each block before writing it, and a skip is a result rather than a failure.** `settings/format.py` runs djLint over every `html(r"""...""")` template, then checks the output against `settings/_markup_equivalence.py` — a tokenizer that decides whether the reformatted markup is _guaranteed_ to render identically. Only proven blocks are written back; the rest are reported with a reason and left alone. The gap this closes is specific: djLint is a general HTML formatter, so it inserts a newline between a block tag and an adjacent inline or `<x-*>` tag, and that newline renders as a visible space because a custom element's `display` comes from CSS the formatter cannot see. Four rules make the oracle correct rather than merely cautious — whitespace inside a tag never renders; a whitespace run in text collapses to one space but presence-vs-absence between inline elements is significant; a text node's edge whitespace collapses only when its parent is block-level; and `<pre>`/`<textarea>` render verbatim while `<script>`/`<style>` are indentation-insensitive code. Those last two are additionally **masked out before djLint sees them**, so code is preserved by construction, not by proof — djLint otherwise reads `/>` inside a JS regex as a tag delimiter and rewrites `.replace(/>/g, …)` into `.replace( />/g, …)`. Do not resolve a skip by relaxing the oracle: a false positive there silently changes rendering across hundreds of templates at once, which is exactly the failure a bulk reformat cannot afford. Coverage is in `tests/test_format.py`, which pins both directions. Two tag families are registered deliberately: `<x-*>` component tags are given to djLint via `--custom-html` so a component tree nests instead of sitting flat, while the oracle still treats them as **inline** — so indenting tags already on separate lines is accepted and separating two touching tags is still refused; and SVG elements count as block-level in the oracle because an SVG fragment lays out no text, so indenting the children of an inline `<svg>` in a component template cannot change what is drawn (`<text>`, `<tspan>`, `<textPath>` and `<foreignObject>` are excluded, since they do render their content).
112
113
  - **A BOM makes a Python file invisible to the `templates` gate.** `check_templates.py` reads files with `path.read_text(encoding="utf-8")` and `ast.parse`, and a UTF-8 BOM makes that parse raise, which the scanner swallows as "skip this file". One file (`src/components/dashboard/ProductsPage.py`) carried a BOM and was silently exempt from every template rule, including `html-form` — it had been using the non-raw `html("""...""")` shape the whole time. `ruff format` strips the BOM, so the violation surfaced the moment formatting ran. If a template rule ever seems not to apply to a file, check for a BOM before assuming the rule is wrong.
113
114
  - **`npm run logs` is how an agent checks front-end health, because the dev terminal usually belongs to someone else.** The developer typically runs `npm run dev` in their own shell, so its stdout is invisible to an agent session — and starting a second dev stack is the wrong fix: `npm run dev` begins with `projectName`, which **deletes `.casp/` and `caches/`** out from under the running server, and then binds different ports and rewrites `settings/bs-config.json`, orphaning the browser tab the developer is actually looking at. **Never start a second `npm run dev` to get a log.** Instead `settings/dev-log-bridge.ts` appends every event to `.casp/browser-log.jsonl` (JSONL, one event per line, gitignored, truncated per dev session because `.casp/` is recreated at startup), and `settings/browser_log.py` renders it via `npm run logs`. `npm run check` prints the same digest at the end of its run but **never lets it affect the exit code** — whether a route has been exercised depends on someone clicking around, and a gate that flaky gets ignored; `--fail-on-error` opts in for scripts that want it, `--no-browser` skips the section.
114
115
  - **The log records successful page loads, not just errors.** This is the property that makes it safe to act on, and it must not be removed as redundant. A clean reload writes nothing on its own, so without `load` events a fixed error would sit in the file forever and an agent would "fix" a bug that no longer exists. A route's status is therefore whatever happened during its **most recent load**: one clean reload retires every earlier error for that route (reported as `N earlier error(s) resolved`). Errors are tied to their load by a client-generated `page` id, never by arrival order, because two `fetch` POSTs can land out of sequence.
@@ -156,8 +157,8 @@ If the task generates or edits route, layout, or component HTML templates, check
156
157
  - Server state: read `node_modules/caspian-utils/dist/docs/state.md`. Verify against `.venv/Lib/site-packages/casp/state_manager.py` and `main.py`.
157
158
  - Page caching: read `node_modules/caspian-utils/dist/docs/cache.md`. Verify against `.venv/Lib/site-packages/casp/cache_handler.py` and `main.py`.
158
159
  - Validation: read `node_modules/caspian-utils/dist/docs/validation.md`. Verify against `.venv/Lib/site-packages/casp/validate.py`.
159
- - Dates, times, "today", or date-range queries: read `node_modules/caspian-utils/dist/docs/core-runtime-map.md` "Application time (`casp.app_time`)". Verify against `.venv/Lib/site-packages/casp/app_time.py` and the `APP_TIMEZONE` resolution in `main.py`. **Never write a bare `datetime.now()` in `src/**` or `main.py`** — it returns the server's local wall clock, which silently disagrees with the UTC timestamps `src/lib/prisma/models.py` writes. Use `app_time.now()` / `today()` for the current moment, `to_app_time(...)` to display a stored value, and `day_bounds_utc(...)` with `gte`/`lt` to query a calendar day. Session expiry and cache TTLs stay on UTC and must not be routed through this module.
160
- - Database and seed flow: read `node_modules/caspian-utils/dist/docs/database.md`. Verify against `prisma/schema.prisma`, `prisma/seed.ts`, and `src/lib/prisma/**`.
160
+ - Dates, times, "today", or date-range queries: read `node_modules/caspian-utils/dist/docs/core-runtime-map.md` "Application time (`casp.app_time`)". Verify against `.venv/Lib/site-packages/casp/app_time.py` and the `APP_TIMEZONE` resolution in `main.py`. **Never write a bare `datetime.now()` in `src/**`or`main.py`** — it returns the server's local wall clock, which silently disagrees with the UTC timestamps `src/lib/prisma/models.py`writes. Use`app_time.now()`/`today()`for the current moment,`to_app_time(...)`to display a stored value, and`day_bounds_utc(...)`with`gte`/`lt` to query a calendar day. Session expiry and cache TTLs stay on UTC and must not be routed through this module.
161
+ - Database and seed flow: read `node_modules/caspian-utils/dist/docs/database.md` — start at "Two Generators, One Schema" for the required command order after schema changes (`npx prisma migrate dev` or `npx prisma db push`, then always `npx ppy generate`; `npx prisma generate` is Node-client-only and never a substitute). Verify against `prisma/schema.prisma`, `prisma/seed.ts`, and `src/lib/prisma/**`.
161
162
  - Static export (SSG) or previewing a static build: read `node_modules/caspian-utils/dist/docs/static-export.md`. Verify against `package.json` (`static`, `static:serve`), `settings/build-static.py`, `settings/serve-static.py`, and `settings/project-name.ts`. This is an app-owned convention, not a shipped Caspian feature and not gated by a `caspian.config.json` flag. `npm run static` = `npm run build && uv run python settings/build-static.py`, so it regenerates `settings/files-list.json` (via `projectName`) before the exporter walks that route index; do not reduce it back to `tailwind:build` only. `npm run static:serve` runs `settings/serve-static.py`, which auto-selects a free port from a preferred default (8000) and binds loopback `127.0.0.1` — read the port it prints, not `settings/bs-config.json` (that is the dev BrowserSync source of truth, not the static preview).
162
163
  - Testing, type checking, linting, or the quality gate: read `tests/README.md` and `settings/check.py`. This is a workspace-adopted convention, not a shipped Caspian feature, so it is documented in the workspace files (this section plus `.github/copilot-instructions.md`), not in the packaged docs. Verify against `pyproject.toml` (`[dependency-groups]`, `[tool.pyright]`, `[tool.ruff]`, `[tool.pytest.ini_options]`) and the `package.json` `check` script. The single command is `npm run check`.
163
164
  - Formatting code or markup: read `tests/README.md` "Formatting" and `settings/format.py`. App-owned tooling, not a shipped Caspian feature. The single command is `npm run format` (`npm run format:check` to report only); `npm run check:fix` runs it first, before the ruff fixes and the gate. It formats markup with **djLint** and Python with **`ruff format`**, in that order — reformatting a template changes how many lines its literal spans, which changes how ruff wraps the enclosing `html(...)` call, so ruff must run last for a single pass to converge. The house style is `html(r"""` on one line with the markup starting on the next; `ruff format` explodes that shape whenever the call has arguments besides the template, so `format.py` rejoins the opening afterwards and iterates the pair to a fixed point. A bare `ruff format` or an IDE format-on-save will re-split them — rerun `npm run format` rather than editing call sites by hand. Prettier is not usable on this markup: it has no Jinja awareness and de-indents `{% for %}` blocks to column 0. Verify against `settings/format.py`, `settings/_markup_equivalence.py`, and `tests/test_format.py`.
@@ -187,4 +188,4 @@ Before merging doc or runtime changes:
187
188
  2. Update the matching packaged doc in `node_modules/caspian-utils/dist/docs/` if the running behavior changed.
188
189
  3. Update `.github/copilot-instructions.md` if the repo-wide implementation rules changed.
189
190
  4. Update this file if the decision order, task routing, workspace clarifications, or packaged-doc maintenance rules changed.
190
- <!-- caspian:end -->
191
+ <!-- caspian:end -->