create-caspian-app 1.5.2 → 1.5.4

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.
@@ -24,6 +24,9 @@ This is the top architectural requirement for this workspace. Treat it as a hard
24
24
 
25
25
  ## Global Rules
26
26
 
27
+ - **Required frontend feedback loop:** for every UI change or browser-error fix, run `npm run logs` before editing and after reloading the affected route and exercising the changed interactions. It reads `.casp/browser-log.jsonl`; the developer's terminal is not your only source of frontend errors. Read both errors and warnings. A green Python/template/test gate or a visually correct first paint is not browser verification. If the browser or dev session is unavailable, report that verification gap explicitly.
28
+ - **Read `pp.props` in the owning script, never directly in authored markup.** Forward incoming props onto the native root, then expose top-level script bindings: `const dialogOpen = !!pp.props.open; const isRotating = !!pp.props.rotate; const projectId = pp.props.projectId || '';`. Use `open="{dialogOpen}"`, `hidden="{isRotating}"`, and `project-id="{projectId}"`. Nested boundary/slot evaluation does not guarantee the script-local prop bag; `open="{!!pp.props.open}"` can throw even when root forwarding is correct. Script-defined callbacks/effects/RPC handlers may use `pp.props`. Do not mask this with optional chaining, replace browser `{...}` with Jinja `{{...}}`, or freeze derived props in state. Read `pulsepoint.md` "Read props in the script, bind names in markup" and `components.md` before editing prop-driven components.
29
+
27
30
  - Use this decision order: `caspian.config.json` for optional feature enablement, app runtime and app-owned code for current project behavior, matching workspace instruction files under `.github/instructions/**/*.instructions.md` for task-specific implementation guidance, installed `casp` runtime for framework internals, and packaged markdown docs for Caspian feature discovery and task routing.
28
31
  - As the app grows, prefer `src/components/` for reusable application UI and reserve `src/lib/` for reusable non-UI code such as services, validators, adapters, and shared helpers.
29
32
  - Read `./caspian.config.json` almost immediately before making feature, tooling, scaffolding, or file-placement decisions. Treat it as the workspace feature gate for flags such as `backendOnly`, `tailwindcss`, `mcp`, `prisma`, `typescript`, `websocket`, and `componentScanDirs`.
@@ -45,7 +48,7 @@ This is the top architectural requirement for this workspace. Treat it as a hard
45
48
  - Use `npm run build` for deployment prep or an explicit build request, not as the default validation step for routine route, feature, or documentation edits.
46
49
  - **This workspace has an app-level quality gate, and running it is mandatory — not optional.** Any time you create, edit, or delete app-owned Python (`main.py`, `src/**`) — whether fixing a bug, adding a new file, refactoring, or implementing a feature — you must run `npm run check` (which calls `uv run python settings/check.py`) and get it fully green before treating the change as done. **A change is not complete while the gate reports anything.** It type checks with `pyright`, lints with `ruff`, and runs `pytest` in one pass, prints each problem as `path:line:col [tool:code] message`, and exits non-zero; fix every reported location and re-run until it passes clean. Do not report work as finished, hand it back, or move on to the next task on the assumption that it passes — actually run it and confirm green output first. Write app-owned Python to pass type checking (annotate parameters and returns, avoid untyped `Any` drift) and add or extend tests in `tests/` for the behavior you change. See `### tests/**/*.py and settings/check.py`.
47
50
  - **Know the gate's boundary: `npm run check` validates Python only** (`pyright` + `ruff` + `pytest` over `main.py`, `src/**`, and `settings/*.py`). It does **not** validate authored markup (the templates inside `index.py`, `layout.py`, and component `.py` files), `globals.css`/Tailwind, or `public/js/**` — a broken template, an invalid `x-*` tag, a single-root violation, or a PulsePoint error will pass the gate and only surface at render time. So a green gate means "the Python is sound," not "the page works." When you change templates, components, styles, or browser JS, verify them by actually loading the affected route in the browser (use the BrowserSync URL from `./settings/bs-config.json`) and checking it renders without console errors — do not treat a passing `npm run check` as proof that front-end work is correct.
