prisma-php 0.0.3 → 0.0.4
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.
- package/.github/copilot-instructions.md +24 -0
- package/dist/docs/components.md +38 -3
- package/dist/docs/index.md +3 -2
- package/dist/docs/layouts-and-pages.md +5 -4
- package/dist/docs/pulsepoint.md +135 -397
- package/package.json +1 -1
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Project Guidelines
|
|
2
|
+
|
|
3
|
+
## Source Of Truth
|
|
4
|
+
|
|
5
|
+
- Treat `dist/docs/index.md` as the entry point for Prisma PHP guidance in this repo.
|
|
6
|
+
- Read the matching doc in `dist/docs` before generating or editing framework-specific Prisma PHP code.
|
|
7
|
+
|
|
8
|
+
## Route File Conventions
|
|
9
|
+
|
|
10
|
+
- For PulsePoint-aware `index.php` and `layout.php`, keep file order as PHP, then HTML, then one `<script>` block.
|
|
11
|
+
- `index.php` and nested `layout.php` must render a single parent HTML element. Treat that root like a React-style component boundary rather than loose sibling markup.
|
|
12
|
+
- Only the root `layout.php` should define `<html>`, `<head>`, and `<body>`. When PulsePoint is present, keep `MainLayout::$children;` inside one clear wrapper.
|
|
13
|
+
|
|
14
|
+
## Component Boundary Rules
|
|
15
|
+
|
|
16
|
+
- Distinguish PHPX class components from `ImportComponent` partials.
|
|
17
|
+
- `ImportComponent` partials must output exactly one root element because Prisma PHP attaches serialized props and a stable `pp-component` attribute to that root.
|
|
18
|
+
- Do not manually add `pp-component` unless a documented framework feature injects it for that boundary.
|
|
19
|
+
|
|
20
|
+
## Relevant Docs
|
|
21
|
+
|
|
22
|
+
- Route and layout structure: `dist/docs/layouts-and-pages.md`
|
|
23
|
+
- PulsePoint runtime rules: `dist/docs/pulsepoint.md`
|
|
24
|
+
- Component and `ImportComponent` rules: `dist/docs/components.md`
|
package/dist/docs/components.md
CHANGED
|
@@ -69,6 +69,8 @@ This is the right mental model for:
|
|
|
69
69
|
- componentized partials that PulsePoint should hydrate later
|
|
70
70
|
- keeping PHP templating ergonomics while still producing component-aware markup
|
|
71
71
|
|
|
72
|
+
For `ImportComponent`, think in terms of a React-style single-root component: one parent element defines the island boundary, Prisma PHP serializes props onto that root, and the framework injects a stable `pp-component` attribute there. Do not design imported partials as loose sibling markup.
|
|
73
|
+
|
|
72
74
|
AI must not collapse these two models into one.
|
|
73
75
|
|
|
74
76
|
- **PHPX** is for class-based component authoring.
|
|
@@ -87,6 +89,8 @@ AI must not collapse these two models into one.
|
|
|
87
89
|
- when a component needs reactivity, distinguish between static PHP props and reactive PulsePoint props
|
|
88
90
|
- when a component requires grouped sub-components, follow the documented same-file naming pattern like `Accordion`, `AccordionItem`, `AccordionTrigger`, and `AccordionContent`
|
|
89
91
|
- when using `ImportComponent`, always ensure the imported file renders **exactly one root element**
|
|
92
|
+
- treat the `ImportComponent` root as the real component boundary because Prisma PHP serializes props onto it and injects `pp-component`
|
|
93
|
+
- do not manually add `pp-component` inside imported partial source; let Prisma PHP inject it
|
|
90
94
|
|
|
91
95
|
## Component file placement and naming
|
|
92
96
|
|
|
@@ -253,6 +257,8 @@ Prisma PHP:
|
|
|
253
257
|
|
|
254
258
|
- use `ImportComponent` for PHP partials, not for PHPX class components
|
|
255
259
|
- require exactly one root element in the imported file
|
|
260
|
+
- keep the imported partial in Prisma PHP order: PHP preamble first, then one parent HTML element; if client script is needed, place the `<script>` inside that parent so the rendered output still has one root
|
|
261
|
+
- treat that single root as the component boundary that receives `pp-component` and serialized props
|
|
256
262
|
- treat the imported output as a PulsePoint-ready island boundary
|
|
257
263
|
- pass server data as props and let Prisma PHP serialize them into attributes
|
|
258
264
|
- use mustache-valued props when passing reactive PulsePoint bindings into imported partials
|
|
@@ -309,20 +315,49 @@ use PP\ImportComponent;
|
|
|
309
315
|
|
|
310
316
|
The imported PHP file must output **exactly one root element**.
|
|
311
317
|
|
|
318
|
+
That root is not just for layout or styling. It is the element Prisma PHP augments with `pp-component` and serialized props.
|
|
319
|
+
|
|
320
|
+
A leading PHP block for imports, variables, helpers, or `#[Exposed]` functions is valid. Root counting applies to the emitted top-level HTML/XML nodes only.
|
|
321
|
+
|
|
322
|
+
For imported Prisma PHP partials, keep the file in this shape:
|
|
323
|
+
|
|
324
|
+
1. PHP
|
|
325
|
+
2. one parent HTML element
|
|
326
|
+
3. optional `<script>` inside that parent element
|
|
327
|
+
|
|
328
|
+
If the file emits a second top-level tag, including a sibling `<script>`, Prisma PHP will fail with an error such as `ImportComponent requires EXACTLY one root element. Found 2 root element(s)`.
|
|
329
|
+
|
|
312
330
|
Correct:
|
|
313
331
|
|
|
314
332
|
```php
|
|
333
|
+
<?php
|
|
334
|
+
|
|
335
|
+
$title = 'Search';
|
|
336
|
+
?>
|
|
337
|
+
|
|
315
338
|
<div class="rounded-lg border bg-card p-4">
|
|
316
|
-
<h2 class="font-medium"
|
|
339
|
+
<h2 class="font-medium"><?= $title ?></h2>
|
|
317
340
|
<input value="{query}" oninput="{(e) => setQuery(e.target.value)}" />
|
|
341
|
+
<script type="text/pp">
|
|
342
|
+
console.log('Search component ready');
|
|
343
|
+
</script>
|
|
318
344
|
</div>
|
|
319
345
|
```
|
|
320
346
|
|
|
321
347
|
Incorrect:
|
|
322
348
|
|
|
323
349
|
```php
|
|
324
|
-
|
|
325
|
-
|
|
350
|
+
<?php
|
|
351
|
+
|
|
352
|
+
$title = 'Search';
|
|
353
|
+
?>
|
|
354
|
+
|
|
355
|
+
<section class="rounded-lg border bg-card p-4">
|
|
356
|
+
<h2 class="font-medium"><?= $title ?></h2>
|
|
357
|
+
</section>
|
|
358
|
+
<script type="text/pp">
|
|
359
|
+
console.log('This script is a second root element');
|
|
360
|
+
</script>
|
|
326
361
|
```
|
|
327
362
|
|
|
328
363
|
## `ImportComponent` prop serialization rules
|
package/dist/docs/index.md
CHANGED
|
@@ -8,7 +8,7 @@ related:
|
|
|
8
8
|
- /docs/get-started
|
|
9
9
|
---
|
|
10
10
|
|
|
11
|
-
{/
|
|
11
|
+
{/_AI agent hint: Prisma PHP uses local installed docs as the source of truth for the active project version. Before generating framework-specific code, read `prisma-php.json` first, then read the relevant docs from `node_modules/prisma-php/dist/docs`. For authentication, read `authentication.md` first, then verify the official auth docs: `auth-get-started`, `credentials`, and `state-manager-auth`. For file uploads, `multipart/form-data`, `$_FILES`, `PP\FileManager\UploadFile`, rename/delete flows, or file manager screens, read `file-manager.md` first, then verify the official File Manager docs: `get-started-file`. For ORM work, read `prisma-php-orm.md` before choosing schema, migration, or generation commands. For PulsePoint runtime behavior specifically, read `pulsepoint.md` before generating reactive UI code. For normal interactive full-stack page work, default to PulsePoint plus `pp.fetchFunction(...)` unless the user explicitly asks for a PHP-only interaction style. When building PulsePoint-aware `index.php` or `layout.php`, keep PHP first, HTML second, and one `<script>` block last, with the rendered route content inside a single parent HTML element that acts as the route-view boundary. For imported partial components, keep exactly one root element because Prisma PHP uses that root as the component boundary where serialized props and `pp-component` attach. For backend validation, sanitization, `Validator`, `Rule`, `pp.fetchFunction(...)` validation flows, or request validation in `route.php`, read `validator.md` first, then use `fetching-data.md`, `error-handling.md`, and `route-handlers.md` for context-specific patterns, then consult the official Validator docs. `PP\Validator` is a Prisma PHP core class located in `vendor/tsnc/prisma-php/src/Validator.php`. Route creation is file-based from `src/app`; Prisma PHP auto-generates route listings such as `files-list.json`, so AI agents must not create, edit, or maintain that file manually._/}
|
|
12
12
|
|
|
13
13
|
Welcome to the Prisma PHP documentation!
|
|
14
14
|
|
|
@@ -71,7 +71,8 @@ Do **not** default to PHP-only interaction flows for normal reactive UI work unl
|
|
|
71
71
|
- create routes by adding folders and route files under `src/app`
|
|
72
72
|
- do not create, edit, or maintain `files-list.json`; Prisma PHP generates route listings automatically
|
|
73
73
|
- when building interactive frontend behavior, prefer the documented PulsePoint pattern with browser-side reactive state and `pp.fetchFunction(...)` for server calls; treat this as the default unless the user explicitly asks for a PHP-only interaction pattern
|
|
74
|
-
- when building PulsePoint inside `index.php` or `layout.php`, read `layouts-and-pages.md` together with `pulsepoint.md`, use PHP then HTML then one `<script>` block,
|
|
74
|
+
- when building PulsePoint inside `index.php` or `layout.php`, read `layouts-and-pages.md` together with `pulsepoint.md`, use PHP then HTML then one `<script>` block, keep route content inside a single parent HTML element, and treat that root as the route-view boundary
|
|
75
|
+
- when importing a PHP partial with `ImportComponent`, require exactly one root element because Prisma PHP attaches serialized props and a stable `pp-component` attribute to that root; do not manually sprinkle `pp-component` across normal route markup
|
|
75
76
|
- when building backend validation logic, read `validator.md` first, then use the route-specific docs for the request entry point
|
|
76
77
|
- when using `pp.fetchFunction(...)`, expose the PHP function explicitly with `#[Exposed]`
|
|
77
78
|
- when changing feature flags, update `prisma-php.json` first, then follow the documented update workflow
|
|
@@ -70,10 +70,11 @@ In practice, that means:
|
|
|
70
70
|
- render the route markup after the PHP block
|
|
71
71
|
- place one PulsePoint `<script>` block at the bottom of the file
|
|
72
72
|
|
|
73
|
-
For the HTML portion, keep a **single parent HTML tag** around the route content, similar to React's single-root component rule.
|
|
73
|
+
For the HTML portion, keep a **single parent HTML tag** around the route content, similar to React's single-root component rule. Treat that root as the page or layout boundary instead of as a throwaway wrapper.
|
|
74
74
|
|
|
75
|
-
- In `index.php`, wrap the page UI in one parent element such as `<main>`, `<section>`, or `<article
|
|
76
|
-
- In nested `layout.php`, wrap the shared layout UI and `<?= MainLayout::$children; ?>` in one parent element.
|
|
75
|
+
- In `index.php`, wrap the page UI in one parent element such as `<main>`, `<section>`, or `<article>`, and treat that root as the route view's component-style boundary.
|
|
76
|
+
- In nested `layout.php`, wrap the shared layout UI and `<?= MainLayout::$children; ?>` in one parent element so the layout still behaves like one boundary.
|
|
77
|
+
- If that markup is later extracted into an `ImportComponent` partial, preserve the same single-root structure so Prisma PHP can attach the stable `pp-component` attribute to that root element.
|
|
77
78
|
- The root `layout.php` is the document shell and is the only layout that should contain `<html>` and `<body>`. Inside `<body>`, keep one clear wrapper around `<?= MainLayout::$children; ?>` when PulsePoint behavior is present.
|
|
78
79
|
|
|
79
80
|
Example PulsePoint page structure:
|
|
@@ -210,7 +211,7 @@ src/app/
|
|
|
210
211
|
```
|
|
211
212
|
|
|
212
213
|
If you combine both layouts, the root layout wraps the blog layout, and the blog layout wraps both `src/app/blog/index.php` and `src/app/blog/[slug]/index.php`.
|
|
213
|
-
Nested layouts should behave like single-root route views: one parent element should wrap the shared layout UI and `MainLayout::$children;`.
|
|
214
|
+
Nested layouts should behave like single-root route views: one parent element should wrap the shared layout UI and `MainLayout::$children;`. Keep thinking in terms of one component-style boundary, not multiple sibling roots.
|
|
214
215
|
|
|
215
216
|
## Creating a dynamic segment
|
|
216
217
|
|
package/dist/docs/pulsepoint.md
CHANGED
|
@@ -1,474 +1,212 @@
|
|
|
1
|
-
# PulsePoint
|
|
1
|
+
# PulsePoint Runtime Guide
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
>
|
|
5
|
-
> Scope: this document covers the **core PulsePoint runtime** only — browser-side state, effects, refs, and markup directives. It does **not** assume any specific backend or server framework.
|
|
6
|
-
>
|
|
7
|
-
> Official docs: [https://pulsepoint.tsnc.tech/llms](https://pulsepoint.tsnc.tech/llms)
|
|
8
|
-
>
|
|
9
|
-
> Version: 3.0.0
|
|
3
|
+
## Purpose
|
|
10
4
|
|
|
11
|
-
|
|
5
|
+
This file describes the PulsePoint runtime that is actually implemented in the current TypeScript source of this repo. Use it as the working contract for AI-generated code.
|
|
12
6
|
|
|
13
|
-
|
|
7
|
+
If `src/` is available, read it first. If the app only exposes a plain HTML page plus a minified or bundled runtime loaded by a script tag, inspect those artifacts instead and follow the same behavior. Do not assume React, Vue, Svelte, or older PulsePoint docs.
|
|
14
8
|
|
|
15
|
-
|
|
9
|
+
## What the runtime actually does
|
|
16
10
|
|
|
17
|
-
|
|
18
|
-
https://pulsepoint.tsnc.tech/llms
|
|
19
|
-
```
|
|
20
|
-
|
|
21
|
-
Treat the PulsePoint docs as the source of truth for:
|
|
22
|
-
|
|
23
|
-
- supported runtime APIs
|
|
24
|
-
- supported directives and markup behavior
|
|
25
|
-
- syntax rules
|
|
26
|
-
- state and effect patterns
|
|
27
|
-
- ref usage
|
|
28
|
-
- limitations and forbidden patterns
|
|
29
|
-
|
|
30
|
-
Do not rely on assumptions from React, Vue, Alpine, Svelte, Livewire, jQuery, or any other framework.
|
|
31
|
-
|
|
32
|
-
If the docs do not show a feature or API, do **not** invent it.
|
|
33
|
-
|
|
34
|
-
---
|
|
35
|
-
|
|
36
|
-
## Prisma PHP integration rule
|
|
37
|
-
|
|
38
|
-
When PulsePoint is used inside a Prisma PHP application, the default full-stack pattern is:
|
|
39
|
-
|
|
40
|
-
- PulsePoint for reactive browser state and UI updates
|
|
41
|
-
- `pp.fetchFunction(...)` for frontend-to-PHP server calls
|
|
42
|
-
- `#[Exposed]` on PHP functions or methods that the frontend should be allowed to call
|
|
43
|
-
- `PP\Validator` on the PHP side for authoritative input validation and normalization
|
|
44
|
-
|
|
45
|
-
For normal interactive Prisma PHP page work, AI should treat this as the **default implementation path**, not as an optional enhancement. Choose a more PHP-only interaction style only when the user explicitly asks for it or when the route is intentionally non-reactive.
|
|
46
|
-
|
|
47
|
-
Do not invent parallel client/server patterns when the built-in Prisma PHP RPC flow already fits the task. In typical Prisma PHP page work, prefer PulsePoint plus `pp.fetchFunction(...)` over ad hoc API wiring, manual JSON fetch boilerplate, or PHP-only interaction patterns that the user did not request.
|
|
48
|
-
|
|
49
|
-
If the PulsePoint code lives inside Prisma PHP `index.php` or `layout.php`, also follow the route-file structure rules from `layouts-and-pages.md`: PHP first, HTML second, one `<script>` block last, and a single parent HTML element for normal route content.
|
|
50
|
-
|
|
51
|
-
Use `route.php` only when you explicitly need a standalone endpoint such as a webhook, public JSON route, external integration endpoint, or other no-view handler. Do not move normal route-local interactivity into `route.php` by default.
|
|
52
|
-
|
|
53
|
-
## Validation boundary
|
|
54
|
-
|
|
55
|
-
PulsePoint can help with local UX such as:
|
|
56
|
-
|
|
57
|
-
- showing inline hints
|
|
58
|
-
- disabling submit buttons
|
|
59
|
-
- rendering field-level error messages
|
|
60
|
-
- debouncing live validation requests
|
|
61
|
-
|
|
62
|
-
But the final validation still belongs on the PHP side.
|
|
63
|
-
|
|
64
|
-
In Prisma PHP, the default boundary is:
|
|
65
|
-
|
|
66
|
-
- **PulsePoint** for local UI state and feedback
|
|
67
|
-
- **`pp.fetchFunction(...)`** for sending data to PHP
|
|
68
|
-
- **`PP\Validator`** for authoritative backend validation
|
|
69
|
-
|
|
70
|
-
Do not present browser-only checks as the final source of truth.
|
|
71
|
-
|
|
72
|
-
---
|
|
73
|
-
|
|
74
|
-
## What PulsePoint is responsible for
|
|
75
|
-
|
|
76
|
-
PulsePoint is the **browser-side reactive runtime**.
|
|
77
|
-
|
|
78
|
-
It is responsible for UI interactivity such as:
|
|
79
|
-
|
|
80
|
-
- local reactive state
|
|
81
|
-
- reactive effects
|
|
82
|
-
- DOM refs
|
|
83
|
-
- list rendering
|
|
84
|
-
- attribute spreading
|
|
85
|
-
- simple interactive bindings
|
|
86
|
-
|
|
87
|
-
PulsePoint is **not** the backend.
|
|
88
|
-
|
|
89
|
-
That means an AI agent must keep these concerns separate:
|
|
90
|
-
|
|
91
|
-
- **PulsePoint docs** decide how frontend runtime code should be written.
|
|
92
|
-
- **Project/backend docs** decide how data is loaded, validated, saved, authenticated, or routed on the server.
|
|
93
|
-
|
|
94
|
-
---
|
|
95
|
-
|
|
96
|
-
## Allowed runtime surface
|
|
97
|
-
|
|
98
|
-
Only use the runtime surface explicitly allowed here unless the official docs say otherwise.
|
|
99
|
-
|
|
100
|
-
### Core JavaScript API
|
|
101
|
-
|
|
102
|
-
1. `pp.state<T>(initial)` → `[state, setState]`
|
|
103
|
-
2. `pp.ref<T>(initial: T | null)`
|
|
104
|
-
3. `pp.effect(fn: () => void | (() => void), deps?: Dependency[])`
|
|
105
|
-
|
|
106
|
-
### Markup directives
|
|
11
|
+
PulsePoint is a browser-side component runtime. It renders HTML, binds state and events, and morphs the DOM in place.
|
|
107
12
|
|
|
108
|
-
|
|
109
|
-
2. `pp-spread`
|
|
110
|
-
3. `pp-ref`
|
|
13
|
+
A component root is any element with `pp-component`.
|
|
111
14
|
|
|
112
|
-
|
|
15
|
+
## Component roots and scripts
|
|
113
16
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
2. Do not invent undocumented helpers, directives, lifecycle APIs, or magic globals.
|
|
122
|
-
3. Keep backend assumptions out of PulsePoint-only examples.
|
|
123
|
-
4. Prefer simple, explicit patterns over framework-inspired abstractions.
|
|
124
|
-
5. Generate code that is valid immediately, not pseudo-code.
|
|
125
|
-
6. When uncertain, reduce scope to documented PulsePoint primitives.
|
|
126
|
-
7. If a project also includes framework-specific rules, apply them **after** the PulsePoint docs for backend concerns, but keep PulsePoint runtime syntax aligned with the official PulsePoint docs.
|
|
127
|
-
|
|
128
|
-
---
|
|
129
|
-
|
|
130
|
-
## Priority order
|
|
131
|
-
|
|
132
|
-
When generating code that uses PulsePoint, use this order of truth:
|
|
133
|
-
|
|
134
|
-
1. The user’s explicit request
|
|
135
|
-
2. The official PulsePoint docs: `https://pulsepoint.tsnc.tech/llms`
|
|
136
|
-
3. Project-specific agent rules or local project docs
|
|
137
|
-
4. Existing project code and structure
|
|
138
|
-
5. General framework knowledge
|
|
139
|
-
|
|
140
|
-
If any assumption conflicts with the official PulsePoint docs, follow the PulsePoint docs.
|
|
141
|
-
|
|
142
|
-
---
|
|
143
|
-
|
|
144
|
-
## File layout rules
|
|
145
|
-
|
|
146
|
-
For PulsePoint runtime code, keep a predictable layout.
|
|
147
|
-
|
|
148
|
-
### Standalone runtime snippet order
|
|
149
|
-
|
|
150
|
-
1. HTML markup first
|
|
151
|
-
2. One `<script>` block at the bottom
|
|
17
|
+
- Each component root may contain one inline `<script type="text/pp">` block for component logic.
|
|
18
|
+
- The script is plain JavaScript, not a module. Do not use `import` or `export` inside it.
|
|
19
|
+
- Top-level bindings from the script are exported automatically to the template. Do not manually `return { ... }`.
|
|
20
|
+
- Top-level `const`, `let`, `function`, and supported destructuring patterns become template bindings.
|
|
21
|
+
- `pp.props` contains the current prop bag for the component.
|
|
22
|
+
- `children` contains the component's initial inner HTML.
|
|
23
|
+
- Nested `pp-component` roots are boundaries and are not flattened by a parent component.
|
|
152
24
|
|
|
153
25
|
Example:
|
|
154
26
|
|
|
155
27
|
```html
|
|
156
|
-
<
|
|
157
|
-
<
|
|
158
|
-
<li key="{user.id}">{user.name}</li>
|
|
159
|
-
</template>
|
|
160
|
-
</ul>
|
|
161
|
-
|
|
162
|
-
<script>
|
|
163
|
-
const [users, setUsers] = pp.state([
|
|
164
|
-
{ id: 1, name: "Alice" },
|
|
165
|
-
{ id: 2, name: "Bob" },
|
|
166
|
-
]);
|
|
167
|
-
</script>
|
|
168
|
-
```
|
|
169
|
-
|
|
170
|
-
### Prisma PHP route file order
|
|
171
|
-
|
|
172
|
-
When PulsePoint is used inside Prisma PHP `index.php` or `layout.php` files, keep this order:
|
|
173
|
-
|
|
174
|
-
1. PHP
|
|
175
|
-
2. HTML
|
|
176
|
-
3. SCRIPT
|
|
177
|
-
|
|
178
|
-
Example:
|
|
179
|
-
|
|
180
|
-
```php filename="src/app/dashboard/index.php"
|
|
181
|
-
<?php
|
|
182
|
-
|
|
183
|
-
$title = "Dashboard";
|
|
184
|
-
?>
|
|
185
|
-
<section class="dashboard-page">
|
|
186
|
-
<h1><?= htmlspecialchars($title); ?></h1>
|
|
28
|
+
<div pp-component="counter-card">
|
|
29
|
+
<h2>{title}</h2>
|
|
187
30
|
<p>Count: {count}</p>
|
|
188
31
|
<button onclick="setCount(count + 1)">Increment</button>
|
|
189
|
-
</section>
|
|
190
|
-
|
|
191
|
-
<script>
|
|
192
|
-
const [count, setCount] = pp.state(0);
|
|
193
|
-
</script>
|
|
194
|
-
```
|
|
195
|
-
|
|
196
|
-
Additional Prisma PHP route rules:
|
|
197
|
-
|
|
198
|
-
- `index.php` and nested `layout.php` should render a single parent HTML element.
|
|
199
|
-
- Treat them like single-root route views; do not emit multiple sibling root nodes for normal PulsePoint pages.
|
|
200
|
-
- The root `layout.php` is the only file that should define `<html>`, `<head>`, and `<body>`.
|
|
201
|
-
- When the root layout uses PulsePoint, keep the script block near the end of `<body>` and keep `MainLayout::$children;` inside one clear wrapper element.
|
|
202
|
-
|
|
203
|
-
Rules:
|
|
204
|
-
|
|
205
|
-
- Use **exactly one** `<script>` block per page, layout, or component unless the project explicitly requires something else.
|
|
206
|
-
- Place the script block **after** the markup. In Prisma PHP route files, that means after the HTML section.
|
|
207
|
-
- JavaScript should only reference data that already exists when the script runs.
|
|
208
|
-
- Do not scatter PulsePoint runtime logic across multiple disconnected script blocks unless the docs or project structure explicitly requires it.
|
|
209
32
|
|
|
210
|
-
|
|
33
|
+
<script type="text/pp">
|
|
34
|
+
const { title } = pp.props;
|
|
35
|
+
const [count, setCount] = pp.state(0);
|
|
36
|
+
const doubled = count * 2;
|
|
211
37
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
```html
|
|
219
|
-
<div hidden="{!isOpen}">Shown when open</div>
|
|
220
|
-
<div hidden="{isOpen}">Shown when closed</div>
|
|
221
|
-
```
|
|
222
|
-
|
|
223
|
-
### Text or attribute ternaries
|
|
224
|
-
|
|
225
|
-
```html
|
|
226
|
-
<span class="{isActive ? 'text-green-600' : 'text-red-600'}">
|
|
227
|
-
{isActive ? 'Active' : 'Inactive'}
|
|
228
|
-
</span>
|
|
229
|
-
```
|
|
230
|
-
|
|
231
|
-
### Boolean attributes
|
|
232
|
-
|
|
233
|
-
```html
|
|
234
|
-
<input type="checkbox" checked="{isActive}" />
|
|
38
|
+
function reset() {
|
|
39
|
+
setCount(0);
|
|
40
|
+
}
|
|
41
|
+
</script>
|
|
42
|
+
</div>
|
|
235
43
|
```
|
|
236
44
|
|
|
237
|
-
|
|
45
|
+
## Hooks exposed through `pp`
|
|
238
46
|
|
|
239
|
-
-
|
|
240
|
-
-
|
|
47
|
+
- `pp.state(initial)` returns `[value, setValue]`.
|
|
48
|
+
- `pp.effect(callback, deps?)` runs after render and can return cleanup.
|
|
49
|
+
- `pp.layoutEffect(callback, deps?)` runs synchronously after DOM mutation.
|
|
50
|
+
- `pp.ref(initialValue?)` returns `{ current }`.
|
|
51
|
+
- `pp.memo(factory, deps)` memoizes a computed value.
|
|
52
|
+
- `pp.callback(callback, deps)` memoizes a function.
|
|
53
|
+
- `pp.reducer(reducer, initialState)` returns `[state, dispatch]`.
|
|
54
|
+
- `pp.portal(ref, target?)` portals a ref-managed element into a target.
|
|
55
|
+
- `pp.props` exposes the current props.
|
|
241
56
|
|
|
242
|
-
|
|
57
|
+
Notes:
|
|
243
58
|
|
|
244
|
-
|
|
59
|
+
- `pp.state` setters accept either a value or an updater function.
|
|
60
|
+
- `pp.effect` and `pp.layoutEffect` use dependency arrays like React, but only the hooks listed here exist.
|
|
61
|
+
- Keep template-facing bindings at the top level so the AST-based exporter can see them.
|
|
245
62
|
|
|
246
|
-
|
|
63
|
+
## Template expressions and attributes
|
|
247
64
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
</script>
|
|
255
|
-
```
|
|
65
|
+
- Use `{expression}` in text nodes and attribute values.
|
|
66
|
+
- Pure bindings like `value="{count}"` are evaluated as expressions.
|
|
67
|
+
- Mixed text like `class="card {isActive ? 'active' : ''}"` is supported.
|
|
68
|
+
- Boolean attributes are normalized for supported boolean names.
|
|
69
|
+
- Use `pp-spread="{...attrs}"` to spread one object expression into attributes.
|
|
70
|
+
- Use plain `key` for keyed diffing. `pp-key` is not implemented in the current source.
|
|
256
71
|
|
|
257
|
-
|
|
72
|
+
Example:
|
|
258
73
|
|
|
259
74
|
```html
|
|
260
|
-
<
|
|
261
|
-
<template pp-for="item in items">
|
|
262
|
-
<li key="{item.id}">
|
|
263
|
-
<input pp-ref="{registerRef(item.id)}" hidden="{editingId !== item.id}" />
|
|
264
|
-
<button onclick="setEditingId(item.id)">Edit</button>
|
|
265
|
-
</li>
|
|
266
|
-
</template>
|
|
267
|
-
</ul>
|
|
75
|
+
<button pp-spread="{...buttonAttrs}" hidden="{isLoading}">Save</button>
|
|
268
76
|
|
|
269
|
-
<script>
|
|
270
|
-
const
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
]);
|
|
274
|
-
const [editingId, setEditingId] = pp.state(null);
|
|
275
|
-
|
|
276
|
-
const itemRefs = new Map();
|
|
277
|
-
|
|
278
|
-
const registerRef = (id) => (el) => {
|
|
279
|
-
if (el) itemRefs.set(id, el);
|
|
280
|
-
else itemRefs.delete(id);
|
|
77
|
+
<script type="text/pp">
|
|
78
|
+
const buttonAttrs = {
|
|
79
|
+
class: "btn btn-primary",
|
|
80
|
+
"aria-label": "save",
|
|
281
81
|
};
|
|
282
|
-
|
|
283
|
-
pp.effect(() => {
|
|
284
|
-
if (editingId && itemRefs.has(editingId)) {
|
|
285
|
-
itemRefs.get(editingId).focus();
|
|
286
|
-
}
|
|
287
|
-
}, [editingId]);
|
|
82
|
+
const isLoading = false;
|
|
288
83
|
</script>
|
|
289
84
|
```
|
|
290
85
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
- Initialize refs with `pp.ref(null)`.
|
|
294
|
-
- Null-check ref access where appropriate.
|
|
295
|
-
- Use refs for DOM access and imperative behavior, not as general reactive state storage.
|
|
296
|
-
|
|
297
|
-
---
|
|
298
|
-
|
|
299
|
-
## Spread with `pp-spread`
|
|
86
|
+
## Refs
|
|
300
87
|
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
<script>
|
|
307
|
-
const [btn] = pp.state({
|
|
308
|
-
class: "px-3 py-2 rounded-md",
|
|
309
|
-
"aria-label": "submit",
|
|
310
|
-
});
|
|
311
|
-
</script>
|
|
312
|
-
```
|
|
88
|
+
- Use `pp-ref="nameInput"` when the value is a ref object or callback already in scope.
|
|
89
|
+
- Use `pp-ref="{registerRef(id)}"` when you need a dynamic expression.
|
|
90
|
+
- `pp.ref(null)` is the normal way to create a ref object.
|
|
91
|
+
- The runtime generates `data-pp-ref` internally. Do not author it.
|
|
92
|
+
- Do not author `pp-event-owner`, `pp-owner`, or `pp-dynamic-*` attributes by hand.
|
|
313
93
|
|
|
314
|
-
|
|
94
|
+
Example:
|
|
315
95
|
|
|
316
96
|
```html
|
|
317
|
-
<
|
|
318
|
-
|
|
319
|
-
<
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
97
|
+
<div pp-component="focus-box">
|
|
98
|
+
<input pp-ref="nameInput" />
|
|
99
|
+
<button onclick="nameInput.current?.focus()">Focus</button>
|
|
100
|
+
|
|
101
|
+
<script type="text/pp">
|
|
102
|
+
const nameInput = pp.ref(null);
|
|
103
|
+
</script>
|
|
104
|
+
</div>
|
|
323
105
|
```
|
|
324
106
|
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
- Later spreads override earlier spreads.
|
|
328
|
-
- Explicit attributes on the element override spread values.
|
|
107
|
+
## Lists and loops
|
|
329
108
|
|
|
330
|
-
|
|
109
|
+
- Use `pp-for` only on `<template>`.
|
|
110
|
+
- Supported forms are `item in items` and `(item, index) in items`.
|
|
111
|
+
- The collection must be an array. Non-arrays are treated as empty lists.
|
|
112
|
+
- Loop content can contain interpolations, events, refs, and nested components.
|
|
113
|
+
- Use stable unique `key` values on repeated elements.
|
|
114
|
+
- Duplicate keys can trigger warnings, so do not use random keys.
|
|
331
115
|
|
|
332
|
-
|
|
116
|
+
Example:
|
|
333
117
|
|
|
334
118
|
```html
|
|
335
119
|
<ul>
|
|
336
|
-
<template pp-for="todo in todos">
|
|
120
|
+
<template pp-for="(todo, index) in todos">
|
|
337
121
|
<li key="{todo.id}">
|
|
338
|
-
{todo.title}
|
|
122
|
+
{index + 1}. {todo.title}
|
|
339
123
|
<button onclick="removeTodo(todo.id)">Remove</button>
|
|
340
124
|
</li>
|
|
341
125
|
</template>
|
|
342
126
|
</ul>
|
|
343
127
|
|
|
344
|
-
<script>
|
|
128
|
+
<script type="text/pp">
|
|
345
129
|
const [todos, setTodos] = pp.state([
|
|
346
130
|
{ id: 1, title: "First task" },
|
|
347
131
|
{ id: 2, title: "Second task" },
|
|
348
132
|
]);
|
|
349
133
|
|
|
350
134
|
function removeTodo(id) {
|
|
351
|
-
setTodos(todos.filter((
|
|
135
|
+
setTodos(todos.filter((todo) => todo.id !== id));
|
|
352
136
|
}
|
|
353
137
|
</script>
|
|
354
138
|
```
|
|
355
139
|
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
- Use `pp-for` only on `<template>`.
|
|
359
|
-
- Always use stable keys such as IDs or stable indices.
|
|
360
|
-
- Never use random keys.
|
|
361
|
-
- Prefer immutable updates through setters.
|
|
362
|
-
|
|
363
|
-
---
|
|
364
|
-
|
|
365
|
-
## Effects with `pp.effect`
|
|
366
|
-
|
|
367
|
-
```html
|
|
368
|
-
<script>
|
|
369
|
-
pp.effect(() => console.log("Any change"));
|
|
370
|
-
|
|
371
|
-
pp.effect(() => {
|
|
372
|
-
return () => {
|
|
373
|
-
// cleanup
|
|
374
|
-
};
|
|
375
|
-
}, []);
|
|
376
|
-
|
|
377
|
-
const [count, setCount] = pp.state(0);
|
|
378
|
-
|
|
379
|
-
pp.effect(() => {
|
|
380
|
-
console.log("count", count);
|
|
381
|
-
}, [count]);
|
|
382
|
-
</script>
|
|
383
|
-
```
|
|
384
|
-
|
|
385
|
-
Rules:
|
|
140
|
+
## Events
|
|
386
141
|
|
|
387
|
-
- Use `
|
|
388
|
-
-
|
|
389
|
-
-
|
|
390
|
-
-
|
|
142
|
+
- Use native `on*` attributes such as `onclick`, `oninput`, and `onsubmit`.
|
|
143
|
+
- Event values may be raw code or wrapped in `{...}`.
|
|
144
|
+
- The runtime injects `event`, `e`, `$event`, `target`, `currentTarget`, and `el`.
|
|
145
|
+
- Do not use hyphenated event attrs like `on-click`.
|
|
146
|
+
- Handlers are rebound after DOM morphing, so do not assume one-time binding.
|
|
391
147
|
|
|
392
|
-
|
|
148
|
+
## SPA, loading, and navigation helpers
|
|
393
149
|
|
|
394
|
-
|
|
150
|
+
- `body[pp-spa="true"]` enables client-side navigation interception.
|
|
151
|
+
- `a[pp-spa="false"]` disables interception for that link.
|
|
152
|
+
- `pp-loading-content="true"` marks the page region that gets swapped during navigation.
|
|
153
|
+
- `pp-loading-url` selects route-specific loading states.
|
|
154
|
+
- `pp-loading-transition` accepts JSON with `fadeIn` and `fadeOut` timing values.
|
|
155
|
+
- `pp-reset-scroll="true"` resets scroll positions during navigation.
|
|
395
156
|
|
|
396
|
-
|
|
157
|
+
The runtime also exposes navigation and bridge helpers through `pp`:
|
|
397
158
|
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
<script>
|
|
406
|
-
const [isActive, setIsActive] = pp.state(false);
|
|
407
|
-
</script>
|
|
408
|
-
```
|
|
159
|
+
- `pp.mount()`
|
|
160
|
+
- `pp.redirect(url)`
|
|
161
|
+
- `pp.fetchFunction(name, data?, optionsOrAbort?)`
|
|
162
|
+
- `pp.enablePerf()`
|
|
163
|
+
- `pp.disablePerf()`
|
|
164
|
+
- `pp.getPerfStats()`
|
|
165
|
+
- `pp.resetPerfStats()`
|
|
409
166
|
|
|
410
|
-
|
|
167
|
+
Notes:
|
|
411
168
|
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
<script>
|
|
416
|
-
const [name, setName] = pp.state("");
|
|
417
|
-
</script>
|
|
418
|
-
```
|
|
169
|
+
- `pp.redirect()` uses SPA navigation when it is enabled and the URL is same-origin; otherwise it falls back to normal navigation.
|
|
170
|
+
- `pp.fetchFunction()` is the runtime RPC bridge. It posts to the current route, attaches the wire headers the backend expects, and can switch to FormData for files, report upload progress, abort the previous request, or stream responses when supported.
|
|
171
|
+
- The bundle mounts the runtime automatically once the global `pp` singleton is accessed after DOMContentLoaded, but `pp.mount()` exists if you want to trigger it explicitly.
|
|
419
172
|
|
|
420
|
-
|
|
173
|
+
## Plain HTML bundle usage
|
|
421
174
|
|
|
422
|
-
|
|
423
|
-
- Inputs should write back through explicit setters.
|
|
424
|
-
- Avoid mixing manual DOM querying with reactive state for the same value.
|
|
175
|
+
When the app is shipped as plain HTML plus a minified bundle:
|
|
425
176
|
|
|
426
|
-
|
|
177
|
+
- Keep the component markup in the HTML.
|
|
178
|
+
- Load the bundle with a script tag after the markup or with `defer`.
|
|
179
|
+
- Keep component logic inside `<script type="text/pp">` blocks in the HTML.
|
|
180
|
+
- Do not assume access to the TypeScript source at runtime.
|
|
427
181
|
|
|
428
|
-
##
|
|
182
|
+
## What to avoid
|
|
429
183
|
|
|
430
|
-
Do not generate these unless the
|
|
184
|
+
Do not generate these unless the current source explicitly supports them:
|
|
431
185
|
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
186
|
+
- `pp-context`
|
|
187
|
+
- `pp-key`
|
|
188
|
+
- `data-pp-ref`
|
|
189
|
+
- `pp-owner`
|
|
190
|
+
- `pp-event-owner`
|
|
191
|
+
- `pp-dynamic-script`
|
|
192
|
+
- `pp-dynamic-meta`
|
|
193
|
+
- `pp-dynamic-link`
|
|
194
|
+
- plain `<script>` for component logic inside a component root
|
|
195
|
+
- React, Vue, Svelte, or JSX-style component patterns that are not implemented here
|
|
196
|
+
- made-up hooks, directives, or globals not present in the current TypeScript source
|
|
440
197
|
|
|
441
|
-
|
|
198
|
+
## AI checklist
|
|
442
199
|
|
|
443
|
-
|
|
200
|
+
When working on PulsePoint code, follow this order:
|
|
444
201
|
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
6. If the code lives in `index.php` or `layout.php`, use PHP, then HTML, then one `<script>` block, and keep a single parent HTML element for route content.
|
|
453
|
-
|
|
454
|
-
A good PulsePoint answer should feel:
|
|
455
|
-
|
|
456
|
-
- minimal
|
|
457
|
-
- explicit
|
|
458
|
-
- backend-agnostic when appropriate
|
|
459
|
-
- syntactically valid
|
|
460
|
-
- faithful to the documented runtime
|
|
461
|
-
|
|
462
|
-
---
|
|
202
|
+
- Use the current TypeScript source when it is available.
|
|
203
|
+
- If only HTML plus a minified bundle exists, inspect those artifacts and follow the runtime behavior implemented there.
|
|
204
|
+
- Keep template-facing variables at the top level of the component script.
|
|
205
|
+
- Use only the hooks, directives, and attributes listed above.
|
|
206
|
+
- Keep `pp-for` on `<template>` and use plain `key`.
|
|
207
|
+
- Use refs and events as implemented, not as they work in another framework.
|
|
208
|
+
- Avoid generating internal runtime attributes.
|
|
463
209
|
|
|
464
210
|
## Final reminder
|
|
465
211
|
|
|
466
|
-
If
|
|
467
|
-
|
|
468
|
-
Read the official docs first:
|
|
469
|
-
|
|
470
|
-
```txt
|
|
471
|
-
https://pulsepoint.tsnc.tech/llms
|
|
472
|
-
```
|
|
473
|
-
|
|
474
|
-
Then generate code using documented PulsePoint behavior only.
|
|
212
|
+
If a feature is not implemented in the current runtime source, do not invent it.
|