caspian-utils 0.1.15 → 0.1.16

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.
@@ -264,6 +264,7 @@ Rules for inline `html(...)`:
264
264
  - The single-root rule still applies: exactly one top-level element with any `<script>` nested inside it.
265
265
  - Keep the component focused on one UI responsibility. Prefer composing several single-file components over one long Python file that contains multiple sections, tab panels, or workflows.
266
266
  - Pass data, labels, variants, current selection, counts, permissions, or callbacks as props instead of making child components reach back into route markup or duplicate parent state.
267
+ - Component attributes are props regardless of the value type. Kebab-case names become camel-cased in `pp.props`, so callbacks may be authored as `on-open-change="{setOpen}"`, `open-change="{setOpen}"`, or `select-first="{selectFirst}"` according to the component API. The `on-` prefix is conventional, not required. Lowercase native DOM event attributes such as `onclick` and `oninput` remain events on the boundary element; use `on-click` only when the component API intentionally exposes an `onClick` prop.
267
268
  - Autoescaping is ON (Jinja default), so `{{ value }}` escapes HTML automatically and user text is safe without `| e`. The flip side: trusted HTML you want rendered as-is must be `Markup(...)` or piped through `| safe`.
268
269
  - A `children` value is auto-marked safe (parity with `render_html(...)`), so `{{ children }}` renders nested component markup correctly without `| safe`.
269
270
  - Do not use a Python f-string for the markup. PulsePoint single braces `{ ... }` would collide with f-string interpolation. Use a plain triple-quoted string passed to `html(...)`.
@@ -412,8 +413,27 @@ Do not create a single `AccountPage.py` or `DashboardTabs.py` that contains ever
412
413
  Related subcomponents may live in one Python file only when they are tiny and tightly coupled, such as `Tabs`, `TabsList`, `TabsTrigger`, and `TabsContent` primitives, or a component plus two very small private helpers. For app-specific page chunks, prefer one exported component per file with a name that explains its role.
413
414
 
414
415
  Props are the boundary between components. Pass parent-owned data and configuration down through attributes or direct Python calls, keep local interactive state inside the component that owns the behavior, and use slot content when the parent needs to provide authored child markup. If two sibling chunks need the same server data, load it in the route's `page()` or a shared helper and pass the relevant pieces into each component.
415
-
416
- ## Auto-Injected `pp-component` And PulsePoint Script Type
416
+
417
+ ## Component Root Refs
418
+
419
+ `pp-ref` on an `x-*` tag is a parent-owned component ref. It resolves in the scope that authored the component tag and binds to the component's rendered root DOM element. Generated component kits that accept `**props` and spread unknown attributes need no `forwardRef` wrapper and should not special-case `pp-ref`.
420
+
421
+ ```html
422
+ <div>
423
+ <x-input pp-ref="{codeInput}" />
424
+ <button onclick="codeInput.current?.focus()">Focus code</button>
425
+
426
+ <script>
427
+ const codeInput = pp.ref(null);
428
+ </script>
429
+ </div>
430
+ ```
431
+
432
+ For ref purposes, a component's root is the single native element that receives its injected `pp-component` boundary. If a composition component renders another `x-*` component as its root, Caspian forwards the ref through its layout-neutral boundary host to the eventual concrete DOM root.
433
+
434
+ The default takes precedence over catch-all `**props`: `pp-ref` is reserved for root ref forwarding and is not delivered in that dictionary. A component that intentionally wants `pp-ref` as ordinary data can opt out by declaring an explicit camel-case `ppRef` parameter, for example `def Inspector(ppRef="", **props)`. That explicit declaration consumes the prop, disables automatic root forwarding for that component invocation, and makes the component responsible for how the value is used. Use this escape hatch sparingly; ordinary UI primitives should keep the standard root-ref contract.
435
+
436
+ ## Auto-Injected `pp-component` And PulsePoint Script Type
417
437
 
418
438
  Treat `pp-component="componentName"` and `type="text/pp"` as framework output, not authored source. The canonical authored-vs-runtime explanation lives in [pulsepoint.md](./pulsepoint.md).
419
439
 
@@ -36,7 +36,7 @@ If an inspected browser DOM disagrees with authored template source, remember th
36
36
  | Template expressions | text and attributes with `{...}` | `pp-reactive-v2.js` | top-level script bindings are exported, nested bindings are not assumed |
37
37
  | State | `pp.state(initial)` | `pp-reactive-v2.js` | setters accept values or updater functions, state belongs to the component instance |
38
38
  | Effects | `pp.effect(...)`, `pp.layoutEffect(...)` | `pp-reactive-v2.js` | callbacks may return cleanup functions, promises are not awaited |