48
- - **To see browser-side errors, run `npm run logs` — never start a second `npm run dev`.** The dev terminal usually belongs to the developer, so its stdout is invisible to you. `npm run dev` starts by deleting `.casp/` and `caches/`, so launching your own copy corrupts the running server's state, takes different ports, and rewrites `settings/bs-config.json`. Instead, PulsePoint's browser errors are appended to `.casp/browser-log.jsonl` and rendered by `npm run logs` (also printed at the end of every `npm run check`, where it never affects the exit code). Read its verdicts literally: `CLEAN` means that route was opened and rendered without error; a route **absent from the listing was never opened**, which is no signal rather than a pass; and `dev server is NOT running` means the entries are leftover history, not current state. After a fix, get the route exercised again and re-run `npm run logs`; the flip to `CLEAN` is your confirmation. **What counts as exercising it depends on the status:** a mount error is cleared by a reload, but `NEEDS RECHECK` means an error thrown from an event handler, which only re-running that interaction (click/submit) can clear a reload never will, and must not be reported as if it did. **Never diagnose from the raw `.casp/browser-log.jsonl`:** it is append-only history, so errors fixed minutes ago are still on disk and will send you hunting a bug that no longer exists any later `load` or `resolved` event for the same route makes them historical. `UNCONFIRMED` means an error with no matching load in this log (a tab left open across a dev restart); ask for a reload before treating it as live. Details in `AGENTS.md`.
51
+ - **To see browser-side errors, run `npm run logs` — never start a second `npm run dev`.** The dev terminal usually belongs to the developer, so its stdout is invisible to you. `npm run dev` starts by deleting `.casp/` and `caches/`, so launching your own copy corrupts the running server's state, takes different ports, and rewrites `settings/bs-config.json`. Instead, PulsePoint's browser errors are appended to `.casp/browser-log.jsonl` and rendered by `npm run logs` (also printed at the end of every `npm run check`, where it never affects the exit code). Read its verdicts literally: `CLEAN` means that route was opened and rendered without error; a route **absent from the listing was never opened**, which is no signal rather than a pass; and `dev server is NOT running` means the entries are leftover history, not current state. After a fix, reload and exercise the affected route, then re-run `npm run logs`; verify the behavior and absence of fresh errors or relevant warnings. **What counts as exercising it depends on the status:** a mount error is cleared by a reload, but `NEEDS RECHECK` can mean an interaction error or an error carried across a source change. Repeat the affected interaction (click/submit). The reporter does not observe successful clicks, so historical recheck entries can remain; report the retest evidence and remaining history separately instead of claiming the digest is `CLEAN`. **Prefer the digest over raw `.casp/browser-log.jsonl`:** the file includes history and is compacted on source changes. A later matching load can retire earlier mount errors, but interaction and carried entries can still need rechecking. If inspecting raw events to correlate a retest, use session, route, page id, and timestamp; a later event does not unconditionally clear every earlier error. Never delete logs or restart the server just to make the status look clean. `UNCONFIRMED` means an error with no matching load in this log (a tab left open across a dev restart); ask for a reload before treating it as live. Details in `AGENTS.md`.
49
52
  - Let the running dev stack own generated outputs such as `public/css/styles.css`, `settings/component-map.json`, `settings/files-list.json`, `__pycache__/`, and `.pyc` files. Treat those as generated artifacts rather than authored source.
50
53
  - Never treat `__pycache__/` directories or `.pyc` files as files to edit, regenerate on purpose, or keep in the final diff.
51
54
  - Treat `settings/component-map.json` and `settings/files-list.json` as generated outputs owned by `settings/component-map.ts` and `settings/files-list.ts`; inspect them when needed, but do not hand-edit them.
package/dist/AGENTS.md CHANGED
@@ -12,6 +12,8 @@ When you work here, use `caspian.config.json` and the code that actually runs as
12
12
 
13
13
  Do not treat the existence of a packaged doc as proof that the feature is enabled in this project.
14
14
 
15
+ **Frontend verification is required for every UI change or browser-error fix.** Run `npm run logs` before editing to read the frontend reports from `.casp/browser-log.jsonl`, then reload the affected route, exercise the changed interactions, and run it again before reporting completion. A passing `npm run check` does not prove browser health: its browser-log section is informational and does not affect the exit code. Never skip this because Python tests passed or the first paint looks correct. See the browser-log rules under Workspace Clarifications for historical entries and unavailable browser access.
16
+
15
17
  ## Caspian Core Contracts (Read Before Any Analysis)
16
18
 
17
19
  Every rule in this section describes shipped behavior of the Caspian runtime this app runs on. Implement against these contracts, not framework intuition. When any claim here disagrees with `caspian.config.json`, the app code, or the installed runtime, the code wins — and this section should then be fixed together with the matching packaged doc.
