prisma-php 0.0.7 → 0.0.9
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 +4 -3
- package/dist/docs/components.md +15 -15
- package/dist/docs/index.md +11 -11
- package/dist/docs/layouts-and-pages.md +15 -15
- package/dist/docs/project-structure.md +9 -10
- package/dist/docs/pulsepoint.md +135 -50
- package/package.json +1 -1
|
@@ -7,15 +7,16 @@
|
|
|
7
7
|
|
|
8
8
|
## Route File Conventions
|
|
9
9
|
|
|
10
|
-
- For PulsePoint-aware `index.php` and `layout.php`, keep file order as PHP, then HTML
|
|
10
|
+
- For PulsePoint-aware `index.php` and nested `layout.php`, keep file order as PHP first, then one parent HTML element; keep the PulsePoint `<script>` as the last child inside that same root element.
|
|
11
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
|
-
-
|
|
12
|
+
- When a route or nested layout uses PulsePoint, put `pp-component` on that single root element and keep any route-local `<script>` inside it.
|
|
13
|
+
- Only the root `layout.php` should define `<html>`, `<head>`, and `<body>`. When PulsePoint is present, keep `MainLayout::$children;` and any `<script>` inside one clear wrapper.
|
|
13
14
|
|
|
14
15
|
## Component Boundary Rules
|
|
15
16
|
|
|
16
17
|
- Distinguish PHPX class components from `ImportComponent` partials.
|
|
17
18
|
- `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`
|
|
19
|
+
- Do not manually add `pp-component` inside `ImportComponent` partial source; Prisma PHP injects it there. For normal PulsePoint-aware route files, add `pp-component` to the single root element yourself.
|
|
19
20
|
|
|
20
21
|
## Relevant Docs
|
|
21
22
|
|
package/dist/docs/components.md
CHANGED
|
@@ -86,7 +86,7 @@ For a normal route file such as `src/app/index.php` or nested `layout.php`, use
|
|
|
86
86
|
|
|
87
87
|
1. PHP
|
|
88
88
|
2. one parent HTML element for the route content
|
|
89
|
-
3. one `<script>` block
|
|
89
|
+
3. when PulsePoint is present, keep one `<script>` block as the last child inside that same root element
|
|
90
90
|
|
|
91
91
|
Example route-file pattern:
|
|
92
92
|
|
|
@@ -96,14 +96,13 @@ Example route-file pattern:
|
|
|
96
96
|
$title = 'Dashboard';
|
|
97
97
|
?>
|
|
98
98
|
|
|
99
|
-
<section
|
|
99
|
+
<section pp-component="dashboard-page">
|
|
100
100
|
<h1><?= htmlspecialchars($title) ?></h1>
|
|
101
101
|
<p>Count: {count}</p>
|
|
102
|
+
<script>
|
|
103
|
+
const [count, setCount] = pp.state(0);
|
|
104
|
+
</script>
|
|
102
105
|
</section>
|
|
103
|
-
|
|
104
|
-
<script>
|
|
105
|
-
const [count, setCount] = pp.state(0);
|
|
106
|
-
</script>
|
|
107
106
|
```
|
|
108
107
|
|
|
109
108
|
That is the correct route-page pattern. It is documented in `layouts-and-pages.md`, not in `ImportComponent`.
|
|
@@ -117,11 +116,11 @@ Correct imported-partial pattern:
|
|
|
117
116
|
```php filename="src/app/inc/SearchBox.php"
|
|
118
117
|
<?php
|
|
119
118
|
|
|
120
|
-
|
|
119
|
+
// PHP Code
|
|
121
120
|
?>
|
|
122
121
|
|
|
123
|
-
<div class="rounded-lg border bg-card p-4"
|
|
124
|
-
<h2 class="font-medium"
|
|
122
|
+
<div class="rounded-lg border bg-card p-4">
|
|
123
|
+
<h2 class="font-medium">Search</h2>
|
|
125
124
|
<input value="{query}" oninput="{(e) => setQuery(e.target.value)}" />
|
|
126
125
|
<script>
|
|
127
126
|
console.log('Search component ready');
|
|
@@ -134,11 +133,11 @@ Incorrect imported-partial pattern:
|
|
|
134
133
|
```php filename="src/app/inc/SearchBox.php"
|
|
135
134
|
<?php
|
|
136
135
|
|
|
137
|
-
|
|
136
|
+
// PHP Code
|
|
138
137
|
?>
|
|
139
138
|
|
|
140
139
|
<section class="rounded-lg border bg-card p-4">
|
|
141
|
-
<h2 class="font-medium"
|
|
140
|
+
<h2 class="font-medium">Search</h2>
|
|
142
141
|
</section>
|
|
143
142
|
|
|
144
143
|
<script>
|
|
@@ -167,6 +166,7 @@ Why this is invalid:
|
|
|
167
166
|
- when using `ImportComponent`, always ensure the imported file renders **exactly one root element**
|
|
168
167
|
- treat the `ImportComponent` root as the real component boundary because Prisma PHP serializes props onto it and injects `pp-component`
|
|
169
168
|
- do not manually add `pp-component` inside imported partial source; let Prisma PHP inject it
|
|
169
|
+
- for normal PulsePoint-aware route files, author `pp-component` on the single route root and keep the `<script>` inside that root
|
|
170
170
|
- do not apply the imported-partial rule to normal route files
|
|
171
171
|
- do not apply the normal route-file pattern to imported partials
|
|
172
172
|
|
|
@@ -377,13 +377,13 @@ ImportComponent::render(APP_PATH . '/inc/UserCard.php', [
|
|
|
377
377
|
use PP\ImportComponent;
|
|
378
378
|
?>
|
|
379
379
|
|
|
380
|
-
<div
|
|
380
|
+
<div pp-component="search-shell">
|
|
381
381
|
<?php ImportComponent::render(APP_PATH . '/inc/SearchBox.php', [
|
|
382
382
|
'query' => '{query}',
|
|
383
383
|
'setQuery' => '{setQuery}',
|
|
384
384
|
]); ?>
|
|
385
385
|
|
|
386
|
-
<script
|
|
386
|
+
<script>
|
|
387
387
|
const [query, setQuery] = pp.state('');
|
|
388
388
|
</script>
|
|
389
389
|
</div>
|
|
@@ -416,7 +416,7 @@ $title = 'Search';
|
|
|
416
416
|
<div class="rounded-lg border bg-card p-4">
|
|
417
417
|
<h2 class="font-medium"><?= $title ?></h2>
|
|
418
418
|
<input value="{query}" oninput="{(e) => setQuery(e.target.value)}" />
|
|
419
|
-
<script
|
|
419
|
+
<script>
|
|
420
420
|
console.log('Search component ready');
|
|
421
421
|
</script>
|
|
422
422
|
</div>
|
|
@@ -433,7 +433,7 @@ $title = 'Search';
|
|
|
433
433
|
<section class="rounded-lg border bg-card p-4">
|
|
434
434
|
<h2 class="font-medium"><?= $title ?></h2>
|
|
435
435
|
</section>
|
|
436
|
-
<script
|
|
436
|
+
<script>
|
|
437
437
|
console.log('This script is a second root element');
|
|
438
438
|
</script>
|
|
439
439
|
```
|
package/dist/docs/index.md
CHANGED
|
@@ -8,7 +8,7 @@ related:
|
|
|
8
8
|
- /docs/get-started
|
|
9
9
|
---
|
|
10
10
|
|
|
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. For normal PulsePoint-aware route files such as `index.php` and nested `layout.php`, keep PHP first,
|
|
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. For normal PulsePoint-aware route files such as `index.php` and nested `layout.php`, keep PHP first, then one parent HTML element that acts as the route-view boundary; when PulsePoint logic is present, put `pp-component` on that root and keep the `<script>` as the last child inside it. For imported partial components rendered with `ImportComponent::render(...)`, keep exactly one root element and place any component-local `<script>` inside that root; Prisma PHP injects `pp-component` on the imported root automatically. Do not confuse the route-file pattern with the imported-partial pattern. 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
|
|
|
@@ -55,7 +55,7 @@ For a normal route file such as `src/app/index.php` or nested `layout.php`, use:
|
|
|
55
55
|
|
|
56
56
|
1. PHP
|
|
57
57
|
2. one parent HTML element
|
|
58
|
-
3. one `<script>` block
|
|
58
|
+
3. when PulsePoint is present, keep one `<script>` block as the last child inside that same root element
|
|
59
59
|
|
|
60
60
|
Example:
|
|
61
61
|
|
|
@@ -68,14 +68,13 @@ MainLayout::$title = 'Todos';
|
|
|
68
68
|
MainLayout::$description = 'Track tasks and view the current item count.';
|
|
69
69
|
?>
|
|
70
70
|
|
|
71
|
-
<section>
|
|
71
|
+
<section pp-component="todos-page">
|
|
72
72
|
<h1>Todos</h1>
|
|
73
73
|
<p>Count: {count}</p>
|
|
74
|
+
<script>
|
|
75
|
+
const [count, setCount] = pp.state(0);
|
|
76
|
+
</script>
|
|
74
77
|
</section>
|
|
75
|
-
|
|
76
|
-
<script>
|
|
77
|
-
const [count, setCount] = pp.state(0);
|
|
78
|
-
</script>
|
|
79
78
|
```
|
|
80
79
|
|
|
81
80
|
For SEO metadata, prefer `MainLayout::$title` and `MainLayout::$description` instead of relying on a local heading variable alone.
|
|
@@ -97,7 +96,7 @@ Example:
|
|
|
97
96
|
|
|
98
97
|
?>
|
|
99
98
|
|
|
100
|
-
<div
|
|
99
|
+
<div pp-component="search-box">
|
|
101
100
|
<h2>Search</h2>
|
|
102
101
|
<input value="{query}" />
|
|
103
102
|
<script>
|
|
@@ -134,9 +133,10 @@ Do not confuse the route-file pattern with the `ImportComponent` partial pattern
|
|
|
134
133
|
- create routes by adding folders and route files under `src/app`
|
|
135
134
|
- do not create, edit, or maintain `files-list.json`; Prisma PHP generates route listings automatically
|
|
136
135
|
- 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
|
|
137
|
-
- when building PulsePoint inside `index.php` or `layout.php`, read `layouts-and-pages.md` together with `pulsepoint.md`, use
|
|
138
|
-
- when importing a PHP partial with `ImportComponent`, require exactly one root element and keep any partial-local `<script>` inside that root because Prisma PHP attaches serialized props and a stable `pp-component` attribute to that root
|
|
139
|
-
- do not
|
|
136
|
+
- when building PulsePoint inside `index.php` or `layout.php`, read `layouts-and-pages.md` together with `pulsepoint.md`, use one parent route root, put `pp-component` on that root, and keep the `<script>` inside it as the last child
|
|
137
|
+
- when importing a PHP partial with `ImportComponent`, require exactly one root element and keep any partial-local `<script>` inside that root because Prisma PHP attaches serialized props and a stable `pp-component` attribute to that imported root
|
|
138
|
+
- do not leave a PulsePoint `<script>` as a sibling outside the route root
|
|
139
|
+
- do not manually add `pp-component` inside imported partial source; Prisma PHP injects it there
|
|
140
140
|
- when building backend validation logic, read `validator.md` first, then use the route-specific docs for the request entry point
|
|
141
141
|
- when using `pp.fetchFunction(...)`, expose the PHP function explicitly with `#[Exposed]`
|
|
142
142
|
- when changing feature flags, update `prisma-php.json` first, then follow the documented update workflow
|
|
@@ -61,20 +61,21 @@ Only shift to a more PHP-only pattern when the user explicitly asks for it or wh
|
|
|
61
61
|
When `index.php` or `layout.php` includes PulsePoint, keep the file in this order:
|
|
62
62
|
|
|
63
63
|
1. PHP
|
|
64
|
-
2. HTML
|
|
65
|
-
3.
|
|
64
|
+
2. one parent HTML element
|
|
65
|
+
3. keep the PulsePoint `<script>` as the last child inside that root element
|
|
66
66
|
|
|
67
67
|
In practice, that means:
|
|
68
68
|
|
|
69
69
|
- put imports, server-side queries, request access, helper functions, and exposed PHP functions in the top PHP block
|
|
70
|
-
- render the route markup after the PHP block
|
|
71
|
-
-
|
|
70
|
+
- render the route markup inside one parent element after the PHP block
|
|
71
|
+
- when PulsePoint is present, put `pp-component` on that single route or layout root
|
|
72
|
+
- place one PulsePoint `<script>` block at the bottom of that same root element
|
|
72
73
|
|
|
73
74
|
For the HTML portion, keep a **single parent HTML tag** around the route content. Treat that root as the page or layout boundary instead of as a throwaway wrapper.
|
|
74
75
|
|
|
75
76
|
- 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 boundary.
|
|
76
77
|
- 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
|
-
- 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;
|
|
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; ?>`, and keep any PulsePoint `<script>` inside that same wrapper when PulsePoint behavior is present.
|
|
78
79
|
|
|
79
80
|
### Important distinction: route files vs imported partials
|
|
80
81
|
|
|
@@ -84,7 +85,7 @@ For **normal route files** such as `src/app/index.php` and nested `layout.php`,
|
|
|
84
85
|
|
|
85
86
|
1. PHP
|
|
86
87
|
2. one parent HTML element for the rendered route UI
|
|
87
|
-
3. one `<script>` block
|
|
88
|
+
3. when PulsePoint is present, keep one `<script>` block as the last child inside that same root element
|
|
88
89
|
|
|
89
90
|
Correct route-file pattern:
|
|
90
91
|
|
|
@@ -99,20 +100,19 @@ MainLayout::$title = 'Todos';
|
|
|
99
100
|
MainLayout::$description = 'Track tasks and view the current item count.';
|
|
100
101
|
?>
|
|
101
102
|
|
|
102
|
-
<section
|
|
103
|
+
<section pp-component="todos-page">
|
|
103
104
|
<h1>Todos</h1>
|
|
104
105
|
<p>Count: {count}</p>
|
|
106
|
+
<script>
|
|
107
|
+
const [count, setCount] = pp.state(0);
|
|
108
|
+
</script>
|
|
105
109
|
</section>
|
|
106
|
-
|
|
107
|
-
<script>
|
|
108
|
-
const [count, setCount] = pp.state(0);
|
|
109
|
-
</script>
|
|
110
110
|
```
|
|
111
111
|
|
|
112
112
|
That is the default structure for a PulsePoint-aware route page.
|
|
113
113
|
Use `MainLayout::$title` and `MainLayout::$description` for page metadata, and keep a separate heading variable only when the visible heading text needs to differ.
|
|
114
114
|
|
|
115
|
-
By contrast, **imported partials rendered with `ImportComponent::render(...)`
|
|
115
|
+
By contrast, **imported partials rendered with `ImportComponent::render(...)` keep the same single-root requirement but a different `pp-component` responsibility**: the imported file must emit exactly **one root element**, any component-local `<script>` must live **inside** that root element, and Prisma PHP injects `pp-component` on that imported root automatically. Read `components.md` for that pattern.
|
|
116
116
|
|
|
117
117
|
## Creating a page
|
|
118
118
|
|
|
@@ -167,7 +167,7 @@ use PP\MainLayout;
|
|
|
167
167
|
```
|
|
168
168
|
|
|
169
169
|
The root layout is defined at the top level of your app directory and wraps the content for the entire application.
|
|
170
|
-
If this root layout uses PulsePoint, keep the same file order: PHP first, then the document HTML,
|
|
170
|
+
If this root layout uses PulsePoint, keep the same file order: PHP first, then the document HTML, and keep any route-local `<script>` inside a single wrapper near the end of `<body>`.
|
|
171
171
|
|
|
172
172
|
## Creating a nested route
|
|
173
173
|
|
|
@@ -385,10 +385,10 @@ echo json_encode($users);
|
|
|
385
385
|
- If a user asks for an API route or direct handler, prefer `route.php`.
|
|
386
386
|
- The root layout is required and should contain the global HTML document structure.
|
|
387
387
|
- Only the root layout should contain `<html>` and `<body>` tags.
|
|
388
|
-
- When `index.php` or `layout.php` uses PulsePoint, structure the file as PHP, then HTML,
|
|
388
|
+
- When `index.php` or `layout.php` uses PulsePoint, structure the file as PHP first, then one root HTML element, and keep the `<script>` inside that root as its last child.
|
|
389
389
|
- `index.php` and nested `layout.php` should render one parent HTML element; only the root layout should contain `<html>` and `<body>`.
|
|
390
390
|
- Layouts can be nested by placing `layout.php` files inside route folders.
|
|
391
391
|
- Dynamic route parameters are available through `Request::$dynamicParams`.
|
|
392
392
|
- Route groups let you organize routes and assign different layouts without affecting the URL.
|
|
393
393
|
- Private folders help you colocate implementation files without making them routable.
|
|
394
|
-
- Imported partials rendered through `ImportComponent` follow a different
|
|
394
|
+
- Imported partials rendered through `ImportComponent` follow the same single-root rule but a different boundary rule: keep the component-local `<script>` inside that root and let Prisma PHP inject `pp-component` there.
|
|
@@ -114,7 +114,7 @@ For normal PulsePoint-aware route files, use:
|
|
|
114
114
|
|
|
115
115
|
1. PHP
|
|
116
116
|
2. one parent HTML element
|
|
117
|
-
3. one `<script>` block
|
|
117
|
+
3. when PulsePoint is present, keep one `<script>` block as the last child inside that same root element
|
|
118
118
|
|
|
119
119
|
Example:
|
|
120
120
|
|
|
@@ -127,14 +127,13 @@ MainLayout::$title = 'Dashboard';
|
|
|
127
127
|
MainLayout::$description = 'View dashboard metrics and current activity.';
|
|
128
128
|
?>
|
|
129
129
|
|
|
130
|
-
<section
|
|
130
|
+
<section pp-component="dashboard-page">
|
|
131
131
|
<h1>Dashboard</h1>
|
|
132
132
|
<p>Count: {count}</p>
|
|
133
|
+
<script>
|
|
134
|
+
const [count, setCount] = pp.state(0);
|
|
135
|
+
</script>
|
|
133
136
|
</section>
|
|
134
|
-
|
|
135
|
-
<script>
|
|
136
|
-
const [count, setCount] = pp.state(0);
|
|
137
|
-
</script>
|
|
138
137
|
```
|
|
139
138
|
|
|
140
139
|
That is the default route-file structure for a full-stack page.
|
|
@@ -144,18 +143,18 @@ For document metadata, set `MainLayout::$title` and `MainLayout::$description` i
|
|
|
144
143
|
|
|
145
144
|
An imported partial rendered through `ImportComponent::render(...)` is **not** a normal route file.
|
|
146
145
|
|
|
147
|
-
For imported partials, the file must emit exactly **one root element**, and if the partial contains a `<script>`, that script must live **inside** that root element.
|
|
146
|
+
For imported partials, the file must emit exactly **one root element**, and if the partial contains a `<script>`, that script must live **inside** that root element. Prisma PHP injects `pp-component` for that imported boundary automatically.
|
|
148
147
|
|
|
149
148
|
Example:
|
|
150
149
|
|
|
151
150
|
```php filename="src/app/inc/SearchBox.php"
|
|
152
151
|
<?php
|
|
153
152
|
|
|
154
|
-
|
|
153
|
+
// PHP Code
|
|
155
154
|
?>
|
|
156
155
|
|
|
157
|
-
<div
|
|
158
|
-
<h2
|
|
156
|
+
<div pp-component="search-box">
|
|
157
|
+
<h2>Search</h2>
|
|
159
158
|
<input value="{query}" />
|
|
160
159
|
<script>
|
|
161
160
|
console.log('Search component ready');
|
package/dist/docs/pulsepoint.md
CHANGED
|
@@ -2,30 +2,48 @@
|
|
|
2
2
|
|
|
3
3
|
## Purpose
|
|
4
4
|
|
|
5
|
-
This file describes the PulsePoint runtime that is actually implemented in the current TypeScript source of this repo.
|
|
5
|
+
This file describes the PulsePoint runtime that is actually implemented in the current TypeScript source of this repo. Treat it as the working contract for AI-generated code.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
Do not assume React, Vue, Svelte, JSX, or older PulsePoint docs.
|
|
8
8
|
|
|
9
|
-
##
|
|
9
|
+
## Runtime shape
|
|
10
10
|
|
|
11
|
-
PulsePoint is a browser-side component runtime. It renders HTML
|
|
11
|
+
PulsePoint is a browser-side component runtime. It executes one component script per root, renders HTML from the component scope, morphs the DOM in place, binds native event handlers, restores focus, and runs hook effects.
|
|
12
12
|
|
|
13
13
|
A component root is any element with `pp-component`.
|
|
14
14
|
|
|
15
|
+
Important:
|
|
16
|
+
|
|
17
|
+
- In the current source, `pp-component` behaves like an instance id, not a reusable component class name.
|
|
18
|
+
- State, scope, template caching, parent tracking, and instance lookup are all keyed by that attribute.
|
|
19
|
+
- Generate unique `pp-component` values per mounted instance. Reusing the same value for multiple live roots can replace or destroy the previous instance.
|
|
20
|
+
|
|
15
21
|
## Component roots and scripts
|
|
16
22
|
|
|
17
23
|
- Each component root may contain one inline `<script>` block for component logic.
|
|
24
|
+
- The runtime looks for that script inside the current root and ignores scripts inside nested component roots.
|
|
25
|
+
- Plain `<script>` is not the component-script contract in the current TypeScript source.
|
|
18
26
|
- The script is plain JavaScript, not a module. Do not use `import` or `export` inside it.
|
|
19
|
-
-
|
|
20
|
-
- Top-level `const`, `let`, `function`, and supported destructuring patterns become template bindings.
|
|
27
|
+
- Do not manually `return { ... }`. The runtime auto-exports supported top-level bindings.
|
|
21
28
|
- `pp.props` contains the current prop bag for the component.
|
|
22
|
-
- `children` contains the
|
|
23
|
-
|
|
29
|
+
- `children` is injected into props and contains the root's initial inner HTML.
|
|
30
|
+
|
|
31
|
+
Bindings that are exported to the template:
|
|
32
|
+
|
|
33
|
+
- Top-level function declarations.
|
|
34
|
+
- Top-level identifier declarations such as `const title = ...`.
|
|
35
|
+
- Top-level object destructuring identifiers such as `const { title, subtitle } = pp.props`.
|
|
36
|
+
- Top-level array destructuring from `pp.state(...)`, such as `const [count, setCount] = pp.state(0)`.
|
|
37
|
+
|
|
38
|
+
Bindings that should not be assumed:
|
|
39
|
+
|
|
40
|
+
- Generic top-level array destructuring is not auto-exported unless it comes from `pp.state(...)`.
|
|
41
|
+
- Nested declarations inside functions, conditions, loops, and callbacks are not auto-exported.
|
|
24
42
|
|
|
25
43
|
Example:
|
|
26
44
|
|
|
27
45
|
```html
|
|
28
|
-
<div pp-component="counter-card">
|
|
46
|
+
<div pp-component="counter-card-1">
|
|
29
47
|
<h2>{title}</h2>
|
|
30
48
|
<p>Count: {count}</p>
|
|
31
49
|
<button onclick="setCount(count + 1)">Increment</button>
|
|
@@ -33,7 +51,6 @@ Example:
|
|
|
33
51
|
<script>
|
|
34
52
|
const { title } = pp.props;
|
|
35
53
|
const [count, setCount] = pp.state(0);
|
|
36
|
-
const doubled = count * 2;
|
|
37
54
|
|
|
38
55
|
function reset() {
|
|
39
56
|
setCount(0);
|
|
@@ -42,32 +59,104 @@ Example:
|
|
|
42
59
|
</div>
|
|
43
60
|
```
|
|
44
61
|
|
|
45
|
-
## Hooks
|
|
62
|
+
## Hooks and runtime API
|
|
63
|
+
|
|
64
|
+
Hooks exposed inside component scripts through `pp`:
|
|
46
65
|
|
|
47
66
|
- `pp.state(initial)` returns `[value, setValue]`.
|
|
48
|
-
- `pp.effect(callback, deps?)` runs after render and
|
|
49
|
-
- `pp.layoutEffect(callback, deps?)` runs synchronously after DOM mutation.
|
|
67
|
+
- `pp.effect(callback, deps?)` runs after render and may return a cleanup function.
|
|
68
|
+
- `pp.layoutEffect(callback, deps?)` runs synchronously after DOM mutation and may return a cleanup function.
|
|
50
69
|
- `pp.ref(initialValue?)` returns `{ current }`.
|
|
51
70
|
- `pp.memo(factory, deps)` memoizes a computed value.
|
|
52
71
|
- `pp.callback(callback, deps)` memoizes a function.
|
|
53
72
|
- `pp.reducer(reducer, initialState)` returns `[state, dispatch]`.
|
|
73
|
+
- `pp.context(token)` resolves a provided context value from ancestor components.
|
|
74
|
+
- `pp.provideContext(token, value)` provides a context value to descendant components.
|
|
54
75
|
- `pp.portal(ref, target?)` portals a ref-managed element into a target.
|
|
55
76
|
- `pp.props` exposes the current props.
|
|
56
77
|
|
|
78
|
+
Runtime helpers exposed through the global `pp` utility object:
|
|
79
|
+
|
|
80
|
+
- `pp.createContext(defaultValue)` creates a context token.
|
|
81
|
+
- `pp.mount()` bootstraps the runtime.
|
|
82
|
+
- `pp.redirect(url)` performs SPA-aware navigation when enabled.
|
|
83
|
+
- `pp.fetchFunction(name, data?, optionsOrAbort?)` performs the runtime RPC/fetch bridge.
|
|
84
|
+
- `pp.enablePerf()` enables render timing collection.
|
|
85
|
+
- `pp.disablePerf()` disables render timing collection.
|
|
86
|
+
- `pp.getPerfStats()` returns collected render timings.
|
|
87
|
+
- `pp.resetPerfStats()` clears collected render timings.
|
|
88
|
+
|
|
57
89
|
Notes:
|
|
58
90
|
|
|
59
91
|
- `pp.state` setters accept either a value or an updater function.
|
|
60
|
-
- `pp.effect` and `pp.layoutEffect`
|
|
92
|
+
- `pp.effect` and `pp.layoutEffect` behave like cleanup-style hooks. Do not rely on returned promises being awaited.
|
|
93
|
+
- `pp.mount()` should be called once. Do not manually instantiate `Component`.
|
|
61
94
|
- Keep template-facing bindings at the top level so the AST-based exporter can see them.
|
|
62
95
|
|
|
96
|
+
## Context
|
|
97
|
+
|
|
98
|
+
Context is implemented in the current source. Do not document it as unsupported.
|
|
99
|
+
|
|
100
|
+
How it works:
|
|
101
|
+
|
|
102
|
+
- Create a token with `pp.createContext(defaultValue)`.
|
|
103
|
+
- Provide a value from a component with `pp.provideContext(token, value)`.
|
|
104
|
+
- Read it in a descendant component with `pp.context(token)`.
|
|
105
|
+
- Resolution walks the component parent chain and falls back to `token.defaultValue` when no provider exists.
|
|
106
|
+
- Descendant consumers are refreshed when a provider value changes or the provider is destroyed.
|
|
107
|
+
|
|
108
|
+
Important:
|
|
109
|
+
|
|
110
|
+
- The same token object must be shared between provider and consumer.
|
|
111
|
+
- In this runtime, context is component-level, not directive-based. There is no `pp-context` attribute.
|
|
112
|
+
- If components are isolated in separate script blocks, pass the token through props or store it in a shared outer scope.
|
|
113
|
+
|
|
114
|
+
Example:
|
|
115
|
+
|
|
116
|
+
```html
|
|
117
|
+
<section pp-component="theme-provider-1">
|
|
118
|
+
<div pp-component="theme-label-1" theme-token="{ThemeContext}">
|
|
119
|
+
<p>{theme}</p>
|
|
120
|
+
|
|
121
|
+
<script>
|
|
122
|
+
const { themeToken } = pp.props;
|
|
123
|
+
const theme = pp.context(themeToken);
|
|
124
|
+
</script>
|
|
125
|
+
</div>
|
|
126
|
+
|
|
127
|
+
<script>
|
|
128
|
+
const ThemeContext = pp.createContext("light");
|
|
129
|
+
const [theme] = pp.state("dark");
|
|
130
|
+
|
|
131
|
+
pp.provideContext(ThemeContext, theme);
|
|
132
|
+
</script>
|
|
133
|
+
</section>
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## Props and nested components
|
|
137
|
+
|
|
138
|
+
- Child component props are derived from DOM attributes.
|
|
139
|
+
- Attribute names are converted from kebab-case to camelCase for the prop bag.
|
|
140
|
+
- Pure prop expressions such as `title="{pageTitle}"` are evaluated in the parent scope.
|
|
141
|
+
- Mixed strings such as `class="card {isActive ? 'active' : ''}"` are interpolated in the parent scope.
|
|
142
|
+
- Empty attributes become boolean `true` props.
|
|
143
|
+
|
|
144
|
+
Nested components:
|
|
145
|
+
|
|
146
|
+
- Nested `pp-component` roots are treated as component boundaries during DOM reconciliation.
|
|
147
|
+
- Nested roots with their own `<script>` are also masked during parent template compilation.
|
|
148
|
+
- Scriptless nested component roots are bootstrapped, but they 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>`.
|
|
149
|
+
|
|
63
150
|
## Template expressions and attributes
|
|
64
151
|
|
|
65
152
|
- Use `{expression}` in text nodes and attribute values.
|
|
66
153
|
- Pure bindings like `value="{count}"` are evaluated as expressions.
|
|
67
154
|
- Mixed text like `class="card {isActive ? 'active' : ''}"` is supported.
|
|
68
|
-
-
|
|
155
|
+
- Arrays in template expressions are joined without commas.
|
|
156
|
+
- `null`, `undefined`, and boolean expression results render as an empty string.
|
|
157
|
+
- Boolean attributes are normalized for the supported attribute names implemented by the runtime.
|
|
69
158
|
- Use `pp-spread="{...attrs}"` to spread one object expression into attributes.
|
|
70
|
-
- Use plain `key` for keyed diffing. `pp-key` is not implemented
|
|
159
|
+
- Use plain `key` for keyed diffing. `pp-key` is not implemented.
|
|
71
160
|
|
|
72
161
|
Example:
|
|
73
162
|
|
|
@@ -79,6 +168,7 @@ Example:
|
|
|
79
168
|
class: "btn btn-primary",
|
|
80
169
|
"aria-label": "save",
|
|
81
170
|
};
|
|
171
|
+
|
|
82
172
|
const isLoading = false;
|
|
83
173
|
</script>
|
|
84
174
|
```
|
|
@@ -86,15 +176,16 @@ Example:
|
|
|
86
176
|
## Refs
|
|
87
177
|
|
|
88
178
|
- Use `pp-ref="nameInput"` when the value is a ref object or callback already in scope.
|
|
89
|
-
- Use `pp-ref="{registerRef(id)}"` when you
|
|
179
|
+
- Use `pp-ref="{registerRef(id)}"` when you want the compiled template to capture a dynamic ref expression.
|
|
90
180
|
- `pp.ref(null)` is the normal way to create a ref object.
|
|
181
|
+
- Callback refs and `{ current }` refs are both supported.
|
|
91
182
|
- The runtime generates `data-pp-ref` internally. Do not author it.
|
|
92
183
|
- Do not author `pp-event-owner`, `pp-owner`, or `pp-dynamic-*` attributes by hand.
|
|
93
184
|
|
|
94
185
|
Example:
|
|
95
186
|
|
|
96
187
|
```html
|
|
97
|
-
<div pp-component="focus-box">
|
|
188
|
+
<div pp-component="focus-box-1">
|
|
98
189
|
<input pp-ref="nameInput" />
|
|
99
190
|
<button onclick="nameInput.current?.focus()">Focus</button>
|
|
100
191
|
|
|
@@ -104,14 +195,15 @@ Example:
|
|
|
104
195
|
</div>
|
|
105
196
|
```
|
|
106
197
|
|
|
107
|
-
## Lists and
|
|
198
|
+
## Lists and keyed diffing
|
|
108
199
|
|
|
109
200
|
- Use `pp-for` only on `<template>`.
|
|
110
201
|
- Supported forms are `item in items` and `(item, index) in items`.
|
|
111
202
|
- The collection must be an array. Non-arrays are treated as empty lists.
|
|
112
203
|
- Loop content can contain interpolations, events, refs, and nested components.
|
|
204
|
+
- Event handlers inside loops are rewritten so loop variables still resolve after rerender.
|
|
113
205
|
- Use stable unique `key` values on repeated elements.
|
|
114
|
-
- Duplicate keys
|
|
206
|
+
- Duplicate keys trigger warnings and reduce diff quality.
|
|
115
207
|
|
|
116
208
|
Example:
|
|
117
209
|
|
|
@@ -154,34 +246,30 @@ Example:
|
|
|
154
246
|
- `pp-loading-transition` accepts JSON with `fadeIn` and `fadeOut` timing values.
|
|
155
247
|
- `pp-reset-scroll="true"` resets scroll positions during navigation.
|
|
156
248
|
|
|
157
|
-
The runtime also exposes navigation and bridge helpers through `pp`:
|
|
158
|
-
|
|
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()`
|
|
166
|
-
|
|
167
249
|
Notes:
|
|
168
250
|
|
|
169
|
-
- `pp.redirect()` uses SPA navigation when
|
|
170
|
-
- `pp.fetchFunction()`
|
|
171
|
-
-
|
|
251
|
+
- `pp.redirect()` uses SPA navigation when enabled and the URL is same-origin. Otherwise it falls back to normal navigation.
|
|
252
|
+
- `pp.fetchFunction()` posts to the current route and supports aborting previous requests, upload progress, and streamed responses when available.
|
|
253
|
+
- `pp.mount()` bootstraps every `[pp-component]` it finds, so AI-generated code should call it once and let the runtime manage nested components.
|
|
172
254
|
|
|
173
|
-
##
|
|
255
|
+
## AI rules
|
|
174
256
|
|
|
175
|
-
|
|
257
|
+
Use these rules when generating or editing PulsePoint code:
|
|
176
258
|
|
|
177
|
-
-
|
|
178
|
-
-
|
|
179
|
-
-
|
|
180
|
-
-
|
|
259
|
+
- Use the current TypeScript source when it is available.
|
|
260
|
+
- Generate unique `pp-component` values per live instance.
|
|
261
|
+
- Use only `<script>` for component logic.
|
|
262
|
+
- Keep template-facing variables at top level.
|
|
263
|
+
- Use `pp.createContext`, `pp.context`, and `pp.provideContext` for context. Do not invent `pp-context`.
|
|
264
|
+
- Keep `pp-for` on `<template>` and use plain `key`.
|
|
265
|
+
- Use native `on*` attributes, not framework-specific event syntax.
|
|
266
|
+
- Use refs and portals only through the implemented `pp` APIs.
|
|
267
|
+
- Avoid generating internal runtime attributes.
|
|
268
|
+
- Avoid scriptless nested components when the child template contains its own bindings.
|
|
181
269
|
|
|
182
270
|
## What to avoid
|
|
183
271
|
|
|
184
|
-
Do not generate these unless the current source explicitly
|
|
272
|
+
Do not generate these unless the current source explicitly adds support:
|
|
185
273
|
|
|
186
274
|
- `pp-context`
|
|
187
275
|
- `pp-key`
|
|
@@ -192,20 +280,17 @@ Do not generate these unless the current source explicitly supports them:
|
|
|
192
280
|
- `pp-dynamic-meta`
|
|
193
281
|
- `pp-dynamic-link`
|
|
194
282
|
- plain `<script>` for component logic inside a component root
|
|
195
|
-
- React, Vue, Svelte, or JSX-
|
|
283
|
+
- React, Vue, Svelte, or JSX-only patterns that are not implemented here
|
|
196
284
|
- made-up hooks, directives, or globals not present in the current TypeScript source
|
|
197
285
|
|
|
198
|
-
##
|
|
286
|
+
## Review notes
|
|
199
287
|
|
|
200
|
-
|
|
288
|
+
These are current runtime caveats that matter for authors and AI tools:
|
|
201
289
|
|
|
202
|
-
-
|
|
203
|
-
-
|
|
204
|
-
-
|
|
205
|
-
-
|
|
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.
|
|
290
|
+
- `pp-component` is currently used as the registry key for instances, state, parent tracking, and templates. Treat it as unique per mounted root.
|
|
291
|
+
- Nested roots without their own `<script>` are not fully isolated during parent template compilation.
|
|
292
|
+
- `pp.mount()` boots every root in the document. Call it once and avoid manual component construction.
|
|
293
|
+
- `pp.effect` and `pp.layoutEffect` are cleanup-style hooks. Their callbacks are not promise-aware.
|
|
209
294
|
|
|
210
295
|
## Final reminder
|
|
211
296
|
|