39
- | Refs | `pp.ref(...)`, `pp-ref` | `pp-reactive-v2.js` | generated ref internals are runtime-managed; do not author `data-pp-ref` |
39
+ | Refs | `pp.ref(...)`, `pp-ref` on native or `x-*` tags | `components_compiler.py`, `TemplateCompiler.ts`, `Component.ts`, `RefBindingManager.ts`, shipped in `pp-reactive-v2.js` | component-tag refs are reserved by the Python compiler, stamped with runtime-owned `pp-ref-owner`, captured in the parent's scope, and bound to the child's concrete root; do not author `data-pp-ref`, `pp-ref-owner`, or `pp-ref-forward`; callback refs may return synchronous cleanup functions that run on replacement or detach |
40
40
  | Context | `pp.createContext(...)`, lowercase `<themecontext.provider>`, `pp.context(token)` | `TemplateCompiler.ts`, `NestedBoundaryManager.ts`, `pp-reactive-v2.js` | authored provider tags are HTML-first and lowercase; `TemplateCompiler.transformContextProviderTags(...)` rewrites `*.provider` to runtime-owned `pp-context-provider`; ancestry is logical component ancestry; do not invent `pp-context` or `pp.provideContext` |
41
41
  | Portals | `pp.portal(ref, target?)` | `pp-reactive-v2.js` | context should preserve logical ancestry through the registry |
42
42
  | Lists | `<template pp-for="item in items">` | `pp-reactive-v2.js` | `pp-for` belongs on `<template>`, accepts arrays and synchronous iterables, captures rendered row values for events/props, and uses plain `key`, not `pp-key` |
@@ -418,9 +418,10 @@ Global helpers exposed through the `pp` singleton and also merged into the compo
418
418
  Notes:
419
419
 
420
420
  - The global `pp` singleton auto-mounts once the runtime is loaded and the DOM is ready. Manual `pp.mount()` is still safe because it short-circuits after the first mount.
421
- - `pp.state` setters accept either a value or an updater function.
421
+ - `pp.state` setters accept either a value or an updater function.
422
422
  - `pp.effect` and `pp.layoutEffect` are cleanup-style hooks. They may return only a synchronous cleanup function; returning a promise warns and is ignored, so start async work inside the effect instead.
423
- - `pp.portal(ref)` defaults to `document.body` when no target is provided.
423
+ - A function used through `pp-ref` may return a synchronous cleanup function. PulsePoint runs it when that callback ref is replaced or detached; callbacks that do not return cleanup retain the legacy `callback(null)` detach behavior.
424
+ - `pp.portal(ref)` defaults to `document.body` when no target is provided.
424
425
  - Older docs may call the RPC helper `pp.fetchFunction()`. In the current bundled runtime the implemented global API is `pp.rpc()`.
425
426
  - Keep template-facing bindings at the top level so the AST-based exporter can see them.
426
427
  - For predictable code generation, prefer passing an explicit dependency array to `pp.effect`, `pp.layoutEffect`, `pp.memo`, and `pp.callback`.
@@ -585,10 +586,11 @@ Nested components:
585
586
  - Nested roots that contain their own `script` block are masked during parent template compilation.
586
587
  - Scriptless nested component roots are not fully masked during parent template compilation in the current source. Avoid generating child-local interpolations inside a nested root unless that child has its own `script` block.
587
588
 
588
- ## Template expressions and attributes
589
-
590
- - Use `{expression}` in text nodes and attribute values.
591
- - Pure bindings like `value="{count}"` are evaluated as expressions.
589
+ ## Template expressions and attributes
590
+
591
+ - Use `{expression}` in text nodes and attribute values.
592
+ - Follow HTML-first attribute naming. Native event attributes are lowercase DOM event attributes (`onclick`, `oninput`, `onsubmit`). Every other attribute on a component boundary is a prop: kebab-case names become camel-cased only inside `pp.props` (`selected-value` becomes `selectedValue`, `open-change` becomes `openChange`, and `on-open-change` becomes `onOpenChange`). A function prop does not need an `on-` prefix; that prefix is only an API naming convention. Use `on-click` when a component intentionally exposes an `onClick` prop, because lowercase `onclick` is reserved for the native DOM event on the boundary element.
593
+ - Pure bindings like `value="{count}"` are evaluated as expressions.
592
594
  - For dynamic inline styles in authored templates, prefer `pp-style="{styleText}"` over `style="{styleText}"` so HTML/CSS tooling does not parse the brace expression as raw CSS.
593
595
  - `pp-style` is an authoring alias. The compiler rewrites it to a native `style` attribute in rendered output.
594
596
  - If the same element already has a static `style` attribute, `pp-style` is merged into that `style` value during compilation.
