create-caspian-app 0.2.0-beta.97 → 0.2.0-beta.99

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.
@@ -37,6 +37,7 @@
37
37
  - In app-owned starter config like this workspace, routes start public because `src/lib/auth/auth_config.py` sets `is_all_routes_private=False` by default.
38
38
  - Decide route privacy in `src/lib/auth/auth_config.py` at app setup time: use `is_all_routes_private=True` when only a few routes should stay public, otherwise keep `is_all_routes_private=False` and list the protected routes in `private_routes`.
39
39
  - In all-private mode, keep public exceptions in `public_routes`; the runtime defaults keep `/` public and keep `auth_routes=["/signin", "/signup"]` public.
40
+ - When building or editing sign-in flows, do not implement app-owned `next` parsing or redirect selection inside the sign-in page or sign-in action unless the user explicitly asks to replace Caspian auth behavior. Guest redirects to `/signin?next=...`, authenticated auth-route redirects, and the default post-login destination are already owned by the Caspian runtime plus `src/lib/auth/auth_config.py`, which defaults `default_signin_redirect` to `/dashboard`.
40
41
  - Do not treat `token_auto_refresh` as the switch that makes routes private. In the current app it only affects sliding-session refresh if `auth.refresh_session()` is called.
41
42
  - Use PulsePoint as the default reactive frontend layer unless the user requests another stack.
42
43
  - When `caspian.config.json` has `tailwindcss: true`, treat Python `merge_classes(...)` plus browser `twMerge(...)` as the only Tailwind class-merging contract: `merge_classes(...)` emits frontend-ready `{twMerge(...)}` expressions, and authored PulsePoint attribute expressions or scripts may call global `twMerge(...)` directly.
package/dist/AGENTS.md CHANGED
@@ -68,6 +68,7 @@ Use `.github/copilot-instructions.md` for the repo-wide implementation rules. Th
68
68
  - For grouped-subtree SPA navigation UX, the current browser runtime keeps unmarked shell scrollers stable and uses `pp-reset-scroll="true"` on the content pane that should reset. Check `pulsepoint.md`, `routing.md`, and `public/js/pp-reactive-v2.js` before changing that behavior.
69
69
  - 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.
70
70
  - 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.
71
+ - 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`.
71
72
 
72
73
  ## Task Routing
73
74
 
@@ -28,5 +28,4 @@ Thumbs.db # Windows
28
28
  .casp/
29
29
  caches/
30
30
  settings/bs-config.json
31
- settings/prisma-schema.json
32
- /src/generated/prisma
31
+ settings/prisma-schema.json
package/dist/main.py CHANGED
@@ -1,11 +1,13 @@
1
1
  from casp.components_compiler import transform_components
2
2
  from casp.scripts_type import transform_scripts
3
+ import asyncio
3
4
  import inspect
4
5
  import os
5
6
  import importlib.util
6
7
  import secrets
7
8
  import traceback
8
9
  import json
10
+ import time
9
11
  from pathlib import Path
10
12
  from fastapi import FastAPI, Request, Response
11
13
  from fastapi.responses import RedirectResponse, FileResponse, HTMLResponse
@@ -81,6 +83,10 @@ MAX_CONTENT_LENGTH_MB = int(os.getenv('MAX_CONTENT_LENGTH_MB', 16))
81
83
  IS_PRODUCTION = os.getenv('APP_ENV') == 'production'
82
84
  CACHE_ENABLED = os.getenv('CACHE_ENABLED', 'false').lower() == 'true'
83
85
  DEFAULT_TTL = int(os.getenv('CACHE_TTL', 600))
86
+ REQUEST_TIMEOUT_SECONDS = max(
87
+ 1.0,
88
+ float(os.getenv('CASPIAN_REQUEST_TIMEOUT_SECONDS', 20)),
89
+ )
84
90
 
85
91
 
86
92
  def _client_error_message(exc: Exception) -> str:
@@ -299,6 +305,57 @@ class RPCMiddleware:
299
305
  return
300
306
  await self.app(scope, receive, send)
301
307
 
308
+
309
+ class RequestDiagnosticsMiddleware:
310
+ """Log request start/end in dev and fail visibly when a route stalls."""
311
+
312
+ def __init__(self, app: ASGIApp): self.app = app
313
+
314
+ async def __call__(self, scope: Scope, receive: Receive, send: Send):
315
+ if scope["type"] != "http":
316
+ await self.app(scope, receive, send)
317
+ return
318
+
319
+ method = scope.get("method", "GET")
320
+ path = scope.get("path", "")
321
+ should_log = not path.startswith(('/css/', '/js/', '/assets/', '/favicon.ico'))
322
+ started = time.perf_counter()
323
+
324
+ if should_log and not IS_PRODUCTION:
325
+ print(f"[request:start] {method} {path}", flush=True)
326
+
327
+ try:
328
+ await asyncio.wait_for(
329
+ self.app(scope, receive, send),
330
+ timeout=REQUEST_TIMEOUT_SECONDS,
331
+ )
332
+ except asyncio.TimeoutError:
333
+ elapsed_ms = int((time.perf_counter() - started) * 1000)
334
+ print(
335
+ f"[request:timeout] {method} {path} exceeded "
336
+ f"{REQUEST_TIMEOUT_SECONDS:g}s after {elapsed_ms}ms",
337
+ flush=True,
338
+ )
339
+ response = HTMLResponse(
340
+ content=(
341
+ "<h1>504 - Request Timeout</h1>"
342
+ "<p>The route took too long to respond. "
343
+ "Check the development terminal for the stalled path.</p>"
344
+ ),
345
+ status_code=504,
346
+ )
347
+ await response(scope, receive, send)
348
+ return
349
+ except Exception:
350
+ if should_log and not IS_PRODUCTION:
351
+ elapsed_ms = int((time.perf_counter() - started) * 1000)
352
+ print(f"[request:error] {method} {path} after {elapsed_ms}ms", flush=True)
353
+ raise
354
+ finally:
355
+ if should_log and not IS_PRODUCTION:
356
+ elapsed_ms = int((time.perf_counter() - started) * 1000)
357
+ print(f"[request:end] {method} {path} {elapsed_ms}ms", flush=True)
358
+
302
359
  # ====
303
360
  # Route Registration
304
361
  # ====
@@ -659,6 +716,7 @@ app.add_middleware(
659
716
  path='/',
660
717
  )
661
718
  app.add_middleware(SecurityHeadersMiddleware)
719
+ app.add_middleware(RequestDiagnosticsMiddleware)
662
720
 
663
721
  if __name__ == '__main__':
664
722
  port = int(os.getenv('PORT', 5091))