@techninja/clearstack 0.3.9 → 0.3.11
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/bin/cli.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* Usage:
|
|
6
6
|
* clearstack init [-y] [--mode fullstack|static] [--port 3000]
|
|
7
7
|
* clearstack update
|
|
8
|
-
* clearstack check [code|docs]
|
|
8
|
+
* clearstack check [code|docs|imports|lint|lint es|format|types|audit|all]
|
|
9
9
|
* clearstack → interactive menu
|
|
10
10
|
*/
|
|
11
11
|
|
|
@@ -25,16 +25,21 @@ const yes = args.includes('-y') || args.includes('--yes');
|
|
|
25
25
|
|
|
26
26
|
/** Show interactive menu. */
|
|
27
27
|
async function interactive() {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
28
|
+
try {
|
|
29
|
+
const { select } = await import('@inquirer/prompts');
|
|
30
|
+
const action = await select({
|
|
31
|
+
message: 'clearstack — what do you want to do?',
|
|
32
|
+
choices: [
|
|
33
|
+
{ name: 'Initialize a new project', value: 'init' },
|
|
34
|
+
{ name: 'Update spec docs + configs', value: 'update' },
|
|
35
|
+
{ name: 'Run spec compliance check', value: 'check' },
|
|
36
|
+
],
|
|
37
|
+
});
|
|
38
|
+
await run(action);
|
|
39
|
+
} catch (e) {
|
|
40
|
+
if (e?.name === 'ExitPromptError') process.exit(0);
|
|
41
|
+
throw e;
|
|
42
|
+
}
|
|
38
43
|
}
|
|
39
44
|
|
|
40
45
|
/**
|
|
@@ -49,9 +54,9 @@ async function run(action) {
|
|
|
49
54
|
const { update } = await import('../lib/update.js');
|
|
50
55
|
await update(PKG_ROOT);
|
|
51
56
|
} else if (action === 'check') {
|
|
52
|
-
const
|
|
57
|
+
const subs = args.filter((a) => a !== cmd && !a.startsWith('-'));
|
|
53
58
|
const { check } = await import('../lib/check.js');
|
|
54
|
-
await check(process.cwd(),
|
|
59
|
+
await check(process.cwd(), subs.join(' ') || undefined);
|
|
55
60
|
} else {
|
|
56
61
|
console.log('Usage: clearstack [init|update|check] [-y]');
|
|
57
62
|
}
|
package/docs/CONVENTIONS.md
CHANGED
|
@@ -117,15 +117,15 @@ relative paths like `../../../utils/foo.js`.
|
|
|
117
117
|
|
|
118
118
|
### Available Prefixes
|
|
119
119
|
|
|
120
|
-
| Prefix
|
|
121
|
-
|
|
|
122
|
-
| `#store/`
|
|
123
|
-
| `#utils/`
|
|
124
|
-
| `#atoms/`
|
|
125
|
-
| `#molecules/`
|
|
126
|
-
| `#organisms/`
|
|
127
|
-
| `#templates/`
|
|
128
|
-
| `#pages/`
|
|
120
|
+
| Prefix | Resolves to |
|
|
121
|
+
| ------------- | ------------------------ |
|
|
122
|
+
| `#store/` | `/store/` |
|
|
123
|
+
| `#utils/` | `/utils/` |
|
|
124
|
+
| `#atoms/` | `/components/atoms/` |
|
|
125
|
+
| `#molecules/` | `/components/molecules/` |
|
|
126
|
+
| `#organisms/` | `/components/organisms/` |
|
|
127
|
+
| `#templates/` | `/components/templates/` |
|
|
128
|
+
| `#pages/` | `/pages/` |
|
|
129
129
|
|
|
130
130
|
### Rules
|
|
131
131
|
|
|
@@ -43,12 +43,12 @@ If a property is defined as `undefined` or later set to `null`, this crashes:
|
|
|
43
43
|
// ❌ BAD — undefined has no type info, null crashes .toString()
|
|
44
44
|
export default define({
|
|
45
45
|
tag: 'my-view',
|
|
46
|
-
_data: undefined,
|
|
46
|
+
_data: undefined, // no type info for hybrids to work with
|
|
47
47
|
// ...
|
|
48
48
|
});
|
|
49
49
|
|
|
50
50
|
// Later in a handler:
|
|
51
|
-
host._data = null;
|
|
51
|
+
host._data = null; // TypeError: Cannot read properties of null (reading 'toString')
|
|
52
52
|
```
|
|
53
53
|
|
|
54
54
|
Always provide a typed default value, and reset to the same type:
|
|
@@ -62,7 +62,7 @@ export default define({
|
|
|
62
62
|
});
|
|
63
63
|
|
|
64
64
|
// Later in a handler:
|
|
65
|
-
host._data = [];
|
|
65
|
+
host._data = []; // safe — same type as default
|
|
66
66
|
```
|
|
67
67
|
|
|
68
68
|
The `connect: () => {}` no-op prevents hybrids from doing anything special
|
|
@@ -232,14 +232,16 @@ export default define({
|
|
|
232
232
|
items: {
|
|
233
233
|
value: [],
|
|
234
234
|
connect: (host, _key, invalidate) => {
|
|
235
|
-
fetchItems().then((list) => {
|
|
235
|
+
fetchItems().then((list) => {
|
|
236
|
+
host.items = list;
|
|
237
|
+
invalidate();
|
|
238
|
+
});
|
|
236
239
|
},
|
|
237
240
|
},
|
|
238
241
|
render: ({ items }) => html`
|
|
239
242
|
// ❌ BAD — items may not be an array yet when render fires
|
|
240
|
-
${items.filter((i) => i.active).map((i) => html`<span>${i.name}</span>`)}
|
|
241
|
-
|
|
242
|
-
// ✅ GOOD — guard before calling array methods
|
|
243
|
+
${items.filter((i) => i.active).map((i) => html`<span>${i.name}</span>`)} // ✅ GOOD — guard
|
|
244
|
+
before calling array methods
|
|
243
245
|
${Array.isArray(items) && items.length > 0
|
|
244
246
|
? items.filter((i) => i.active).map((i) => html`<span>${i.name}</span>`)
|
|
245
247
|
: html``}
|
|
@@ -350,7 +352,7 @@ properties holding complex objects (arrays of records, parsed data, etc.),
|
|
|
350
352
|
the URL becomes enormous — potentially megabytes — and the browser locks
|
|
351
353
|
up just rendering the `<a>` element.
|
|
352
354
|
|
|
353
|
-
The `connect: () => {}` no-op prevents hybrids from
|
|
355
|
+
The `connect: () => {}` no-op prevents hybrids from _observing_ a property,
|
|
354
356
|
but the router still serializes it. The only way to fully exclude a
|
|
355
357
|
property from URL serialization is to not define it on the routed view at
|
|
356
358
|
all (use a module-level variable or a separate store).
|
|
@@ -361,10 +363,10 @@ direct href instead of `router.backUrl()`:
|
|
|
361
363
|
```javascript
|
|
362
364
|
// ❌ BAD — serializes ALL parent properties into the href
|
|
363
365
|
// If parent has a 700K-item array, the URL is megabytes
|
|
364
|
-
html`<a href="${router.backUrl()}">← Back</a
|
|
366
|
+
html`<a href="${router.backUrl()}">← Back</a>`;
|
|
365
367
|
|
|
366
368
|
// ✅ GOOD — direct link, no serialization
|
|
367
|
-
html`<a href="/dashboard">← Back</a
|
|
369
|
+
html`<a href="/dashboard">← Back</a>`;
|
|
368
370
|
```
|
|
369
371
|
|
|
370
372
|
Use `router.backUrl()` only when the parent view has simple scalar
|
|
@@ -476,25 +478,11 @@ re-fetches automatically.
|
|
|
476
478
|
### Debouncing Batch Operations
|
|
477
479
|
|
|
478
480
|
Operations like drag-to-reorder send multiple PUTs, each triggering an SSE
|
|
479
|
-
event. Without debouncing, each event clears the store
|
|
480
|
-
re-render while the previous render is still pending — causing cascading
|
|
481
|
-
errors.
|
|
481
|
+
event. Without debouncing, each event clears the store mid-render.
|
|
482
482
|
|
|
483
483
|
The `connectRealtime()` utility debounces by entity type: multiple SSE
|
|
484
|
-
events within 300ms trigger only one `store.clear()`.
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
```javascript
|
|
488
|
-
// Inside connectRealtime — debounce per entity type
|
|
489
|
-
const timers = {};
|
|
490
|
-
source.addEventListener('update', (event) => {
|
|
491
|
-
const { type } = JSON.parse(event.data);
|
|
492
|
-
clearTimeout(timers[type]);
|
|
493
|
-
timers[type] = setTimeout(() => {
|
|
494
|
-
store.clear([Model]); // one clear after the batch settles
|
|
495
|
-
}, 300);
|
|
496
|
-
});
|
|
497
|
-
```
|
|
484
|
+
events within 300ms trigger only one `store.clear()`. A reorder of 5 tasks
|
|
485
|
+
sends 5 PUTs → 5 SSE events → 1 store clear after the batch settles.
|
|
498
486
|
|
|
499
|
-
For the local user,
|
|
500
|
-
|
|
487
|
+
For the local user, don't call `store.clear()` explicitly after batch
|
|
488
|
+
operations — let the debounced SSE handler do it once.
|
package/lib/check.js
CHANGED
|
@@ -114,7 +114,7 @@ export async function check(projectDir, scope) {
|
|
|
114
114
|
const cmds = buildCmds(detectRunner(projectDir));
|
|
115
115
|
const checks = buildChecks(projectDir, cfg, cmds);
|
|
116
116
|
|
|
117
|
-
if (scope) {
|
|
117
|
+
if (scope && scope !== 'all') {
|
|
118
118
|
const matched = resolveChecks(checks, scope);
|
|
119
119
|
if (!matched) {
|
|
120
120
|
console.log(`Unknown check: ${scope}`);
|
package/package.json
CHANGED
|
@@ -117,15 +117,15 @@ relative paths like `../../../utils/foo.js`.
|
|
|
117
117
|
|
|
118
118
|
### Available Prefixes
|
|
119
119
|
|
|
120
|
-
| Prefix
|
|
121
|
-
|
|
|
122
|
-
| `#store/`
|
|
123
|
-
| `#utils/`
|
|
124
|
-
| `#atoms/`
|
|
125
|
-
| `#molecules/`
|
|
126
|
-
| `#organisms/`
|
|
127
|
-
| `#templates/`
|
|
128
|
-
| `#pages/`
|
|
120
|
+
| Prefix | Resolves to |
|
|
121
|
+
| ------------- | ------------------------ |
|
|
122
|
+
| `#store/` | `/store/` |
|
|
123
|
+
| `#utils/` | `/utils/` |
|
|
124
|
+
| `#atoms/` | `/components/atoms/` |
|
|
125
|
+
| `#molecules/` | `/components/molecules/` |
|
|
126
|
+
| `#organisms/` | `/components/organisms/` |
|
|
127
|
+
| `#templates/` | `/components/templates/` |
|
|
128
|
+
| `#pages/` | `/pages/` |
|
|
129
129
|
|
|
130
130
|
### Rules
|
|
131
131
|
|
|
@@ -43,12 +43,12 @@ If a property is defined as `undefined` or later set to `null`, this crashes:
|
|
|
43
43
|
// ❌ BAD — undefined has no type info, null crashes .toString()
|
|
44
44
|
export default define({
|
|
45
45
|
tag: 'my-view',
|
|
46
|
-
_data: undefined,
|
|
46
|
+
_data: undefined, // no type info for hybrids to work with
|
|
47
47
|
// ...
|
|
48
48
|
});
|
|
49
49
|
|
|
50
50
|
// Later in a handler:
|
|
51
|
-
host._data = null;
|
|
51
|
+
host._data = null; // TypeError: Cannot read properties of null (reading 'toString')
|
|
52
52
|
```
|
|
53
53
|
|
|
54
54
|
Always provide a typed default value, and reset to the same type:
|
|
@@ -62,7 +62,7 @@ export default define({
|
|
|
62
62
|
});
|
|
63
63
|
|
|
64
64
|
// Later in a handler:
|
|
65
|
-
host._data = [];
|
|
65
|
+
host._data = []; // safe — same type as default
|
|
66
66
|
```
|
|
67
67
|
|
|
68
68
|
The `connect: () => {}` no-op prevents hybrids from doing anything special
|
|
@@ -232,14 +232,16 @@ export default define({
|
|
|
232
232
|
items: {
|
|
233
233
|
value: [],
|
|
234
234
|
connect: (host, _key, invalidate) => {
|
|
235
|
-
fetchItems().then((list) => {
|
|
235
|
+
fetchItems().then((list) => {
|
|
236
|
+
host.items = list;
|
|
237
|
+
invalidate();
|
|
238
|
+
});
|
|
236
239
|
},
|
|
237
240
|
},
|
|
238
241
|
render: ({ items }) => html`
|
|
239
242
|
// ❌ BAD — items may not be an array yet when render fires
|
|
240
|
-
${items.filter((i) => i.active).map((i) => html`<span>${i.name}</span>`)}
|
|
241
|
-
|
|
242
|
-
// ✅ GOOD — guard before calling array methods
|
|
243
|
+
${items.filter((i) => i.active).map((i) => html`<span>${i.name}</span>`)} // ✅ GOOD — guard
|
|
244
|
+
before calling array methods
|
|
243
245
|
${Array.isArray(items) && items.length > 0
|
|
244
246
|
? items.filter((i) => i.active).map((i) => html`<span>${i.name}</span>`)
|
|
245
247
|
: html``}
|
|
@@ -350,7 +352,7 @@ properties holding complex objects (arrays of records, parsed data, etc.),
|
|
|
350
352
|
the URL becomes enormous — potentially megabytes — and the browser locks
|
|
351
353
|
up just rendering the `<a>` element.
|
|
352
354
|
|
|
353
|
-
The `connect: () => {}` no-op prevents hybrids from
|
|
355
|
+
The `connect: () => {}` no-op prevents hybrids from _observing_ a property,
|
|
354
356
|
but the router still serializes it. The only way to fully exclude a
|
|
355
357
|
property from URL serialization is to not define it on the routed view at
|
|
356
358
|
all (use a module-level variable or a separate store).
|
|
@@ -361,10 +363,10 @@ direct href instead of `router.backUrl()`:
|
|
|
361
363
|
```javascript
|
|
362
364
|
// ❌ BAD — serializes ALL parent properties into the href
|
|
363
365
|
// If parent has a 700K-item array, the URL is megabytes
|
|
364
|
-
html`<a href="${router.backUrl()}">← Back</a
|
|
366
|
+
html`<a href="${router.backUrl()}">← Back</a>`;
|
|
365
367
|
|
|
366
368
|
// ✅ GOOD — direct link, no serialization
|
|
367
|
-
html`<a href="/dashboard">← Back</a
|
|
369
|
+
html`<a href="/dashboard">← Back</a>`;
|
|
368
370
|
```
|
|
369
371
|
|
|
370
372
|
Use `router.backUrl()` only when the parent view has simple scalar
|
|
@@ -476,25 +478,11 @@ re-fetches automatically.
|
|
|
476
478
|
### Debouncing Batch Operations
|
|
477
479
|
|
|
478
480
|
Operations like drag-to-reorder send multiple PUTs, each triggering an SSE
|
|
479
|
-
event. Without debouncing, each event clears the store
|
|
480
|
-
re-render while the previous render is still pending — causing cascading
|
|
481
|
-
errors.
|
|
481
|
+
event. Without debouncing, each event clears the store mid-render.
|
|
482
482
|
|
|
483
483
|
The `connectRealtime()` utility debounces by entity type: multiple SSE
|
|
484
|
-
events within 300ms trigger only one `store.clear()`.
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
```javascript
|
|
488
|
-
// Inside connectRealtime — debounce per entity type
|
|
489
|
-
const timers = {};
|
|
490
|
-
source.addEventListener('update', (event) => {
|
|
491
|
-
const { type } = JSON.parse(event.data);
|
|
492
|
-
clearTimeout(timers[type]);
|
|
493
|
-
timers[type] = setTimeout(() => {
|
|
494
|
-
store.clear([Model]); // one clear after the batch settles
|
|
495
|
-
}, 300);
|
|
496
|
-
});
|
|
497
|
-
```
|
|
484
|
+
events within 300ms trigger only one `store.clear()`. A reorder of 5 tasks
|
|
485
|
+
sends 5 PUTs → 5 SSE events → 1 store clear after the batch settles.
|
|
498
486
|
|
|
499
|
-
For the local user,
|
|
500
|
-
|
|
487
|
+
For the local user, don't call `store.clear()` explicitly after batch
|
|
488
|
+
operations — let the debounced SSE handler do it once.
|