@techninja/clearstack 0.3.16 → 0.3.19
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/docs/BUILD_LOG.md +107 -0
- package/docs/CONVENTIONS.md +45 -5
- package/docs/JSDOC_TYPING.md +24 -3
- package/docs/PLATFORM_STACKING.md +486 -0
- package/docs/QUICKSTART.md +2 -2
- package/docs/STATE_AND_ROUTING.md +54 -49
- package/docs/TESTING.md +52 -19
- package/lib/check.js +26 -50
- package/lib/init.js +5 -0
- package/lib/platform-files.js +113 -0
- package/lib/platform.js +52 -0
- package/lib/spec-config.js +55 -0
- package/lib/spec-utils.js +5 -2
- package/lib/update.js +8 -0
- package/package.json +1 -1
- package/templates/fullstack/src/store/realtimeSync.js +4 -4
- package/templates/shared/.configs/eslint.config.js +1 -0
- package/templates/shared/.configs/web-test-runner.config.js +28 -1
- package/templates/shared/docs/clearstack/CONVENTIONS.md +45 -5
- package/templates/shared/docs/clearstack/JSDOC_TYPING.md +24 -3
- package/templates/shared/docs/clearstack/QUICKSTART.md +2 -2
- package/templates/shared/docs/clearstack/STATE_AND_ROUTING.md +54 -49
- package/templates/shared/docs/clearstack/TESTING.md +52 -19
- package/templates/shared/docs/clearstack/BUILD_LOG.md +0 -252
|
@@ -0,0 +1,486 @@
|
|
|
1
|
+
# Platform Stacking
|
|
2
|
+
|
|
3
|
+
## How Clearstack Projects Become Scaffoldable Platforms
|
|
4
|
+
|
|
5
|
+
> A Clearstack project can declare itself as a **platform** — a reusable
|
|
6
|
+
> foundation that scaffolds child projects with vendor files, config schemas,
|
|
7
|
+
> and override layers. This spec defines the contract.
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## The Problem
|
|
12
|
+
|
|
13
|
+
Clearstack scaffolds vanilla projects. But some Clearstack projects are
|
|
14
|
+
themselves platforms — they provide components, store models, API handlers,
|
|
15
|
+
and build scripts that other projects consume and customize.
|
|
16
|
+
|
|
17
|
+
Without a stacking API, each platform reinvents its own CLI, its own
|
|
18
|
+
vendor sync, its own override conventions, and its own update-without-
|
|
19
|
+
clobbering logic. That's the same problem Clearstack already solved for
|
|
20
|
+
itself.
|
|
21
|
+
|
|
22
|
+
## The Stack
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
Clearstack (spec + CLI)
|
|
26
|
+
└── Platform (Clearstack project that declares itself scaffoldable)
|
|
27
|
+
└── Project (consumes the platform, overrides what it needs)
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Concrete example:
|
|
31
|
+
|
|
32
|
+
```
|
|
33
|
+
Clearstack v0.3.17
|
|
34
|
+
└── StatiCart v1.x (e-commerce platform)
|
|
35
|
+
└── "My Coffee Shop" (store built on StatiCart)
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Each layer owns different things and has different update rules.
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## 1. Platform Manifest
|
|
43
|
+
|
|
44
|
+
A Clearstack project becomes a platform by adding a `platform` key to its
|
|
45
|
+
`package.json`. This is the only requirement.
|
|
46
|
+
|
|
47
|
+
```json
|
|
48
|
+
{
|
|
49
|
+
"name": "@techninja/staticart",
|
|
50
|
+
"version": "1.0.0",
|
|
51
|
+
"clearstack": {
|
|
52
|
+
"platform": {
|
|
53
|
+
"prefix": "staticart",
|
|
54
|
+
"vendorDir": "src/vendor/staticart",
|
|
55
|
+
"configFile": "staticart.config.json",
|
|
56
|
+
"configSchema": "staticart.schema.json",
|
|
57
|
+
"templates": "templates/",
|
|
58
|
+
"vendor": "vendor/",
|
|
59
|
+
"docs": "docs/staticart/",
|
|
60
|
+
"scripts": "scripts/",
|
|
61
|
+
"api": "api/"
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Manifest Fields
|
|
68
|
+
|
|
69
|
+
| Field | Required | Purpose |
|
|
70
|
+
| -------------- | -------- | ---------------------------------------------------------------- |
|
|
71
|
+
| `prefix` | Yes | Import map prefix (`#staticart/`). Must be unique per platform. |
|
|
72
|
+
| `vendorDir` | Yes | Where platform files land in the child project. Gitignored. |
|
|
73
|
+
| `configFile` | Yes | Project-level config file name. Never overwritten on update. |
|
|
74
|
+
| `configSchema` | No | JSON Schema for the config file. Used for validation. |
|
|
75
|
+
| `templates` | Yes | Directory in the npm package containing scaffold templates. |
|
|
76
|
+
| `vendor` | Yes | Directory in the npm package containing vendorable source files. |
|
|
77
|
+
| `docs` | No | Platform docs synced to child project on update. |
|
|
78
|
+
| `scripts` | No | Build/admin scripts copied on init, skipped on update. |
|
|
79
|
+
| `api` | No | API handler templates copied on init, skipped on update. |
|
|
80
|
+
|
|
81
|
+
### The `prefix` Contract
|
|
82
|
+
|
|
83
|
+
The platform prefix becomes an import map namespace in the child project.
|
|
84
|
+
It follows the same `#prefix/` convention Clearstack uses for `#store/`,
|
|
85
|
+
`#atoms/`, etc.
|
|
86
|
+
|
|
87
|
+
```html
|
|
88
|
+
<script type="importmap">
|
|
89
|
+
{
|
|
90
|
+
"imports": {
|
|
91
|
+
"#staticart/": "/vendor/staticart/",
|
|
92
|
+
"#store/": "/store/",
|
|
93
|
+
"#atoms/": "/components/atoms/"
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
</script>
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
The child project can override any platform module by remapping a specific
|
|
100
|
+
path before the wildcard:
|
|
101
|
+
|
|
102
|
+
```html
|
|
103
|
+
<script type="importmap">
|
|
104
|
+
{
|
|
105
|
+
"imports": {
|
|
106
|
+
"#staticart/product-card": "/components/my-product-card.js",
|
|
107
|
+
"#staticart/": "/vendor/staticart/"
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
</script>
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Import map specificity rules (longer prefix wins) make this work natively.
|
|
114
|
+
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
## 2. File Ownership Model
|
|
118
|
+
|
|
119
|
+
Every file in a stacked project belongs to exactly one layer. The layer
|
|
120
|
+
determines who creates it, who updates it, and who must never touch it.
|
|
121
|
+
|
|
122
|
+
### Ownership Table
|
|
123
|
+
|
|
124
|
+
| Owner | Creates | Updates | Examples |
|
|
125
|
+
| -------------- | ---------------------- | ----------------------------- | -------------------------------------------------------- |
|
|
126
|
+
| **Clearstack** | `init` | `update` (always) | `docs/clearstack/`, `.configs/` |
|
|
127
|
+
| **Platform** | `init` | `update` (vendor + docs only) | `src/vendor/<prefix>/`, `docs/<prefix>/` |
|
|
128
|
+
| **Project** | `init` (from template) | Never (project owns it) | `src/components/`, config file, `tokens.css`, data files |
|
|
129
|
+
|
|
130
|
+
### The Three Update Behaviors
|
|
131
|
+
|
|
132
|
+
**Always overwrite:** Spec docs and vendor files. These are the platform's
|
|
133
|
+
source of truth. The child project must not edit them directly.
|
|
134
|
+
|
|
135
|
+
- `docs/clearstack/*.md`
|
|
136
|
+
- `docs/<prefix>/*.md`
|
|
137
|
+
- `src/vendor/<prefix>/`
|
|
138
|
+
|
|
139
|
+
**Create once, never overwrite:** Templates that the project customizes.
|
|
140
|
+
On `update`, these are skipped unless `--force` is passed.
|
|
141
|
+
|
|
142
|
+
- `.configs/*`
|
|
143
|
+
- `scripts/*`
|
|
144
|
+
- `api/` handlers
|
|
145
|
+
- `src/index.html`
|
|
146
|
+
|
|
147
|
+
**Never touch:** Project-owned files. No init or update command writes
|
|
148
|
+
to these after initial creation.
|
|
149
|
+
|
|
150
|
+
- Config file (`staticart.config.json`)
|
|
151
|
+
- `src/styles/tokens.css`
|
|
152
|
+
- `src/data/`
|
|
153
|
+
- `src/locales/overrides*.json`
|
|
154
|
+
- `src/components/` (project overrides)
|
|
155
|
+
|
|
156
|
+
---
|
|
157
|
+
|
|
158
|
+
## 3. CLI Integration
|
|
159
|
+
|
|
160
|
+
The Clearstack CLI gains platform awareness. No new top-level commands —
|
|
161
|
+
platforms hook into the existing `init` and `update` flow.
|
|
162
|
+
|
|
163
|
+
### `clearstack init`
|
|
164
|
+
|
|
165
|
+
Current behavior (unchanged):
|
|
166
|
+
|
|
167
|
+
1. Scaffold Clearstack project structure
|
|
168
|
+
2. Create `.configs/`, `docs/clearstack/`, `scripts/`, `src/`
|
|
169
|
+
|
|
170
|
+
New behavior when a platform is detected in `devDependencies`: 3. Read the platform's manifest from its `package.json` 4. Copy `templates/` → project root (respecting ownership rules) 5. Vendor `vendor/` → `src/vendor/<prefix>/` 6. Copy `docs/` → `docs/<prefix>/` 7. Copy `scripts/` → `scripts/` (skip existing) 8. Copy `api/` → `api/` (skip existing) 9. Generate import map entries for `#<prefix>/` 10. Create config file from template (if not exists)
|
|
171
|
+
|
|
172
|
+
### `clearstack update`
|
|
173
|
+
|
|
174
|
+
Current behavior (unchanged):
|
|
175
|
+
|
|
176
|
+
1. Sync `docs/clearstack/` (always overwrite)
|
|
177
|
+
2. Sync `.configs/` (skip existing, `--force` to overwrite)
|
|
178
|
+
|
|
179
|
+
New behavior when a platform is detected: 3. Re-vendor `vendor/` → `src/vendor/<prefix>/` (always overwrite) 4. Sync `docs/<prefix>/` (always overwrite) 5. Skip everything else (config, templates, scripts, api)
|
|
180
|
+
|
|
181
|
+
### Platform CLI Passthrough
|
|
182
|
+
|
|
183
|
+
A platform can also provide its own CLI commands via a `bin` entry. The
|
|
184
|
+
Clearstack CLI doesn't need to know about these — they're standard npm
|
|
185
|
+
bin scripts.
|
|
186
|
+
|
|
187
|
+
```json
|
|
188
|
+
{
|
|
189
|
+
"bin": {
|
|
190
|
+
"staticart": "./bin/cli.js"
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
The platform CLI can call Clearstack's API programmatically for shared
|
|
196
|
+
operations (vendoring, doc sync) rather than reimplementing them.
|
|
197
|
+
|
|
198
|
+
---
|
|
199
|
+
|
|
200
|
+
## 4. Vendor Sync
|
|
201
|
+
|
|
202
|
+
Platform vendor files are the equivalent of Clearstack's `src/vendor/hybrids/`
|
|
203
|
+
pattern — source files copied from `node_modules` into the project so the
|
|
204
|
+
browser can serve them directly.
|
|
205
|
+
|
|
206
|
+
### How It Works
|
|
207
|
+
|
|
208
|
+
The platform's `vendor/` directory in the npm package contains the source
|
|
209
|
+
files that child projects consume. On `init` and `update`, these are copied
|
|
210
|
+
to `src/vendor/<prefix>/`.
|
|
211
|
+
|
|
212
|
+
```
|
|
213
|
+
node_modules/@techninja/staticart/vendor/
|
|
214
|
+
├── components/
|
|
215
|
+
│ ├── atoms/
|
|
216
|
+
│ ├── molecules/
|
|
217
|
+
│ └── organisms/
|
|
218
|
+
├── store/
|
|
219
|
+
├── utils/
|
|
220
|
+
└── styles/
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
Becomes:
|
|
224
|
+
|
|
225
|
+
```
|
|
226
|
+
src/vendor/staticart/
|
|
227
|
+
├── components/
|
|
228
|
+
├── store/
|
|
229
|
+
├── utils/
|
|
230
|
+
└── styles/
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
### Vendor Directory Rules
|
|
234
|
+
|
|
235
|
+
- Always gitignored. Regenerated from the installed package version.
|
|
236
|
+
- Always overwritten on `update`. The platform owns these files.
|
|
237
|
+
- The child project never edits files in `src/vendor/<prefix>/`.
|
|
238
|
+
- To customize a vendored component, override it via the import map.
|
|
239
|
+
|
|
240
|
+
### `postinstall` Hook
|
|
241
|
+
|
|
242
|
+
The platform should provide a setup script that runs on `npm install`,
|
|
243
|
+
similar to how Clearstack's `vendor-deps.js` works:
|
|
244
|
+
|
|
245
|
+
```javascript
|
|
246
|
+
// In the platform's postinstall or the child project's setup.js
|
|
247
|
+
const PLATFORM_VENDOR = 'node_modules/@techninja/staticart/vendor';
|
|
248
|
+
const DEST = 'src/vendor/staticart';
|
|
249
|
+
cpSync(PLATFORM_VENDOR, DEST, { recursive: true });
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
This ensures vendor files stay in sync with the installed package version
|
|
253
|
+
without requiring a manual `clearstack update`.
|
|
254
|
+
|
|
255
|
+
---
|
|
256
|
+
|
|
257
|
+
## 5. Override Layers
|
|
258
|
+
|
|
259
|
+
A stacked project has multiple override layers. Each layer can customize
|
|
260
|
+
the one below it without forking.
|
|
261
|
+
|
|
262
|
+
### Component Overrides (Import Map)
|
|
263
|
+
|
|
264
|
+
The child project replaces a platform component by remapping its import:
|
|
265
|
+
|
|
266
|
+
```html
|
|
267
|
+
<script type="importmap">
|
|
268
|
+
{
|
|
269
|
+
"imports": {
|
|
270
|
+
"#staticart/product-card": "/components/my-product-card.js",
|
|
271
|
+
"#staticart/": "/vendor/staticart/"
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
</script>
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
The override component can:
|
|
278
|
+
|
|
279
|
+
- Replace the platform component entirely
|
|
280
|
+
- Import and wrap the original (decorator pattern)
|
|
281
|
+
- Import the original's helpers and compose differently
|
|
282
|
+
|
|
283
|
+
### Style Overrides (CSS Custom Properties)
|
|
284
|
+
|
|
285
|
+
The platform defines all visual properties as CSS custom properties in its
|
|
286
|
+
vendored `tokens.css`. The child project overrides them in its own
|
|
287
|
+
`tokens.css`, which loads after the platform's:
|
|
288
|
+
|
|
289
|
+
```css
|
|
290
|
+
/* src/styles/tokens.css — project overrides */
|
|
291
|
+
:root {
|
|
292
|
+
--color-primary: #8b4513; /* coffee brown, overrides platform blue */
|
|
293
|
+
}
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
### Config Overrides (Config File)
|
|
297
|
+
|
|
298
|
+
The platform reads its config file at runtime. The child project owns
|
|
299
|
+
this file entirely. The platform provides defaults for any missing keys.
|
|
300
|
+
|
|
301
|
+
### i18n Overrides (Locale Files)
|
|
302
|
+
|
|
303
|
+
Four-layer cascade, split by responsibility:
|
|
304
|
+
|
|
305
|
+
| Layer | Owner | File | Purpose |
|
|
306
|
+
| ------------------- | -------- | ------------------------------- | ------------------------ |
|
|
307
|
+
| 1. Defaults | Platform | Hardcoded in `i18n.js` | English UI chrome |
|
|
308
|
+
| 2. Locale | Platform | `locales/<lang>.json` | Translated UI chrome |
|
|
309
|
+
| 3. Overrides | Project | `locales/overrides.json` | English project terms |
|
|
310
|
+
| 4. Locale overrides | Project | `locales/overrides.<lang>.json` | Translated project terms |
|
|
311
|
+
|
|
312
|
+
The platform translates its own UI strings (buttons, labels, status text).
|
|
313
|
+
The project translates its own domain terms (category names, variant labels,
|
|
314
|
+
product descriptions). Neither layer needs to know about the other's keys.
|
|
315
|
+
|
|
316
|
+
---
|
|
317
|
+
|
|
318
|
+
## 6. Spec Check Composition
|
|
319
|
+
|
|
320
|
+
Clearstack's spec checks must work across the stack. The platform can
|
|
321
|
+
declare additional check requirements that compose with Clearstack's base
|
|
322
|
+
checks.
|
|
323
|
+
|
|
324
|
+
### What Composes Automatically
|
|
325
|
+
|
|
326
|
+
These Clearstack checks apply to all code regardless of layer:
|
|
327
|
+
|
|
328
|
+
- Line count limits (≤150 lines)
|
|
329
|
+
- Import map alias enforcement (no `../`)
|
|
330
|
+
- Prettier formatting
|
|
331
|
+
- ESLint rules
|
|
332
|
+
- Stylelint rules
|
|
333
|
+
- Security audit
|
|
334
|
+
|
|
335
|
+
### What the Platform Can Add
|
|
336
|
+
|
|
337
|
+
The platform manifest can declare additional `jsconfig.json` paths for
|
|
338
|
+
type checking:
|
|
339
|
+
|
|
340
|
+
```json
|
|
341
|
+
{
|
|
342
|
+
"clearstack": {
|
|
343
|
+
"platform": {
|
|
344
|
+
"tscDomains": ["api/jsconfig.json"]
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
Clearstack's type checker auto-discovers `jsconfig.json` files, so this
|
|
351
|
+
usually works without explicit declaration. The manifest field exists for
|
|
352
|
+
edge cases where the platform needs type checking in non-standard locations.
|
|
353
|
+
|
|
354
|
+
### Vendor Files Are Excluded
|
|
355
|
+
|
|
356
|
+
Spec checks skip `src/vendor/` by default (already in `SPEC_IGNORE_DIRS`).
|
|
357
|
+
Platform vendor files are the platform's responsibility to keep compliant —
|
|
358
|
+
the child project doesn't lint them.
|
|
359
|
+
|
|
360
|
+
---
|
|
361
|
+
|
|
362
|
+
## 7. Multi-Platform Stacking
|
|
363
|
+
|
|
364
|
+
A project can consume multiple platforms. Each gets its own prefix,
|
|
365
|
+
vendor directory, and config file.
|
|
366
|
+
|
|
367
|
+
```html
|
|
368
|
+
<script type="importmap">
|
|
369
|
+
{
|
|
370
|
+
"imports": {
|
|
371
|
+
"#staticart/": "/vendor/staticart/",
|
|
372
|
+
"#blogengine/": "/vendor/blogengine/",
|
|
373
|
+
"#store/": "/store/"
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
</script>
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
### Conflict Resolution
|
|
380
|
+
|
|
381
|
+
- Import map prefixes must be unique. Two platforms cannot use the same prefix.
|
|
382
|
+
- Config files must have different names.
|
|
383
|
+
- CSS custom properties use platform-prefixed names to avoid collisions:
|
|
384
|
+
`--staticart-color-primary`, `--blogengine-color-primary`.
|
|
385
|
+
- Shared dependencies (e.g. both platforms need hybrids) are vendored once
|
|
386
|
+
at the Clearstack level (`src/vendor/hybrids/`), not per-platform.
|
|
387
|
+
|
|
388
|
+
### Practical Limits
|
|
389
|
+
|
|
390
|
+
Multi-platform stacking is supported but not the common case. Most projects
|
|
391
|
+
use one platform. The architecture supports multiple platforms to avoid
|
|
392
|
+
painting into a corner, not because it's a recommended pattern.
|
|
393
|
+
|
|
394
|
+
---
|
|
395
|
+
|
|
396
|
+
## 8. Build Pipeline Integration
|
|
397
|
+
|
|
398
|
+
Platforms often provide build scripts (e.g. StatiCart's `build-products.js`).
|
|
399
|
+
These integrate with the child project's build pipeline.
|
|
400
|
+
|
|
401
|
+
### Script Ownership
|
|
402
|
+
|
|
403
|
+
| Script | Copied on | Updated on | Owner |
|
|
404
|
+
| --------------------------- | ------------ | ---------- | --------------------------------- |
|
|
405
|
+
| `scripts/setup.js` | init | never | Project (may customize) |
|
|
406
|
+
| `scripts/vendor-deps.js` | init | never | Project (adds platform vendor) |
|
|
407
|
+
| `scripts/build-products.js` | init | never | Project (may customize) |
|
|
408
|
+
| Platform-internal scripts | never copied | n/a | Platform (runs from node_modules) |
|
|
409
|
+
|
|
410
|
+
The child project's `setup.js` (run on `postinstall`) should vendor both
|
|
411
|
+
Clearstack dependencies and platform files:
|
|
412
|
+
|
|
413
|
+
```javascript
|
|
414
|
+
// scripts/setup.js
|
|
415
|
+
await import('./vendor-deps.js'); // hybrids → src/vendor/hybrids/
|
|
416
|
+
await import('./vendor-platform.js'); // staticart → src/vendor/staticart/
|
|
417
|
+
await import('./build-icons.js'); // lucide → icons.json
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
### CI/CD
|
|
421
|
+
|
|
422
|
+
The platform's GitHub Actions workflow template is copied on `init`. The
|
|
423
|
+
child project owns it and can customize. The platform documents what
|
|
424
|
+
environment variables and secrets are required.
|
|
425
|
+
|
|
426
|
+
---
|
|
427
|
+
|
|
428
|
+
## 9. Version Compatibility
|
|
429
|
+
|
|
430
|
+
### Clearstack ↔ Platform
|
|
431
|
+
|
|
432
|
+
The platform declares its required Clearstack version as a `peerDependency`
|
|
433
|
+
(e.g. `"@techninja/clearstack": ">=0.3.17"`).
|
|
434
|
+
|
|
435
|
+
### Platform ↔ Project
|
|
436
|
+
|
|
437
|
+
The child project pins the platform version in `dependencies` or
|
|
438
|
+
`devDependencies`. `npm update` + `clearstack update` brings in new
|
|
439
|
+
vendor files. The project's config, overrides, and custom components
|
|
440
|
+
are untouched.
|
|
441
|
+
|
|
442
|
+
### Breaking Changes
|
|
443
|
+
|
|
444
|
+
When a platform makes a breaking change (new required config key, renamed
|
|
445
|
+
component, changed store model shape):
|
|
446
|
+
|
|
447
|
+
1. Platform bumps major version
|
|
448
|
+
2. Platform's `CHANGELOG.md` documents migration steps
|
|
449
|
+
3. `clearstack update` warns if the installed platform version has a
|
|
450
|
+
major bump since last update
|
|
451
|
+
4. `--force` is required to proceed with major version updates
|
|
452
|
+
|
|
453
|
+
---
|
|
454
|
+
|
|
455
|
+
## 10. Example
|
|
456
|
+
|
|
457
|
+
See `docs/app-spec/` for project-specific platform examples (e.g.
|
|
458
|
+
StatiCart package structure, child project layout, i18n layer mapping).
|
|
459
|
+
|
|
460
|
+
---
|
|
461
|
+
|
|
462
|
+
## Summary
|
|
463
|
+
|
|
464
|
+
| Concept | Mechanism |
|
|
465
|
+
| -------------------- | ------------------------------------------------------------------------------------------ |
|
|
466
|
+
| Platform declaration | `clearstack.platform` in `package.json` |
|
|
467
|
+
| Component override | Import map specificity (`#prefix/component` before `#prefix/`) |
|
|
468
|
+
| Style override | CSS custom properties in project `tokens.css` |
|
|
469
|
+
| Config override | Project-owned config file, platform provides defaults |
|
|
470
|
+
| i18n override | 4-layer cascade: platform defaults → locale → project overrides → project locale overrides |
|
|
471
|
+
| Vendor sync | `postinstall` + `clearstack update` copies platform source to `src/vendor/<prefix>/` |
|
|
472
|
+
| Spec checks | Clearstack checks compose automatically; vendor files excluded |
|
|
473
|
+
| Version management | semver + `peerDependencies` + `clearstack update` warnings |
|
|
474
|
+
|
|
475
|
+
### Design Principles
|
|
476
|
+
|
|
477
|
+
1. **Convention over configuration.** The import map prefix, vendor directory,
|
|
478
|
+
and override cascade all follow predictable patterns.
|
|
479
|
+
2. **Ownership is explicit.** Every file belongs to exactly one layer.
|
|
480
|
+
No ambiguity about who can edit what.
|
|
481
|
+
3. **Update without clobbering.** `clearstack update` never destroys
|
|
482
|
+
project customizations. Vendor files are regenerable. Config is sacred.
|
|
483
|
+
4. **Override without forking.** Import maps, CSS custom properties, and
|
|
484
|
+
i18n cascades let projects customize without modifying platform source.
|
|
485
|
+
5. **Spec checks compose.** A stacked project passes the same checks as
|
|
486
|
+
a vanilla Clearstack project. No special configuration needed.
|
package/docs/QUICKSTART.md
CHANGED
|
@@ -137,7 +137,7 @@ npm run spec lint # ESLint + Stylelint + Markdown
|
|
|
137
137
|
npm run spec lint es # ESLint only
|
|
138
138
|
npm run spec format # Prettier
|
|
139
139
|
npm run spec imports # import map aliases
|
|
140
|
-
npm run spec types # JSDoc
|
|
140
|
+
npm run spec types # JSDoc types (all jsconfigs)
|
|
141
141
|
npm run spec audit # security audit
|
|
142
142
|
npm test # Node + browser tests
|
|
143
143
|
```
|
|
@@ -172,7 +172,7 @@ spec all → lint + format + code + docs + imports + types + audit
|
|
|
172
172
|
| Install dependencies | `npm install` |
|
|
173
173
|
| Start dev server | `npm run dev` |
|
|
174
174
|
| Lint + format | `npm run spec lint && npm run spec format` |
|
|
175
|
-
| Type check | `npm run
|
|
175
|
+
| Type check | `npm run spec types` |
|
|
176
176
|
| Run tests | `npm test` |
|
|
177
177
|
| Full spec check | `npm run spec` |
|
|
178
178
|
| Update spec + configs | `npm run spec update` |
|
|
@@ -253,6 +253,43 @@ This applies to any property with `value: []` and an async `connect`.
|
|
|
253
253
|
The `connect` callback sets the value and calls `invalidate()`, but the
|
|
254
254
|
first render happens before that resolves.
|
|
255
255
|
|
|
256
|
+
#### Async Init with Child Component Props
|
|
257
|
+
|
|
258
|
+
When a parent loads data during `connect` and passes it to a child via
|
|
259
|
+
template props, the child may never see the change. Hybrids' change
|
|
260
|
+
detection requires observing old → new. If the prop is set before the
|
|
261
|
+
child mounts, there's no "old" value to compare against.
|
|
262
|
+
|
|
263
|
+
```javascript
|
|
264
|
+
// ❌ BAD — resultCount set during connect, before child mounts
|
|
265
|
+
_init: {
|
|
266
|
+
value: false,
|
|
267
|
+
connect: (host, _key, invalidate) => {
|
|
268
|
+
loadData(host).then(() => {
|
|
269
|
+
host.resultCount = 14;
|
|
270
|
+
invalidate(); // child mounts with 14, never sees a change
|
|
271
|
+
});
|
|
272
|
+
},
|
|
273
|
+
},
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
The fix: defer the prop update to after the first render frame so the
|
|
277
|
+
child is mounted and can observe the transition:
|
|
278
|
+
|
|
279
|
+
```javascript
|
|
280
|
+
// ✅ GOOD — child mounts with default (0), then sees 0 → 14
|
|
281
|
+
connect: (host, _key, invalidate) => {
|
|
282
|
+
loadData(host).then(() => {
|
|
283
|
+
invalidate();
|
|
284
|
+
requestAnimationFrame(() => { host.resultCount = loadedCount; });
|
|
285
|
+
});
|
|
286
|
+
},
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
Pre-populate external caches during `loadData` so the child has data
|
|
290
|
+
ready when the deferred update triggers. The 0 → 14 transition happens
|
|
291
|
+
within a single frame — no flicker.
|
|
292
|
+
|
|
256
293
|
---
|
|
257
294
|
|
|
258
295
|
## Routing
|
|
@@ -410,37 +447,16 @@ For live data updates, the backend pushes events via **Server-Sent Events
|
|
|
410
447
|
|
|
411
448
|
### Frontend: SSE Listener
|
|
412
449
|
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
import { store } from 'hybrids';
|
|
417
|
-
|
|
418
|
-
export function connectRealtime(url, modelMap) {
|
|
419
|
-
const source = new EventSource(url);
|
|
420
|
-
|
|
421
|
-
source.addEventListener('update', (event) => {
|
|
422
|
-
const { type } = JSON.parse(event.data);
|
|
423
|
-
const Model = modelMap[type];
|
|
424
|
-
if (Model) store.clear(Model); // full clear triggers re-fetch
|
|
425
|
-
});
|
|
426
|
-
|
|
427
|
-
source.addEventListener('error', () => {
|
|
428
|
-
source.close();
|
|
429
|
-
setTimeout(() => connectRealtime(url, modelMap), 5000);
|
|
430
|
-
});
|
|
431
|
-
|
|
432
|
-
return () => source.close();
|
|
433
|
-
}
|
|
434
|
-
```
|
|
450
|
+
`src/utils/realtimeSync.js` connects to the SSE endpoint, listens for
|
|
451
|
+
`update` events, and calls `store.clear(Model)` to invalidate the cache.
|
|
452
|
+
Any component bound via `store()` re-fetches automatically.
|
|
435
453
|
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
from the API. This is the mechanism that makes multi-user realtime work —
|
|
439
|
-
when user A creates a task, user B's task list updates automatically.
|
|
454
|
+
The listener reconnects on error (5s delay) and returns a disconnect
|
|
455
|
+
function for cleanup in the router shell's `connect` descriptor.
|
|
440
456
|
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
457
|
+
`store.clear(Model)` is also called by form submit handlers after a
|
|
458
|
+
successful save. The SSE event that follows is redundant for the local
|
|
459
|
+
user but ensures other connected clients update.
|
|
444
460
|
|
|
445
461
|
### Backend: SSE Endpoint
|
|
446
462
|
|
|
@@ -449,31 +465,20 @@ contract.
|
|
|
449
465
|
|
|
450
466
|
### Wiring It Up
|
|
451
467
|
|
|
452
|
-
In the router shell
|
|
468
|
+
In the router shell, use a `connect` descriptor to start SSE and return
|
|
469
|
+
the disconnect function. Map entity types to store models:
|
|
453
470
|
|
|
454
471
|
```javascript
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
tag: 'app-router',
|
|
460
|
-
stack: router(HomeView, { url: '/' }),
|
|
461
|
-
connection: {
|
|
462
|
-
value: undefined,
|
|
463
|
-
connect(host) {
|
|
464
|
-
const disconnect = connectRealtime('/api/events', {
|
|
465
|
-
user: UserModel,
|
|
466
|
-
});
|
|
467
|
-
return disconnect;
|
|
468
|
-
},
|
|
472
|
+
connection: {
|
|
473
|
+
value: undefined,
|
|
474
|
+
connect(host) {
|
|
475
|
+
return connectRealtime('/api/events', { user: UserModel });
|
|
469
476
|
},
|
|
470
|
-
|
|
471
|
-
});
|
|
477
|
+
},
|
|
472
478
|
```
|
|
473
479
|
|
|
474
|
-
When the backend sends `{ type: "user"
|
|
475
|
-
|
|
476
|
-
re-fetches automatically.
|
|
480
|
+
When the backend sends `{ type: "user" }` over SSE, the store cache for
|
|
481
|
+
`UserModel` is cleared and any bound component re-fetches automatically.
|
|
477
482
|
|
|
478
483
|
### Debouncing Batch Operations
|
|
479
484
|
|