create-caspian-app 0.4.0-rc.8 → 0.5.0-rc.0
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.
|
@@ -95,6 +95,7 @@ This is the top architectural requirement for this workspace. Treat it as a hard
|
|
|
95
95
|
### `main.py`
|
|
96
96
|
|
|
97
97
|
- Treat `main.py` as the repo source of truth for FastAPI setup, auth bootstrap, middleware wiring, route registration, cache defaults, and error handlers.
|
|
98
|
+
- `main.py` finalizes every rendered page through `finalize_html(...)` = `transform_scripts(...)` then `defer_component_roots(...)`. `defer_component_roots(...)` wraps each outermost `pp-component` root in an inert `<template pp-component>` so the browser never parses raw `{...}` placeholders as live DOM; the PulsePoint `mount()` bootstrap materializes them back before scanning. Because of this deferral, `{...}` is safe in any attribute or position (SVG `d`/`viewBox`/`points`, `src`/`href`, form `value`/date/number/color, table/select text). Do not add per-tag workarounds to dodge browser first-paint validation (static-path `hidden` toggles, `data-*` URL holders, `hidden`-gated `<img src>`, or SSR-resolved initial values). Keep `pp-style` (source-file tooling) and the controlled form-field `value`/`checked`/`defaultvalue`/`<textarea>` rewrites (attribute-vs-property correctness); those exist for reasons deferral does not replace.
|
|
98
99
|
- Treat `main.py` as the source of truth for app-owned WebSocket endpoints, origin validation, idle timeouts, maximum socket message size, JSON message handling, close codes, and broadcast-channel wiring.
|
|
99
100
|
- When the app factors response-header hardening or safe static-file behavior into app-owned helpers, treat `main.py` plus those imported helpers as the runtime source of truth together.
|
|
100
101
|
- Preserve the effective middleware execution order unless the task explicitly changes request semantics: `SecurityHeadersMiddleware -> SessionMiddleware -> CSRFMiddleware -> AuthMiddleware -> RPCMiddleware`.
|
package/dist/AGENTS.md
CHANGED
|
@@ -82,6 +82,7 @@ Use `.github/copilot-instructions.md` for the repo-wide implementation rules. Th
|
|
|
82
82
|
- Before updating docs, verify runtime-specific claims such as middleware order, route param injection, `layout()` behavior, `StateManager` persistence, safe public-file serving, response header, or session-secret behavior against the current `main.py` and installed `casp` package, especially `.venv/Lib/site-packages/casp/runtime_security.py`, rather than copying older notes.
|
|
83
83
|
- When generating or reviewing `src/app/**/index.html`, `src/app/**/layout.html`, or component HTML templates, treat the single-root rule as a hard requirement: exactly one authored top-level parent element or one imported `x-*` root, with any owned `<script>` kept inside that same root. Do not allow sibling top-level tags, sibling scripts, or stray top-level text, because Caspian injects `pp-component` on that final root and errors if it cannot.
|
|
84
84
|
- When generating or reviewing sign-in flows, do not ask the sign-in page to decide redirect targets by re-implementing `next` support or post-login routing. In this stack, redirect behavior is already owned by the Caspian auth runtime plus `src/lib/auth/auth_config.py`; protected-route guest redirects, auth-route redirects, and the default destination are centralized there, with `default_signin_redirect` defaulting to `/dashboard`.
|
|
85
|
+
- Component markup is server-deferred in an inert `<template>`. `main.py` finalizes every page through `finalize_html(...)` = `transform_scripts(...)` then `defer_component_roots(...)`, which wraps each outermost `pp-component` root in `<template pp-component="…">`. The browser never parses/validates/fetches `<template>` contents, so raw `{...}` placeholders never reach live DOM at first paint; the PulsePoint `mount()` bootstrap materializes `template[pp-component]` back into live DOM (via `OwnedTemplateManager.materializeTemplateComponentBoundaries(document.body)`, which also runs on every SPA navigation) before it scans for roots. Because of this, `{...}` is safe in ANY attribute or position — SVG geometry (`d`, `viewBox`, `points`, `transform`), URL attributes (`src`, `srcset`, `href`, `poster`), form `value`/date/number/color, and text placed directly inside `<table>`/`<select>`. Do NOT add per-tag workarounds to dodge browser first-paint validation: no static-path `hidden` toggles just to avoid binding `d`, no `data-*` URL holders, no gating `<img src>` behind `hidden`, and no SSR-resolving an initial value only to prevent a validation flash. Two compiler transforms still apply for different reasons and stay: `pp-style` (so `.html` source-file HTML/CSS tooling does not choke on `style="{...}"`) and the `<input>`/`<select>`/`checked`/`defaultvalue`/`<textarea>` value rewrites (attribute-vs-property correctness for controlled form fields), not first-paint validation.
|
|
85
86
|
|
|
86
87
|
## Task Routing
|
|
87
88
|
|
|
@@ -132,4 +133,4 @@ Before merging doc or runtime changes:
|
|
|
132
133
|
1. Compare the claim or behavior against `main.py`, `src/lib/**`, and `.venv/Lib/site-packages/casp/**`.
|
|
133
134
|
2. Update the matching packaged doc in `node_modules/caspian-utils/dist/docs/` if the running behavior changed.
|
|
134
135
|
3. Update `.github/copilot-instructions.md` if the repo-wide implementation rules changed.
|
|
135
|
-
4. Update this file if the decision order, task routing, workspace clarifications, or packaged-doc maintenance rules changed.
|
|
136
|
+
4. Update this file if the decision order, task routing, workspace clarifications, or packaged-doc maintenance rules changed.
|
package/dist/main.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from casp.components_compiler import transform_components
|
|
2
2
|
from casp.scripts_type import transform_scripts
|
|
3
|
+
from casp.html_native import parse_fragment, serialize_fragment
|
|
3
4
|
import asyncio
|
|
4
5
|
import inspect
|
|
5
6
|
import os
|
|
@@ -214,6 +215,7 @@ async def combined_lifespan(app: FastAPI):
|
|
|
214
215
|
async with AsyncExitStack() as stack:
|
|
215
216
|
for lifespan in get_app_lifespans():
|
|
216
217
|
await stack.enter_async_context(lifespan(app))
|
|
218
|
+
|
|
217
219
|
yield
|
|
218
220
|
|
|
219
221
|
app = FastAPI(
|
|
@@ -950,7 +952,7 @@ def register_single_route(url_pattern: str, file_path: str):
|
|
|
950
952
|
component_compiler=transform_components
|
|
951
953
|
)
|
|
952
954
|
|
|
953
|
-
html_output =
|
|
955
|
+
html_output = finalize_html(html_output)
|
|
954
956
|
response = HTMLResponse(content=html_output)
|
|
955
957
|
response.headers['X-PP-Root-Layout'] = root_layout_id
|
|
956
958
|
|
|
@@ -989,6 +991,63 @@ def register_single_route(url_pattern: str, file_path: str):
|
|
|
989
991
|
methods=route_methods, name=endpoint)
|
|
990
992
|
|
|
991
993
|
|
|
994
|
+
def defer_component_roots(html_output: str) -> str:
|
|
995
|
+
"""Wrap top-level ``[pp-component]`` roots in an inert ``<template>``.
|
|
996
|
+
|
|
997
|
+
The browser never parses/validates/fetches the contents of a ``<template>``
|
|
998
|
+
element, so raw ``{...}`` placeholders inside SVG geometry attributes, form
|
|
999
|
+
``value``/date inputs, ``src``/``href`` URLs, or table/select structure no
|
|
1000
|
+
longer trigger console errors, bogus ``404`` requests, value coercion, or
|
|
1001
|
+
HTML foster-parenting before hydration. PulsePoint's ``mount()`` bootstrap
|
|
1002
|
+
materializes ``template[pp-component]`` back into live DOM (reusing the
|
|
1003
|
+
existing ``materializeTemplateComponentBoundaries`` path) before it scans
|
|
1004
|
+
for component roots, so post-hydration behavior is identical to today.
|
|
1005
|
+
|
|
1006
|
+
Only the outermost (non-nested) roots are wrapped; nested component
|
|
1007
|
+
boundaries ride along inside the inert content and become live when the
|
|
1008
|
+
outer template is materialized, so morphing and RPC re-render still operate
|
|
1009
|
+
on live ``[pp-component]`` DOM.
|
|
1010
|
+
"""
|
|
1011
|
+
if 'pp-component' not in html_output:
|
|
1012
|
+
return html_output
|
|
1013
|
+
|
|
1014
|
+
soup = parse_fragment(html_output)
|
|
1015
|
+
body = soup.body
|
|
1016
|
+
if body is None:
|
|
1017
|
+
return html_output
|
|
1018
|
+
|
|
1019
|
+
roots = [
|
|
1020
|
+
el for el in body.select('[pp-component]')
|
|
1021
|
+
if el.name != 'template'
|
|
1022
|
+
and not any(
|
|
1023
|
+
parent.has_attr('pp-component') for parent in el.parents
|
|
1024
|
+
)
|
|
1025
|
+
]
|
|
1026
|
+
if not roots:
|
|
1027
|
+
return html_output
|
|
1028
|
+
|
|
1029
|
+
for root in roots:
|
|
1030
|
+
key = root.get('pp-component')
|
|
1031
|
+
if key is None:
|
|
1032
|
+
continue
|
|
1033
|
+
template = soup.new_tag('template')
|
|
1034
|
+
template['pp-component'] = key
|
|
1035
|
+
root.insert_before(template)
|
|
1036
|
+
template.append(root.extract())
|
|
1037
|
+
|
|
1038
|
+
return serialize_fragment(soup)
|
|
1039
|
+
|
|
1040
|
+
|
|
1041
|
+
def finalize_html(html_output: str) -> str:
|
|
1042
|
+
"""Final full-document transforms applied just before the response.
|
|
1043
|
+
|
|
1044
|
+
Runs ``transform_scripts`` (author ``<script>`` -> ``type="text/pp"``) then
|
|
1045
|
+
``defer_component_roots`` so scripts are tagged before they are moved into
|
|
1046
|
+
the inert component ``<template>``.
|
|
1047
|
+
"""
|
|
1048
|
+
return defer_component_roots(transform_scripts(html_output))
|
|
1049
|
+
|
|
1050
|
+
|
|
992
1051
|
register_routes()
|
|
993
1052
|
register_rpc_routes(app)
|
|
994
1053
|
|
|
@@ -1019,7 +1078,7 @@ async def custom_404_handler(request: Request, exc: StarletteHTTPException):
|
|
|
1019
1078
|
context_data={'request': request},
|
|
1020
1079
|
page_component_source=not_found_path,
|
|
1021
1080
|
control_mode=True,
|
|
1022
|
-
transform_fn=
|
|
1081
|
+
transform_fn=finalize_html
|
|
1023
1082
|
)
|
|
1024
1083
|
resp = HTMLResponse(content=html_output, status_code=404)
|
|
1025
1084
|
resp.headers['X-PP-Root-Layout'] = root_layout_id
|
|
@@ -1054,7 +1113,7 @@ async def custom_general_exception_handler(request: Request, exc: Exception):
|
|
|
1054
1113
|
context_data=context_data,
|
|
1055
1114
|
page_component_source=error_page_path,
|
|
1056
1115
|
control_mode=True,
|
|
1057
|
-
transform_fn=
|
|
1116
|
+
transform_fn=finalize_html
|
|
1058
1117
|
)
|
|
1059
1118
|
resp = HTMLResponse(content=html_output, status_code=500)
|
|
1060
1119
|
resp.headers['X-PP-Root-Layout'] = root_layout_id
|