@@ -135,12 +137,33 @@ Value-type contract (forwarding fixes presence, not type):
135
137
  | `class`, `for` — JS reserved words | dropped; `pp.props.class` never exists |
136
138
  | value was `None`/`False`/`""`/empty collection | attribute omitted by `get_attributes` → `undefined` (never `false`) |
137
139
 
138
- Design rules: read booleans defensively (`!!pp.props.playing`); coerce server literals before strict comparison; avoid native-attribute collisions (`title` makes a tooltip — prefer `user-name`); camelCase round-trips through kebab-case (`isFullscreen` ↔ `is-fullscreen`). `get_attributes` aliases: `className`/`class_name` → `class`, `htmlFor`/`html_for` → `for`, `defaultValue` → `defaultvalue`, `defaultChecked` → `defaultchecked`. When Tailwind is enabled, `merge_classes(...)` emits a live `{twMerge(...)}` expression — pass it straight through, never wrap or re-merge it, and pop the incoming `class` from `props` first.
140
+ Design rules: read booleans defensively inside the owning script (`const isPlaying = !!pp.props.playing;`) and use `{isPlaying}` in markup; coerce server literals before strict comparison; avoid native-attribute collisions (`title` makes a tooltip — prefer `user-name`); camelCase round-trips through kebab-case (`isFullscreen` ↔ `is-fullscreen`). `get_attributes` aliases: `className`/`class_name` → `class`, `htmlFor`/`html_for` → `for`, `defaultValue` → `defaultvalue`, `defaultChecked` → `defaultchecked`. When Tailwind is enabled, `merge_classes(...)` emits a live `{twMerge(...)}` expression — pass it straight through, never wrap or re-merge it, and pop the incoming `class` from `props` first.
139
141
 
140
142
  `pp-ref` on an `x-*` tag is parent-owned and binds the component's concrete DOM root (forwarded through composition hosts). A component can opt out by declaring an explicit `ppRef` parameter. For a child-defined imperative API, pass a parent ref as an ordinary prop and publish with `pp.imperativeHandle(pp.props.controlRef, () => ({...}), [])` — never author `pp-ref-forward`.
141
143
 
142
144
  Deep dive: `node_modules/caspian-utils/dist/docs/components.md` "Receiving Props In A Python Component", "Every Prop A Template Reads Must Be Forwarded To The Root", and "HTML Attribute Helper Contract" (the full `get_attributes`/`merge_classes` behavior and end-to-end examples).
143
145
 
