create-caspian-app 1.3.18 → 1.3.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.
|
@@ -70,6 +70,7 @@ This is the top architectural requirement for this workspace. Treat it as a hard
|
|
|
70
70
|
- Components are authored single-file. Return `html(r"""...""", **context)` (import `html` from `casp.component_decorator`) to keep markup, server interpolation, and a PulsePoint `<script>` inline. Inside `html(...)`, `{{ ... }}` is server-side Jinja and `{ ... }` is left for PulsePoint; never use a Python f-string for the markup. Autoescaping is on, so `{{ value }}` is safe for user text and trusted HTML needs `Markup(...)` or `| safe`; a `children` value is auto-safe. Keep each file focused on one responsibility. Split multiple panels, tabs, forms, tables, cards, and toolbars into separate components instead of making one giant single-file component.
|
|
71
71
|
- For single-file Python components that receive browser props, treat the Python render as an explicit bridge. Attributes on the parent `x-*` tag arrive as raw string kwargs, including unevaluated PulsePoint expressions such as `"{permOpen}"`; they do not reach `pp.props` merely because the Python signature accepts them. Re-emit browser-facing values on the component's single native root with `attributes = get_attributes({...}, props)`, `<root {{ attributes }}>`, and `html(..., attributes=attributes)`. Otherwise `pp.props` is silently empty or missing those keys with no error or warning. Forwarded props are real DOM attributes, so avoid accidental native behavior such as a `title` tooltip by choosing a non-native API name such as `user-name` when appropriate. Follow the full helper and prop-passing contract in `node_modules/caspian-utils/dist/docs/components.md`.
|
|
72
72
|
- Use real Python imports for child components everywhere: a module's `x-*` tags resolve from the Components imported into that module (pages and layouts included), which disambiguates same-name components across directories. Runtime resolution precedence is inherited ancestor components, then the module's own Python imports. Slot content resolves in the scope where it was authored, so the module that writes an `x-*` tag must import that component. For directories whose names are not valid identifiers (hyphens, `(group)`), bind via `importlib.import_module(...)` assignment.
|
|
73
|
+
- All three import forms resolve, so pick the one that reads best rather than working around a directory layout. Import a name from its own file (`from src.lib.maddex.Button import Button`), import several names from a file that exports several (`from src.lib.maddex.Breadcrumb import Breadcrumb, BreadcrumbItem`), or import straight from a one-component-per-file **directory** without naming each file (`from src.lib.ppicons import Search, ArrowLeft` -> `<x-search />`, `<x-arrow-left />`). That last form is what the generated component directories are built for and is the preferred way to pull in icons. Python binds the _submodules_ there, since a component directory has no `__init__.py` re-exports, so Caspian unwraps a module binding that defines a component under its own file name (`Search.py` -> `Search`). The tag follows the _binding_ name, so `from src.lib.ppicons import Search as MagnifyIcon` renders `<x-magnify-icon />`. Nothing else is unwrapped: a helper module (`utils.py`), an ordinary `import os`, a bare package, or a file whose function is missing the `@component` decorator all still raise `UnknownComponentError` — so that error on a correct-looking import usually means the missing decorator, not the import form. The directory form also reaches only the component named after its file, so a multi-export file's other exports keep the exact-file form: `from src.lib.maddex import BreadcrumbItem` is a plain `ImportError` because there is no `BreadcrumbItem.py`. Ruff sees all three forms as unused imports, but `settings/check.py` suppresses `F401` for any name used as an `x-*` tag, so the gate stays clean.
|
|
73
74
|
- For CRUD operations and any browser-initiated reads from the backend, use route or backend `@rpc()` actions on the server and `pp.rpc(...)` from PulsePoint code on the client unless the user explicitly asks for another integration pattern.
|
|
74
75
|
- Google and GitHub OAuth ship pre-registered in this starter: `main.py` already calls `Auth.set_providers(GithubProvider(), GoogleProvider())`, and `AuthMiddleware` already handles the `signin/{google,github}` and `callback/{google,github}` paths under `api_auth_prefix` (default `/api/auth`). To add social sign-in, point a link or button at `/api/auth/signin/google` or `/api/auth/signin/github` and set the provider credentials in `.env` (`GOOGLE_CLIENT_ID`, `GOOGLE_CLIENT_SECRET`, `GOOGLE_REDIRECT_URI`, `GITHUB_CLIENT_ID`, `GITHUB_CLIENT_SECRET`). Do not hand-roll an OAuth flow, manual `httpx2`/`authlib` token exchange, or custom callback routes; reuse the shipped providers and let `auth.auth_providers(...)` own redirect, callback, and sign-in.
|
|
75
76
|
- For one-way streaming output, including AI/LLM/chat token streams, use Caspian's shipped RPC streaming: write a generator `@rpc()` action that `yield`s chunks (the runtime wraps generators as SSE via `casp.streaming.SSE`) and consume it with `pp.rpc(name, args, { onStream, onStreamComplete, onStreamError })`. When bridging a Python LLM/SDK stream, `async for` over the provider's stream inside the `@rpc()` action and `yield` each token. Do not reinvent one-way streaming with raw `fetch`/`ReadableStream`, `EventSource`, or a WebSocket; reserve WebSockets for genuinely bidirectional channels per the WebSocket rules above.
|
package/dist/AGENTS.md
CHANGED
|
@@ -67,7 +67,7 @@ Authoring is **Python-only and single-file**. There are no `.html` sidecars in t
|
|
|
67
67
|
**Components (`src/components/**/\*.py`):\*\*
|
|
68
68
|
|
|
69
69
|
- One `@component` function per responsibility, markup inline via `html(r"""...""")`, PulsePoint `<script>` inside the root. Split by responsibility exactly as you would split React components — **that analogy covers decomposition and hook API only, never markup syntax** (see the PulsePoint contract below).
|
|
70
|
-
- **Composition is Python-import-driven.** An `<x-*>` tag resolves from the `Component` objects imported into the module that authors the tag: `Container` → `<x-container>`, `CommandDialog` → `<x-command-dialog>`. Import every tag you write, including in pages and layouts. Same-file multi-exports are imported from that exact file. Directories that are not valid identifiers (hyphens, `(group)`) bind via `Name = importlib.import_module("src.app.some-dir.Name").Name`.
|
|
70
|
+
- **Composition is Python-import-driven.** An `<x-*>` tag resolves from the `Component` objects imported into the module that authors the tag: `Container` → `<x-container>`, `CommandDialog` → `<x-command-dialog>`. Import every tag you write, including in pages and layouts. Same-file multi-exports are imported from that exact file. Directories that are not valid identifiers (hyphens, `(group)`) bind via `Name = importlib.import_module("src.app.some-dir.Name").Name`. **A package import of a one-component-per-file directory also works** — `from src.lib.ppicons import Search, ArrowLeft` → `<x-search />`, `<x-arrow-left />` — even though Python binds the _submodules_ there rather than the Components (a component directory has no `__init__.py` re-exports). A module binding is unwrapped when the module defines a component under its own file name (`Search.py` → `Search`), and the tag alias stays the _binding_ name, so `import Search as MagnifyIcon` gives `<x-magnify-icon />`. Nothing else is unwrapped: `utils.py`, `import os`, a bare package, or a file whose function is missing `@component` still raise `UnknownComponentError`.
|
|
71
71
|
- Resolution precedence inside a component's output: inherited ancestor components, then the module's own imports (imports win). Slot content resolves in the scope where it was **authored**, so the module writing the tag must import it.
|
|
72
72
|
- A component may also be called directly as a function and interpolated with `{{ }}`; its nested tags still resolve from its own module's imports.
|
|
73
73
|
- **Root shape:** default to one authored top-level element with the `<script>` inside it.
|