@@ -630,20 +632,23 @@ The render pipeline wraps each top-level `pp-component` root in an inert `<templ
630
632
  - Do not add per-tag workarounds to dodge first-paint validation (static-path `hidden` toggles instead of binding `d`, `data-*` URL holders, `hidden`-gated `<img src>`, or an SSR-resolved initial value). The deferral removes the whole class at once.
631
633
  - `pp-style` and the `<input>`/`<select>`/`checked`/`defaultvalue`/`<textarea>` value rewrites still apply — but for different reasons that deferral does not replace: authoring-source tooling (`style="{...}"` breaks HTML/CSS linters) and attribute-vs-property correctness for controlled form fields.
632
634
 
633
- ## Refs
634
-
635
- - Refs are for imperative element access. Do not use `pp-ref` as the default way to read normal form input values on submit; prefer the form's `onsubmit` event plus `Object.fromEntries(new FormData(event.currentTarget).entries())`.
636
- - Use `pp-ref="nameInput"` when the ref object or callback is already available in scope.
637
- - Use `pp-ref="{registerRef(id)}"` when you want the compiler to capture a dynamic ref expression.
638
- - `pp.ref(null)` is the normal way to create a ref object.
639
- - Callback refs and `{ current }` refs are both supported.
640
- - Captured brace-form refs are compiled into an internal `data-pp-ref` token and rebound after render.
641
- - Plain `pp-ref` bindings are preserved across rerenders, including no-op rerenders that skip DOM diffing.
635
+ ## Refs
636
+
637
+ - Refs are for imperative element access. Do not use `pp-ref` as the default way to read normal form input values on submit; prefer the form's `onsubmit` event plus `Object.fromEntries(new FormData(event.currentTarget).entries())`.
638
+ - `pp-ref` works on native elements and on `x-*` component tags. On a component tag it resolves in the parent's scope and binds to the component's concrete root DOM element, so `<x-input pp-ref="{nameInput}" />` exposes the rendered `<input>` without a wrapper or `querySelector(...)`.
639
+ - Use the bare-name form, such as `pp-ref="nameInput"`, when the ref object or callback is already available under that name in scope. The runtime resolves this form by scope lookup.
640
+ - Use the brace-expression form, such as `pp-ref="{nameInput}"`, `pp-ref="{registerRef(id)}"`, or `pp-ref="{el => setNode(el)}"`, when the compiler should capture an expression. This is the preferred general form for component tags and dynamic or callback expressions.
641
+ - `pp.ref(null)` is the normal way to create a ref object.
642
+ - Callback refs and `{ current }` refs are both supported.
643
+ - Captured brace-form refs are compiled into an internal `data-pp-ref` token and rebound after render.
644
+ - Component refs are attached before layout effects and passive effects run, and detach with `null` (or a callback's returned synchronous cleanup) when the owning element unmounts.
645
+ - A composition component whose root is another `x-*` component forwards the ref through Caspian's layout-neutral component hosts to the eventual concrete DOM root.
646
+ - Plain `pp-ref` bindings are preserved across rerenders, including no-op rerenders that skip DOM diffing.
642
647
  - Ref callbacks may be called with `null` during cleanup.
643
648
  - The runtime generates `data-pp-ref` internally. Do not author it.
644
649
  - Do not author `pp-event-owner`, `pp-owner`, or `pp-dynamic-*` attributes by hand.
645
650
 
646
- Example:
651
+ Example:
647
652
 
648
653
  ```html
649
654
  <div>
@@ -653,8 +658,21 @@ Example:
653
658
  <script>
654
659
  const nameInput = pp.ref(null);
655
660
  </script>
656
- </div>
657
- ```
661
+ </div>
662
+ ```
663
+
664
+ Component example:
665
+
666
+ ```html
667
+ <div>
668
+ <x-input pp-ref="{nameInput}" name="name" />
669
+ <button onclick="nameInput.current?.focus()">Focus</button>
670
+
671
+ <script>
672
+ const nameInput = pp.ref(null);
673
+ </script>
674
+ </div>
675
+ ```
658
676
 
659
677
  ## Lists and keyed diffing
660
678
 
@@ -863,8 +881,9 @@ These are current runtime caveats that matter for authors and AI tools:
863
881
  - Caspian already injects `pp-component` and rewrites owned scripts to `type="text/pp"` during render.
864
882
  - Nested roots without their own `script[type="text/pp"]` block are not fully isolated during parent template compilation.
865
883
  - The global `pp` singleton auto-mounts on DOM ready, and `pp.mount()` is idempotent.
866
- - `pp.effect` and `pp.layoutEffect` are cleanup-style hooks. Their callbacks are not promise-aware.
867
- - `pp.context()` resolves through ancestor components, not the current component's own pending providers.
884
+ - `pp.effect` and `pp.layoutEffect` are cleanup-style hooks. Their callbacks are not promise-aware.
885
+ - Callback refs may return synchronous cleanup functions, which run instead of a later `callback(null)` detach call.
886
+ - `pp.context()` resolves through ancestor components, not the current component's own pending providers.
868
887
  - `pp.provideContext` is not part of the current runtime API. Use an HTML-first lowercase provider tag such as `<themecontext.provider>`.
869
888
  - `pp.portal()` preserves logical ancestry through the registry, so context and prop refresh behavior continue to work through portaled descendants.
870
889
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "caspian-utils",
3
- "version": "0.1.15",
3
+ "version": "0.1.16",
4
4
  "description": "Caspian tooling",
5
5
  "main": "index.js",
6
6
  "scripts": {