146
+ ### Read props in the script; expose bindings to the template
147
+
148
+ Forwarding props to the root and making them available to markup are separate requirements. Read `pp.props` inside the owning component's `<script>`, then expose top-level bindings for `{...}` expressions. Do not access `pp.props` directly in authored markup, including child `x-*` attributes, slot content, text, boolean attributes, or inline event expressions. Nested boundary and owned-slot evaluation cannot be assumed to receive the script-local `pp.props` bag.
149
+
150
+ ```html
151
+ <!-- Wrong: open="{!!pp.props.open}", hidden="{!!pp.props.rotate}",
152
+ project-id="{pp.props.projectId}" -->
153
+ <x-dialog open="{dialogOpen}">
154
+ <p hidden="{isRotating}">Enter a replacement value.</p>
155
+ </x-dialog>
156
+ <script>
157
+ const dialogOpen = !!pp.props.open;
158
+ const isRotating = !!pp.props.rotate;
159
+ const projectId = pp.props.projectId || "";
160
+ </script>
161
+ ```
162
+
163
+ This is a template excerpt: keep it inside the component's single root, forward its incoming props, and import the child component. Pass `project-id="{projectId}"` at the child call site. Script-defined callbacks, effects, and RPC handlers may still read `pp.props`. These are browser `{...}` bindings, not server Jinja `{{...}}` interpolation. Do not change brace dialects or use `pp.props?.open` to hide a scope error. Derive bindings each render; do not freeze incoming props with `pp.state(pp.props.open)` unless intentionally creating independent state.
164
+
165
+ Diagnostic signatures include `Failed to eval "!!pp.props.open"`, `Failed to evaluate prop expression`, and `Cannot read properties of undefined (reading 'rotate')` from owned content. A missing field in a valid prop bag is different from the bag itself being unavailable. Check root forwarding, then script-to-template scope, then repeat the interaction and inspect the frontend log. See [Read props in the script, bind names in markup](node_modules/caspian-utils/dist/docs/pulsepoint.md#read-props-in-the-script-bind-names-in-markup).
166
+
144
167
  ### PulsePoint templates — plain HTML, never JSX
145
168
 
146
169
  The React comparison covers exactly two things: the `pp.*` hook API inside `<script>` and how components are split by responsibility. **The markup is plain HTML parsed by an HTML parser.** The one-line test before finishing any template: _would it still be valid HTML with every `{}` deleted?_
@@ -174,7 +197,7 @@ Deep dive: `node_modules/caspian-utils/dist/docs/pulsepoint.md` "PulsePoint Is N
174
197
 
175
198
  ### Component scripts — hooks and runtime API
176
199
 
177
- The script is a plain, untyped `<script>` inside the root: captured by the runtime before materialization, evaluated in component scope via `new Function(...)`. No `import`/`export`/top-level `await`. Only **top-level** declarations reach the template (functions, `const`s, every destructuring shape). Props are read via `pp.props` — there is no injected `props` variable.
200
+ The script is a plain, untyped `<script>` inside the root: captured by the runtime before materialization, evaluated in component scope via `new Function(...)`. No `import`/`export`/top-level `await`. Only **top-level** declarations reach the template (functions, `const`s, every destructuring shape). Inside the script, props are read via `pp.props` — there is no injected `props` variable. Export top-level bindings for markup; do not read `pp.props` directly in template expressions.
178
201
 
179
202
  Hooks (closed list): `pp.state`, `pp.effect`, `pp.layoutEffect`, `pp.ref`, `pp.memo`, `pp.callback`, `pp.reducer`, `pp.context`, `pp.portal`, `pp.id`, `pp.errorBoundary`, `pp.syncExternalStore`, `pp.imperativeHandle`, `pp.transition`, `pp.deferredValue`, `pp.optimistic`, plus `pp.props`. Utilities: `pp.createContext`, `pp.mount`, `pp.redirect`, `pp.rpc`, `pp.socket`, `pp.enablePerf`/`disablePerf`/`getPerfStats`/`resetPerfStats`. No `forwardRef`, `Suspense`, `lazy`, `useActionState`, or `pp.provideContext` — do not invent hooks.
180
203
 
@@ -331,12 +354,12 @@ Use `.github/copilot-instructions.md` for the repo-wide implementation rules. Th
331
354
  - **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.
332
355
  - **`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.
333
356
  - **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.
334
- - **`NEEDS RECHECK` is the status that matters most — a reload does not re-test everything.** A reload re-runs mount, so it is real evidence against a mount-phase error. It never clicks a button, so it proves nothing about an error thrown from an event handler. Errors are classified by how long after their page load they arrived (`phase: "mount"` within 2s, `"interaction"` after), and an interaction error is **not** cleared by a later load — it is carried as `NEEDS RECHECK` with its timing shown. Treating that as `CLEAN` is exactly how a live bug gets signed off; this was a real defect in an earlier version of this tool, on a route whose `onclick` threw 17s after load. To clear one, repeat the interaction (click/submit) and re-run `npm run logs`.
357
+ - **`NEEDS RECHECK` can represent interaction errors or errors carried across source changes — a reload does not re-test everything.** A reload re-runs mount, so it is real evidence against a mount-phase error. It never clicks a button, so it proves nothing about an error thrown from an event handler. Errors are classified by how long after their page load they arrived (`phase: "mount"` within 2s, `"interaction"` after), and an interaction error is **not** cleared by a later load — it is carried as `NEEDS RECHECK` with its timing shown. Treating that as `CLEAN` is exactly how a live bug gets signed off; this was a real defect in an earlier version of this tool, on a route whose `onclick` threw 17s after load. Repeat the interaction (click/submit) and re-run `npm run logs`. The reporter does not record successful clicks, so an old recheck entry may remain even after a successful retest. Confirm there are no fresh errors or relevant warnings and report the interaction evidence separately; do not claim the status became `CLEAN` if it did not.
335
358
  - **The log is compacted on every source change, not appended forever.** A dev session left running for hours would otherwise grow an unbounded file (~331 KB / ~85k tokens in a measured 6-hour session), and `.casp/` is only wiped by a full `npm run dev`, which nobody does mid-session. So when `src/**` changes, `compactBrowserLog(...)` rewrites the file down to the session header, a `{"type":"restart"}` marker, and the errors still open — resolved history is dropped. Survivors are marked `"carried": true` and dropped at the _next_ compaction, so a stale interaction error cannot haunt the log forever. Reading the digest costs ~855 tokens regardless of session length, because it is bounded by route count, not time.
336
- - **Do not diagnose from the raw JSONL.** It is history, not state: it can hold errors resolved minutes ago, and errors carried across a source change. Always prefer `npm run logs`, which derives current status. If you do read the file, the `session` line's `readme` states the rule — **a later `load` or `resolved` supersedes earlier errors on that route, except `phase: "interaction"` errors, which a reload cannot re-test.**
359
+ - **Do not diagnose from raw JSONL alone.** It is history, not state: it can hold errors resolved minutes ago, and errors carried across a source change. Always prefer `npm run logs`, which derives current status. If you do read the file, the `session` line's `readme` states the rule — **a later matching load can retire earlier mount errors, but interaction errors and carried entries can remain pending recheck.** When the digest retains history after a retest, correlate raw details by session, route, page id, and timestamp; never treat every line as a new failure.
337
360
  - **`UNCONFIRMED` means an error with no matching `load` in this log** — almost always a browser tab left open across a dev restart, reporting against a page that was rendered under the previous session. The error was real when it fired but may already be fixed. Ask for a reload of that route and re-run `npm run logs` before treating it as a live bug; do not start editing code on an `UNCONFIRMED` line alone.
338
361
  - **Read the three not-an-error states literally.** `NONE` = no dev session ever wrote — nothing has been observed. `WARN … dev server is NOT running` = the log is left over from an exited session; treat every line as history. `CLEAN` on a route means _that route was opened and rendered without error_. A route absent from the listing was never opened, which is **no signal, not a pass** — say so rather than reporting the front end healthy.
339
- - **How to act on a failure.** The digest gives route, message, and the top stack frames. Fix the owning `src/app/<route>/index.py` or the component it names, then get the route exercised again and re-run `npm run logs`. What "exercised" means depends on the status: a mount error needs a **reload**; a `NEEDS RECHECK` interaction error needs the **same interaction repeated** — a reload will not clear it and should not be expected to. The flip to `CLEAN` is the confirmation. Do not treat a still-present error as unfixed until it has actually been re-exercised; the log cannot know about a fix nobody has run.
362
+ - **How to act on a failure.** The digest gives route, message, and the top stack frames. Fix the owning `src/app/<route>/index.py` or the component it names, then get the route exercised again and re-run `npm run logs`. What "exercised" means depends on the status: a mount error needs a **reload**; a `NEEDS RECHECK` interaction error needs the **same interaction repeated** — a reload will not clear it and should not be expected to. A `CLEAN` digest supports the retest, but historical `NEEDS RECHECK` entries may remain because the reporter cannot observe successful interactions. Record what was exercised and whether any fresh errors or warnings followed. Do not treat a retained historical entry as a fresh failure or claim browser verification when the route or interaction was never exercised. If browser access or the dev session is unavailable, state that gap explicitly.
340
363
  - Coverage is in `tests/test_browser_log.py`. The event shape is defined by `LogEvent` in `settings/dev-log-bridge.ts` and consumed by `build_report(...)` in `settings/browser_log.py`; change both together. Known limit: the client script runs per full document load, so **SPA navigations do not emit a `load` event** — the log tracks full page loads only.
341
364
 
342
365
  - Security posture is enforced in three places and must stay in sync: `casp.runtime_security` (environment resolution, security headers, safe public-file serving), `main.py` (middleware stack, MCP gate, cache eligibility, the named-socket endpoint), and the Caspian-owned files under `src/lib/**` — `src/lib/auth/auth_config.py` (route privacy and RBAC policy) and `src/lib/websocket/sockets.py` (socket origin check, connection ceiling, auth delegation, and per-connection limits). Key behaviors an agent must not silently undo:
@@ -396,7 +396,11 @@ async function shutdown(exitCode: number): Promise<void> {
396
396
  {
397
397
  proxy: {
398
398
  target: `http://localhost:${pythonPort}`,
399
- ws: true,
399
+ // The backend only registers the PulsePoint WebSocket endpoint when
400
+ // this feature is enabled. Do not proxy upgrade requests otherwise:
401
+ // Uvicorn has no WebSocket protocol dependency in a socket-free app,
402
+ // and the attempted upgrade tears down BrowserSync with ECONNRESET.
403
+ ws: caspianConfig.websocket,
400
404
  },
401
405
  port: bsPort,
402
406
  online: true,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-caspian-app",
3
- "version": "1.5.2",
3
+ "version": "1.5.4",
4
4
  "description": "Scaffold a new Caspian project (FastAPI-powered reactive Python framework).",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",