Reactivity (PulsePoint)

PulsePoint is the default reactive frontend layer for Caspian. In the current runtime, you author plain HTML plus a plain <script> inside a single template root, and Caspian injects the runtime-facing attributes before the browser mounts the page.

Authored vs Runtime

Authored templates

  • Keep exactly one root element or one imported x-* root.
  • Use a plain <script> inside that root when needed.
  • Use PulsePoint helpers like pp.state, pp.effect, and pp.rpc.
  • Do not handwrite pp-component or type="text/pp".

Runtime output

  • The browser sees mounted roots with pp-component.
  • Owned scripts are rewritten to script[type="text/pp"].
  • The global pp runtime auto-mounts on DOM ready.
  • Those runtime details explain behavior, but they are not the default authored form.

Hard invariant

A sibling <script> after the root or any second top-level node breaks Caspian’s component boundary injection. Keep the script inside the single authored root.

Core APIs

pp.state(initial) pp.effect(callback, deps?) pp.layoutEffect(callback, deps?) pp.ref(initialValue?) pp.context(token) pp.portal(ref, target?) pp-for on <template> pp-ref pp-spread / pp-style
<section>
  <h2>{title}</h2>
  <p>Count: {count}</p>

  <button onclick="setCount(count + 1)">
    Increment
  </button>

  <script>
    const  title = "Counter"  = pp.props;
    const [count, setCount] = pp.state(0);

    pp.effect(() => console.log(count), [count]);
  </script>
</section>

Browser To Server

Use pp.rpc() as the default bridge from interactive UI to route or backend @rpc() actions. Older wording like pp.fetchFunction() is outdated for the current bundled runtime.

async function saveProfile() 
  const user = await pp.rpc("update_user",  name: "Jeff" );

  const file = fileInput.files[0];
  await pp.rpc("upload_avatar",  avatar: file );

  await pp.rpc("search",  query , true);

  • Redirect headers are honored through pp.redirect().
  • Streaming and upload progress use the options form of pp.rpc().
  • For CRUD views, keep the authoritative list in pp.state(...) and render it with pp-for.

URL And Navigation

Use native URLSearchParams when you need to read the current query string. For navigation, use pp.redirect() for programmatic moves and keep standard <a> links for normal route navigation.

const params = new URLSearchParams(window.location.search);
const tab = params.get("tab") || "overview";
function goHome() 
  pp.redirect("/dashboard");


function goExternal() 
  pp.redirect("https://google.com");

In grouped shells, put pp-reset-scroll="true" on the main content pane when that pane should reset on child-route navigation while sidebars or rails keep their scroll position.

Context And Refs

The current context API follows a React-style provider pattern: pp.createContext(...), a Context.Provider tag with a value expression, and pp.context(token). Do not invent legacy helpers like pp.provideContext.

<div>
  <input pp-ref="nameInput" />
  <button onclick="nameInput.current?.focus()">Focus</button>

  <script>
    const nameInput = pp.ref(null);
  </script>
</div>
Read the upstream PulsePoint site for general concepts

For Caspian-specific authoring rules, prefer the installed runtime and the packaged Caspian docs over older examples or third-party snippets.