@techninja/clearstack 0.2.16 → 0.2.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/BACKEND_API_SPEC.md +85 -48
- package/docs/BUILD_LOG.md +42 -19
- package/docs/COMPONENT_PATTERNS.md +57 -51
- package/docs/CONVENTIONS.md +43 -31
- package/docs/FRONTEND_IMPLEMENTATION_RULES.md +57 -58
- package/docs/JSDOC_TYPING.md +1 -0
- package/docs/QUICKSTART.md +20 -18
- package/docs/SERVER_AND_DEPS.md +28 -29
- package/docs/STATE_AND_ROUTING.md +53 -52
- package/docs/TESTING.md +38 -37
- package/docs/app-spec/ENTITIES.md +16 -16
- package/docs/app-spec/README.md +4 -4
- package/lib/check.js +42 -77
- package/lib/package-gen.js +3 -0
- package/lib/spec-utils.js +109 -0
- package/package.json +5 -2
- package/templates/fullstack/data/seed.json +1 -1
- package/templates/shared/.configs/.markdownlint.jsonc +9 -0
- package/templates/shared/.configs/.stylelintrc.json +16 -0
- package/templates/shared/.configs/jsconfig.json +2 -9
- package/templates/shared/.github/ISSUE_TEMPLATE/bug_report.md +1 -1
- package/templates/shared/.github/ISSUE_TEMPLATE/feature_request.md +1 -1
- package/templates/shared/.github/ISSUE_TEMPLATE/spec_correction.md +1 -1
- package/templates/shared/.github/pull_request_template.md +3 -0
- package/templates/shared/.github/workflows/spec.yml +3 -3
- package/templates/shared/docs/app-spec/README.md +8 -8
- package/templates/shared/docs/clearstack/BACKEND_API_SPEC.md +85 -48
- package/templates/shared/docs/clearstack/BUILD_LOG.md +42 -19
- package/templates/shared/docs/clearstack/COMPONENT_PATTERNS.md +57 -51
- package/templates/shared/docs/clearstack/CONVENTIONS.md +43 -31
- package/templates/shared/docs/clearstack/FRONTEND_IMPLEMENTATION_RULES.md +57 -58
- package/templates/shared/docs/clearstack/JSDOC_TYPING.md +1 -0
- package/templates/shared/docs/clearstack/QUICKSTART.md +20 -18
- package/templates/shared/docs/clearstack/SERVER_AND_DEPS.md +28 -29
- package/templates/shared/docs/clearstack/STATE_AND_ROUTING.md +53 -52
- package/templates/shared/docs/clearstack/TESTING.md +38 -37
- package/templates/shared/src/public/index.html +23 -23
- package/templates/shared/src/styles/shared.css +3 -3
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
# Component Patterns
|
|
2
|
+
|
|
2
3
|
## Authoring, Styling, Templates & JSDoc Typing
|
|
3
4
|
|
|
4
5
|
> How to write, style, and type components in this framework.
|
|
@@ -33,13 +34,13 @@ See the Light DOM section below for why.
|
|
|
33
34
|
|
|
34
35
|
Properties are declared as default values. Hybrids infers the type:
|
|
35
36
|
|
|
36
|
-
| Declaration
|
|
37
|
-
|
|
38
|
-
| `count: 0`
|
|
39
|
-
| `label: ''`
|
|
40
|
-
| `active: false`
|
|
41
|
-
| `items: []`
|
|
42
|
-
| `onClick: () => {}` | Function
|
|
37
|
+
| Declaration | Type | Reflected to attribute |
|
|
38
|
+
| ------------------- | ------------ | ---------------------- |
|
|
39
|
+
| `count: 0` | Number | Yes |
|
|
40
|
+
| `label: ''` | String | Yes |
|
|
41
|
+
| `active: false` | Boolean | Yes |
|
|
42
|
+
| `items: []` | Array/Object | No |
|
|
43
|
+
| `onClick: () => {}` | Function | No |
|
|
43
44
|
|
|
44
45
|
### Property Descriptors
|
|
45
46
|
|
|
@@ -51,8 +52,10 @@ export default define({
|
|
|
51
52
|
elapsed: {
|
|
52
53
|
value: 0,
|
|
53
54
|
connect(host, key, invalidate) {
|
|
54
|
-
const id = setInterval(() => {
|
|
55
|
-
|
|
55
|
+
const id = setInterval(() => {
|
|
56
|
+
host.elapsed++;
|
|
57
|
+
}, 1000);
|
|
58
|
+
return () => clearInterval(id); // cleanup on disconnect
|
|
56
59
|
},
|
|
57
60
|
observe(host, value) {
|
|
58
61
|
if (value >= 60) dispatch(host, 'timeout');
|
|
@@ -62,12 +65,12 @@ export default define({
|
|
|
62
65
|
});
|
|
63
66
|
```
|
|
64
67
|
|
|
65
|
-
| Descriptor field
|
|
66
|
-
|
|
67
|
-
| `value`
|
|
68
|
-
| `connect(host, key, invalidate)`
|
|
69
|
-
| `observe(host, value, lastValue)` | Runs when value changes
|
|
70
|
-
| `reflect`
|
|
68
|
+
| Descriptor field | Purpose |
|
|
69
|
+
| --------------------------------- | ------------------------------------------------- |
|
|
70
|
+
| `value` | Default value or factory function |
|
|
71
|
+
| `connect(host, key, invalidate)` | Runs on DOM connect. Return cleanup fn. |
|
|
72
|
+
| `observe(host, value, lastValue)` | Runs when value changes |
|
|
73
|
+
| `reflect` | `true` or `(value) => string` — sync to attribute |
|
|
71
74
|
|
|
72
75
|
### Event Handling
|
|
73
76
|
|
|
@@ -81,9 +84,7 @@ function handleClick(host, event) {
|
|
|
81
84
|
export default define({
|
|
82
85
|
tag: 'app-counter',
|
|
83
86
|
count: 0,
|
|
84
|
-
render: ({ count }) => html`
|
|
85
|
-
<button onclick="${handleClick}">Count: ${count}</button>
|
|
86
|
-
`,
|
|
87
|
+
render: ({ count }) => html` <button onclick="${handleClick}">Count: ${count}</button> `,
|
|
87
88
|
});
|
|
88
89
|
```
|
|
89
90
|
|
|
@@ -148,12 +149,12 @@ Custom events from atoms can be unreliable when templates are passed as
|
|
|
148
149
|
properties through intermediate components (e.g. `page-layout`'s `content`).
|
|
149
150
|
The host context and event bubbling path may not resolve as expected.
|
|
150
151
|
|
|
151
|
-
| Context
|
|
152
|
-
|
|
153
|
-
| Inside a component's own template
|
|
152
|
+
| Context | Use | Why |
|
|
153
|
+
| ------------------------------------ | ---------------------------- | ------------------------------------------------ |
|
|
154
|
+
| Inside a component's own template | `app-button` | Host context is correct, events bubble normally |
|
|
154
155
|
| Inside a `content` template property | Plain `<button class="btn">` | Direct `onclick` handler, no custom event needed |
|
|
155
|
-
| Reusable molecule/organism
|
|
156
|
-
| Page-level actions
|
|
156
|
+
| Reusable molecule/organism | `app-button` | Encapsulated, predictable host |
|
|
157
|
+
| Page-level actions | Plain `<button class="btn">` | Simplest, most reliable |
|
|
157
158
|
|
|
158
159
|
The `.btn` classes are global (defined in `buttons.css`), so plain buttons
|
|
159
160
|
look identical to `app-button`. Use the atom when you need its component
|
|
@@ -213,11 +214,11 @@ tree. CSS scoping is achieved through **native CSS nesting** on the tag name
|
|
|
213
214
|
|
|
214
215
|
Shadow DOM is the exception, not the rule. Enable it only when:
|
|
215
216
|
|
|
216
|
-
| Situation
|
|
217
|
-
|
|
218
|
-
| Wrapping a third-party widget
|
|
217
|
+
| Situation | Why shadow DOM |
|
|
218
|
+
| ----------------------------------- | ----------------------------------- |
|
|
219
|
+
| Wrapping a third-party widget | Prevent its styles from leaking out |
|
|
219
220
|
| Distributing a standalone component | Consumer's styles must not break it |
|
|
220
|
-
| Embedding untrusted content
|
|
221
|
+
| Embedding untrusted content | Hard style boundary needed |
|
|
221
222
|
|
|
222
223
|
For an internal application where you control all the CSS, shadow DOM
|
|
223
224
|
creates more problems than it solves — you end up fighting it to inject
|
|
@@ -302,11 +303,11 @@ Every component automatically inherits all shared styles. No injection needed.
|
|
|
302
303
|
|
|
303
304
|
### Three Layers of CSS
|
|
304
305
|
|
|
305
|
-
| Layer
|
|
306
|
-
|
|
307
|
-
| **Tokens**
|
|
308
|
-
| **Shared**
|
|
309
|
-
| **Components** | `components.css` + per-component `.css` | Styles scoped to tag names via native CSS nesting
|
|
306
|
+
| Layer | File(s) | What goes here |
|
|
307
|
+
| -------------- | --------------------------------------- | ------------------------------------------------------------------------- |
|
|
308
|
+
| **Tokens** | `tokens.css` | `:root` custom properties — colors, spacing, type, radii, shadows |
|
|
309
|
+
| **Shared** | `shared.css` | Error states, loading states, icon base, screen-reader utils, transitions |
|
|
310
|
+
| **Components** | `components.css` + per-component `.css` | Styles scoped to tag names via native CSS nesting |
|
|
310
311
|
|
|
311
312
|
### Per-Component CSS with Native Nesting
|
|
312
313
|
|
|
@@ -326,17 +327,22 @@ app-button {
|
|
|
326
327
|
color: white;
|
|
327
328
|
font: inherit;
|
|
328
329
|
|
|
329
|
-
&:hover {
|
|
330
|
-
|
|
330
|
+
&:hover {
|
|
331
|
+
background: var(--color-primary-hover);
|
|
332
|
+
}
|
|
333
|
+
&:disabled {
|
|
334
|
+
opacity: 0.5;
|
|
335
|
+
cursor: not-allowed;
|
|
336
|
+
}
|
|
331
337
|
}
|
|
332
338
|
|
|
333
|
-
&[variant=
|
|
339
|
+
&[variant='secondary'] button {
|
|
334
340
|
background: var(--color-surface);
|
|
335
341
|
color: var(--color-text);
|
|
336
342
|
border: 1px solid var(--color-border);
|
|
337
343
|
}
|
|
338
344
|
|
|
339
|
-
&[variant=
|
|
345
|
+
&[variant='ghost'] button {
|
|
340
346
|
background: transparent;
|
|
341
347
|
color: var(--color-primary);
|
|
342
348
|
}
|
|
@@ -393,7 +399,7 @@ Components reference tokens, never hardcode values.
|
|
|
393
399
|
All templates use `html` from hybrids:
|
|
394
400
|
|
|
395
401
|
```javascript
|
|
396
|
-
html`<div>${expression}</div
|
|
402
|
+
html`<div>${expression}</div>`;
|
|
397
403
|
```
|
|
398
404
|
|
|
399
405
|
Expressions can be: strings, numbers, booleans, other templates, arrays of
|
|
@@ -413,14 +419,14 @@ render: () => html`
|
|
|
413
419
|
`,
|
|
414
420
|
```
|
|
415
421
|
|
|
416
|
-
| Attribute
|
|
417
|
-
|
|
418
|
-
| `layout="row"`
|
|
419
|
-
| `layout="column"`
|
|
420
|
-
| `layout="grid:1\|max"`
|
|
421
|
-
| `layout="grow"`
|
|
422
|
-
| `layout="center"`
|
|
423
|
-
| `layout="gap:2"`
|
|
422
|
+
| Attribute | Effect |
|
|
423
|
+
| ----------------------- | ------------------------------ |
|
|
424
|
+
| `layout="row"` | Flexbox row |
|
|
425
|
+
| `layout="column"` | Flexbox column |
|
|
426
|
+
| `layout="grid:1\|max"` | CSS grid with defined tracks |
|
|
427
|
+
| `layout="grow"` | `flex-grow: 1` |
|
|
428
|
+
| `layout="center"` | Center content |
|
|
429
|
+
| `layout="gap:2"` | Gap using spacing scale |
|
|
424
430
|
| `layout@768px="hidden"` | Responsive — applies at ≥768px |
|
|
425
431
|
|
|
426
432
|
### Keyed Lists
|
|
@@ -455,12 +461,12 @@ render: ({ dataPromise }) => html`
|
|
|
455
461
|
|
|
456
462
|
### When a file grows too large
|
|
457
463
|
|
|
458
|
-
| Situation
|
|
459
|
-
|
|
460
|
-
| Component logic exceeds 150 lines | Extract helpers to `src/utils/`
|
|
461
|
-
| Template is too complex
|
|
462
|
-
| CSS exceeds 150 lines
|
|
463
|
-
| Store model has many relations
|
|
464
|
+
| Situation | Action |
|
|
465
|
+
| --------------------------------- | ---------------------------------------- |
|
|
466
|
+
| Component logic exceeds 150 lines | Extract helpers to `src/utils/` |
|
|
467
|
+
| Template is too complex | Split into child sub-components |
|
|
468
|
+
| CSS exceeds 150 lines | Extract shared patterns to `src/styles/` |
|
|
469
|
+
| Store model has many relations | Split related models into own files |
|
|
464
470
|
|
|
465
471
|
### What counts toward the limit
|
|
466
472
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
# Conventions
|
|
2
|
+
|
|
2
3
|
## Naming Rules & Anti-Patterns
|
|
3
4
|
|
|
4
5
|
> Quick reference for naming and what to avoid.
|
|
@@ -13,37 +14,37 @@
|
|
|
13
14
|
|
|
14
15
|
All custom element tags use **kebab-case** with an `app-` prefix:
|
|
15
16
|
|
|
16
|
-
| Thing
|
|
17
|
-
|
|
18
|
-
| Button atom
|
|
19
|
-
| Header organism
|
|
17
|
+
| Thing | Name | Tag |
|
|
18
|
+
| -------------------- | ------------- | --------------- |
|
|
19
|
+
| Button atom | `app-button` | `<app-button>` |
|
|
20
|
+
| Header organism | `app-header` | `<app-header>` |
|
|
20
21
|
| Page layout template | `page-layout` | `<page-layout>` |
|
|
21
|
-
| Home page view
|
|
22
|
+
| Home page view | `home-view` | `<home-view>` |
|
|
22
23
|
|
|
23
24
|
The `app-` prefix prevents collisions with native elements and third-party
|
|
24
25
|
components. Page views and templates may drop the prefix when unambiguous.
|
|
25
26
|
|
|
26
27
|
### Files & Directories
|
|
27
28
|
|
|
28
|
-
| Type
|
|
29
|
-
|
|
30
|
-
| Component file
|
|
31
|
-
| Component CSS
|
|
32
|
-
| Component dir
|
|
33
|
-
| Re-export
|
|
34
|
-
| Store model
|
|
35
|
-
| Utility function | camelCase
|
|
36
|
-
| Shared CSS
|
|
29
|
+
| Type | Convention | Example |
|
|
30
|
+
| ---------------- | ----------------- | ------------------------- |
|
|
31
|
+
| Component file | Match tag name | `app-button.js` |
|
|
32
|
+
| Component CSS | Match tag name | `app-button.css` |
|
|
33
|
+
| Component dir | Match tag name | `app-button/` |
|
|
34
|
+
| Re-export | Always `index.js` | `index.js` |
|
|
35
|
+
| Store model | PascalCase | `UserModel.js` |
|
|
36
|
+
| Utility function | camelCase | `formatDate.js` |
|
|
37
|
+
| Shared CSS | Descriptive kebab | `tokens.css`, `reset.css` |
|
|
37
38
|
|
|
38
39
|
### JavaScript
|
|
39
40
|
|
|
40
|
-
| Type
|
|
41
|
-
|
|
42
|
-
| Event handlers
|
|
43
|
-
| Store models
|
|
44
|
-
| Utility functions | camelCase verb
|
|
45
|
-
| Constants
|
|
46
|
-
| JSDoc typedefs
|
|
41
|
+
| Type | Convention | Example |
|
|
42
|
+
| ----------------- | ----------------- | ----------------------------- |
|
|
43
|
+
| Event handlers | `handle` + action | `handleClick`, `handleSubmit` |
|
|
44
|
+
| Store models | PascalCase noun | `UserModel`, `AppState` |
|
|
45
|
+
| Utility functions | camelCase verb | `formatDate`, `parseQuery` |
|
|
46
|
+
| Constants | UPPER_SNAKE | `MAX_RETRIES`, `API_BASE` |
|
|
47
|
+
| JSDoc typedefs | PascalCase | `@typedef {Object} User` |
|
|
47
48
|
|
|
48
49
|
---
|
|
49
50
|
|
|
@@ -52,12 +53,14 @@ components. Page views and templates may drop the prefix when unambiguous.
|
|
|
52
53
|
### ❌ Never Do This
|
|
53
54
|
|
|
54
55
|
**DOM queries inside components:**
|
|
56
|
+
|
|
55
57
|
```javascript
|
|
56
58
|
// BAD — breaks encapsulation, ignores shadow DOM
|
|
57
59
|
const el = document.querySelector('.my-thing');
|
|
58
60
|
```
|
|
59
61
|
|
|
60
62
|
**Manual event listeners:**
|
|
63
|
+
|
|
61
64
|
```javascript
|
|
62
65
|
// BAD — leaks memory, bypasses hybrids lifecycle
|
|
63
66
|
connectedCallback() {
|
|
@@ -66,18 +69,21 @@ connectedCallback() {
|
|
|
66
69
|
```
|
|
67
70
|
|
|
68
71
|
**Global mutable state:**
|
|
72
|
+
|
|
69
73
|
```javascript
|
|
70
74
|
// BAD — invisible dependencies, untraceable bugs
|
|
71
75
|
window.appState = { user: null };
|
|
72
76
|
```
|
|
73
77
|
|
|
74
78
|
**Imperative DOM manipulation:**
|
|
79
|
+
|
|
75
80
|
```javascript
|
|
76
81
|
// BAD — fights the reactive render cycle
|
|
77
82
|
host.shadowRoot.querySelector('span').textContent = 'updated';
|
|
78
83
|
```
|
|
79
84
|
|
|
80
85
|
**Business logic in render:**
|
|
86
|
+
|
|
81
87
|
```javascript
|
|
82
88
|
// BAD — render should be pure projection of state
|
|
83
89
|
render: ({ items }) => html`
|
|
@@ -86,11 +92,13 @@ render: ({ items }) => html`
|
|
|
86
92
|
```
|
|
87
93
|
|
|
88
94
|
**Files over 150 lines:**
|
|
95
|
+
|
|
89
96
|
```
|
|
90
97
|
// BAD — extract to utils/ or split into sub-components
|
|
91
98
|
```
|
|
92
99
|
|
|
93
100
|
**Deep nesting (>3 component levels):**
|
|
101
|
+
|
|
94
102
|
```
|
|
95
103
|
// BAD — flatten by composing at the page level
|
|
96
104
|
<app-layout>
|
|
@@ -109,14 +117,14 @@ let everything else propagate.
|
|
|
109
117
|
|
|
110
118
|
### Boundary Rules
|
|
111
119
|
|
|
112
|
-
| Layer
|
|
113
|
-
|
|
114
|
-
| **Utils**
|
|
115
|
-
| **Store connectors** | Let fetch failures propagate. Hybrids' `store.error()` catches them.
|
|
116
|
-
| **Components**
|
|
117
|
-
| **Event handlers**
|
|
118
|
-
| **Server routes**
|
|
119
|
-
| **Server infra**
|
|
120
|
+
| Layer | Responsibility | Example |
|
|
121
|
+
| -------------------- | ------------------------------------------------------------------------------ | --------------------------------------------------------------------- |
|
|
122
|
+
| **Utils** | Never catch. Return error values or throw. Caller decides. | `formatDate(null)` returns `''` |
|
|
123
|
+
| **Store connectors** | Let fetch failures propagate. Hybrids' `store.error()` catches them. | Don't wrap fetch in try/catch |
|
|
124
|
+
| **Components** | Display `store.pending()` / `store.error()` states. Never try/catch in render. | `${store.error(model) && html`<div class="error-message">...</div>`}` |
|
|
125
|
+
| **Event handlers** | Guard with `store.ready()` before accessing store properties. | `if (!store.ready(host.state)) return;` |
|
|
126
|
+
| **Server routes** | Return HTTP status + JSON error body. Never crash the process. | `res.status(404).json({ error: 'Not found' })` |
|
|
127
|
+
| **Server infra** | Handle process-level errors with clear messages and exit codes. | Port conflict → log message → `process.exit(1)` |
|
|
120
128
|
|
|
121
129
|
### Why This Matters
|
|
122
130
|
|
|
@@ -141,7 +149,7 @@ next layer up to handle what it can't.
|
|
|
141
149
|
// BAD — accesses store properties without ready guard
|
|
142
150
|
function toggle(host) {
|
|
143
151
|
const next = host.state.theme === 'light' ? 'dark' : 'light';
|
|
144
|
-
store.set(host.state, { theme: next });
|
|
152
|
+
store.set(host.state, { theme: next }); // crashes if model is in error state
|
|
145
153
|
}
|
|
146
154
|
```
|
|
147
155
|
|
|
@@ -168,16 +176,19 @@ function toggle(host) {
|
|
|
168
176
|
### ✅ Always Do This
|
|
169
177
|
|
|
170
178
|
**Declarative event binding:**
|
|
179
|
+
|
|
171
180
|
```javascript
|
|
172
|
-
html`<button onclick="${handleClick}">Go</button
|
|
181
|
+
html`<button onclick="${handleClick}">Go</button>`;
|
|
173
182
|
```
|
|
174
183
|
|
|
175
184
|
**Store for shared state:**
|
|
185
|
+
|
|
176
186
|
```javascript
|
|
177
187
|
state: store(AppState),
|
|
178
188
|
```
|
|
179
189
|
|
|
180
190
|
**Pure functions for logic:**
|
|
191
|
+
|
|
181
192
|
```javascript
|
|
182
193
|
// In src/utils/filterActive.js
|
|
183
194
|
export const filterActive = (items) => items.filter(i => i.active);
|
|
@@ -188,6 +199,7 @@ render: ({ items }) => html`<ul>${filterActive(items).map(...)}</ul>`,
|
|
|
188
199
|
```
|
|
189
200
|
|
|
190
201
|
**JSDoc on all exports:**
|
|
202
|
+
|
|
191
203
|
```javascript
|
|
192
204
|
/** @param {User} user */
|
|
193
205
|
export const fullName = (user) => `${user.firstName} ${user.lastName}`;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
# Frontend Implementation Rules
|
|
2
|
+
|
|
2
3
|
## Hybrids.js "No-Build" Web Component Specification
|
|
3
4
|
|
|
4
5
|
> A concise, LLM-and-human-friendly specification for building web applications
|
|
@@ -17,15 +18,15 @@
|
|
|
17
18
|
|
|
18
19
|
**Sub-specifications:**
|
|
19
20
|
|
|
20
|
-
| Document
|
|
21
|
-
|
|
22
|
-
| [COMPONENT_PATTERNS.md](./COMPONENT_PATTERNS.md) | Authoring, light DOM, styling, layout engine, file size rules
|
|
23
|
-
| [JSDOC_TYPING.md](./JSDOC_TYPING.md)
|
|
24
|
-
| [STATE_AND_ROUTING.md](./STATE_AND_ROUTING.md)
|
|
25
|
-
| [CONVENTIONS.md](./CONVENTIONS.md)
|
|
26
|
-
| [SERVER_AND_DEPS.md](./SERVER_AND_DEPS.md)
|
|
27
|
-
| [BACKEND_API_SPEC.md](./BACKEND_API_SPEC.md)
|
|
28
|
-
| [TESTING.md](./TESTING.md)
|
|
21
|
+
| Document | Covers |
|
|
22
|
+
| ------------------------------------------------ | ----------------------------------------------------------------------- |
|
|
23
|
+
| [COMPONENT_PATTERNS.md](./COMPONENT_PATTERNS.md) | Authoring, light DOM, styling, layout engine, file size rules |
|
|
24
|
+
| [JSDOC_TYPING.md](./JSDOC_TYPING.md) | JSDoc typing strategy, tsc validation, component/model/handler patterns |
|
|
25
|
+
| [STATE_AND_ROUTING.md](./STATE_AND_ROUTING.md) | Store, routing, unified app state, realtime sync |
|
|
26
|
+
| [CONVENTIONS.md](./CONVENTIONS.md) | Naming conventions, anti-patterns |
|
|
27
|
+
| [SERVER_AND_DEPS.md](./SERVER_AND_DEPS.md) | Express server, import maps, vendor dependency loading |
|
|
28
|
+
| [BACKEND_API_SPEC.md](./BACKEND_API_SPEC.md) | REST endpoints, JSON Schema via HEAD, entity CRUD, realtime sync |
|
|
29
|
+
| [TESTING.md](./TESTING.md) | Testing philosophy, tools, patterns, build-phase checkpoints |
|
|
29
30
|
|
|
30
31
|
---
|
|
31
32
|
|
|
@@ -36,16 +37,16 @@ no transpiler, no compile step. Code runs exactly as written.
|
|
|
36
37
|
|
|
37
38
|
### Core Principles
|
|
38
39
|
|
|
39
|
-
| Principle
|
|
40
|
-
|
|
41
|
-
| No build tools
|
|
42
|
-
| ES modules only
|
|
43
|
-
| Import maps
|
|
44
|
-
| Small files
|
|
45
|
-
| Explicit over implicit
|
|
46
|
-
| Readable over clever
|
|
47
|
-
| Declarative over imperative
|
|
48
|
-
| Composition over inheritance | Build complex UIs by composing small components
|
|
40
|
+
| Principle | Rule |
|
|
41
|
+
| ---------------------------- | --------------------------------------------------------------------------- |
|
|
42
|
+
| No build tools | No Webpack, Vite, Rollup, esbuild, or Babel |
|
|
43
|
+
| ES modules only | All `.js` files are native ES modules served to the browser |
|
|
44
|
+
| Import maps | Bare specifiers (e.g. `"hybrids"`) resolved via `<script type="importmap">` |
|
|
45
|
+
| Small files | Every file ≤ **150 lines** before extracting shared logic |
|
|
46
|
+
| Explicit over implicit | Name things clearly; avoid magic strings and hidden conventions |
|
|
47
|
+
| Readable over clever | Optimize for comprehension by humans and LLMs alike |
|
|
48
|
+
| Declarative over imperative | Use framework patterns, not raw DOM manipulation |
|
|
49
|
+
| Composition over inheritance | Build complex UIs by composing small components |
|
|
49
50
|
|
|
50
51
|
### What This Means in Practice
|
|
51
52
|
|
|
@@ -72,15 +73,15 @@ web component framework built on plain objects and pure functions.
|
|
|
72
73
|
|
|
73
74
|
### Why Hybrids
|
|
74
75
|
|
|
75
|
-
| Need
|
|
76
|
-
|
|
77
|
-
| Components
|
|
78
|
-
| Templating
|
|
79
|
-
| State
|
|
80
|
-
| Routing
|
|
81
|
-
| Layout
|
|
82
|
-
| Localization | `localize()` — automatic template translation
|
|
83
|
-
| ES modules
|
|
76
|
+
| Need | Hybrids Provides |
|
|
77
|
+
| ------------ | --------------------------------------------------------------- |
|
|
78
|
+
| Components | `define()` — plain object definitions, no classes |
|
|
79
|
+
| Templating | `html``` — tagged template literals with reactive bindings |
|
|
80
|
+
| State | `store()` — global state with async storage, caching, relations |
|
|
81
|
+
| Routing | `router()` — view-graph-based routing with guards and dialogs |
|
|
82
|
+
| Layout | `layout=""` attribute — CSS layout engine in templates |
|
|
83
|
+
| Localization | `localize()` — automatic template translation |
|
|
84
|
+
| ES modules | Ships as raw ES modules in `src/` — no build needed |
|
|
84
85
|
|
|
85
86
|
### API Surface (v9.1)
|
|
86
87
|
|
|
@@ -88,27 +89,27 @@ These are the only imports you need:
|
|
|
88
89
|
|
|
89
90
|
```javascript
|
|
90
91
|
import {
|
|
91
|
-
define,
|
|
92
|
-
html,
|
|
93
|
-
store,
|
|
94
|
-
router,
|
|
95
|
-
mount,
|
|
96
|
-
parent,
|
|
97
|
-
children,
|
|
98
|
-
dispatch,
|
|
99
|
-
msg,
|
|
100
|
-
localize,
|
|
92
|
+
define, // Register a component
|
|
93
|
+
html, // Template tagged literal
|
|
94
|
+
store, // State management
|
|
95
|
+
router, // Routing
|
|
96
|
+
mount, // Mount component on existing element
|
|
97
|
+
parent, // Access parent component
|
|
98
|
+
children, // Access child components
|
|
99
|
+
dispatch, // Dispatch custom events
|
|
100
|
+
msg, // Localization messages
|
|
101
|
+
localize, // Register translations
|
|
101
102
|
} from 'hybrids';
|
|
102
103
|
```
|
|
103
104
|
|
|
104
105
|
### Why Not Alternatives
|
|
105
106
|
|
|
106
|
-
| Alternative
|
|
107
|
-
|
|
108
|
-
| **Lit**
|
|
109
|
-
| **Stencil**
|
|
110
|
-
| **Vanilla**
|
|
111
|
-
| **React/Vue/Svelte** | Require build steps, not native web components
|
|
107
|
+
| Alternative | Reason to pass |
|
|
108
|
+
| -------------------- | ------------------------------------------------------------------ |
|
|
109
|
+
| **Lit** | Class-based, heavier API surface, decorators encourage build tools |
|
|
110
|
+
| **Stencil** | Requires a compiler — violates no-build constraint |
|
|
111
|
+
| **Vanilla** | No state management, no templating — too much boilerplate |
|
|
112
|
+
| **React/Vue/Svelte** | Require build steps, not native web components |
|
|
112
113
|
|
|
113
114
|
---
|
|
114
115
|
|
|
@@ -185,12 +186,12 @@ import direction: **higher tiers import from lower tiers, never the reverse.**
|
|
|
185
186
|
|
|
186
187
|
### Tiers
|
|
187
188
|
|
|
188
|
-
| Tier
|
|
189
|
-
|
|
190
|
-
| **Atom**
|
|
191
|
-
| **Molecule** | `components/molecules/` | Small composition of 2–4 atoms that form a reusable unit.
|
|
189
|
+
| Tier | Location | Scope | Examples |
|
|
190
|
+
| ------------ | ----------------------- | ------------------------------------------------------------------ | --------------------------------------- |
|
|
191
|
+
| **Atom** | `components/atoms/` | Single-purpose UI primitive. One element, one job. | `app-button`, `app-icon`, `app-input` |
|
|
192
|
+
| **Molecule** | `components/molecules/` | Small composition of 2–4 atoms that form a reusable unit. | `nav-link`, `search-bar`, `form-field` |
|
|
192
193
|
| **Organism** | `components/organisms/` | Complex UI section. May contain molecules, atoms, and local state. | `app-header`, `app-footer`, `user-card` |
|
|
193
|
-
| **Template** | `components/templates/` | Page-level layout shell. Defines slot regions, no business logic.
|
|
194
|
+
| **Template** | `components/templates/` | Page-level layout shell. Defines slot regions, no business logic. | `page-layout`, `dashboard-layout` |
|
|
194
195
|
|
|
195
196
|
**Pages** (`src/pages/`) sit outside the component hierarchy. They are
|
|
196
197
|
route-bound views that compose templates and organisms.
|
|
@@ -214,13 +215,13 @@ Pages → Templates → Organisms → Molecules → Atoms
|
|
|
214
215
|
|
|
215
216
|
Use this checklist to decide if a component belongs at a higher tier:
|
|
216
217
|
|
|
217
|
-
| Signal
|
|
218
|
-
|
|
219
|
-
| It renders a single HTML element with props
|
|
220
|
-
| It composes 2–4 atoms into a reusable group
|
|
221
|
-
| It has its own local state or fetches data
|
|
222
|
-
| It defines layout regions via slots, no logic
|
|
223
|
-
| It's bound to a route and composes a full page | Put it in **pages/**
|
|
218
|
+
| Signal | Action |
|
|
219
|
+
| ---------------------------------------------- | ----------------------- |
|
|
220
|
+
| It renders a single HTML element with props | Keep as **atom** |
|
|
221
|
+
| It composes 2–4 atoms into a reusable group | Make it a **molecule** |
|
|
222
|
+
| It has its own local state or fetches data | Promote to **organism** |
|
|
223
|
+
| It defines layout regions via slots, no logic | Make it a **template** |
|
|
224
|
+
| It's bound to a route and composes a full page | Put it in **pages/** |
|
|
224
225
|
|
|
225
226
|
### File Anatomy (All Tiers)
|
|
226
227
|
|
|
@@ -235,5 +236,3 @@ app-button/
|
|
|
235
236
|
|
|
236
237
|
If a component needs helpers that push it past 150 lines, extract them to
|
|
237
238
|
`src/utils/` — not into sibling files within the component directory.
|
|
238
|
-
|
|
239
|
-
|
|
@@ -28,12 +28,12 @@ npx clearstack init -y # non-interactive (defaults)
|
|
|
28
28
|
|
|
29
29
|
The interactive prompt asks for:
|
|
30
30
|
|
|
31
|
-
| Prompt
|
|
32
|
-
|
|
33
|
-
| Project name | current directory name | Used in package.json and templates
|
|
34
|
-
| Description
|
|
35
|
-
| Mode
|
|
36
|
-
| Port
|
|
31
|
+
| Prompt | Default | Notes |
|
|
32
|
+
| ------------ | ---------------------- | --------------------------------------------------------------------------------- |
|
|
33
|
+
| Project name | current directory name | Used in package.json and templates |
|
|
34
|
+
| Description | `A Clearstack project` | Goes into package.json |
|
|
35
|
+
| Mode | — | **Fullstack**: Express + WebSocket + JSON DB + SSE. **Static**: localStorage only |
|
|
36
|
+
| Port | `3000` | Fullstack only. Set in `.env` |
|
|
37
37
|
|
|
38
38
|
If a `package.json` already exists, Clearstack merges into it — your existing fields (`author`, `license`, `engines`, `keywords`, etc.) are preserved.
|
|
39
39
|
|
|
@@ -120,10 +120,12 @@ git diff docs/ .configs/ # review what changed
|
|
|
120
120
|
```
|
|
121
121
|
|
|
122
122
|
This updates:
|
|
123
|
+
|
|
123
124
|
- `docs/clearstack/*.md` — spec documentation
|
|
124
125
|
- `.configs/*` — linter, formatter, type checker, test runner configs
|
|
125
126
|
|
|
126
127
|
This never touches:
|
|
128
|
+
|
|
127
129
|
- `docs/app-spec/` — your project specs
|
|
128
130
|
- `src/` — your code
|
|
129
131
|
- `scripts/` — your build scripts
|
|
@@ -163,15 +165,15 @@ spec:code → spec:docs → lint → format → typecheck → test
|
|
|
163
165
|
|
|
164
166
|
## Summary
|
|
165
167
|
|
|
166
|
-
| Task
|
|
167
|
-
|
|
168
|
-
| Install Clearstack
|
|
169
|
-
| Scaffold a project
|
|
170
|
-
| Install dependencies
|
|
171
|
-
| Start dev server
|
|
172
|
-
| Lint + format
|
|
173
|
-
| Type check
|
|
174
|
-
| Run tests
|
|
175
|
-
| Full spec check
|
|
176
|
-
| Update spec + configs | `npm run spec:update`
|
|
177
|
-
| Review spec changes
|
|
168
|
+
| Task | Command |
|
|
169
|
+
| --------------------- | -------------------------------------- |
|
|
170
|
+
| Install Clearstack | `npm install -D @techninja/clearstack` |
|
|
171
|
+
| Scaffold a project | `npx clearstack init` |
|
|
172
|
+
| Install dependencies | `npm install` |
|
|
173
|
+
| Start dev server | `npm run dev` / `npx serve public` |
|
|
174
|
+
| Lint + format | `npm run lint:fix && npm run format` |
|
|
175
|
+
| Type check | `npm run typecheck` |
|
|
176
|
+
| Run tests | `npm test` |
|
|
177
|
+
| Full spec check | `npm run spec` |
|
|
178
|
+
| Update spec + configs | `npm run spec:update` |
|
|
179
|
+
| Review spec changes | `git diff docs/ .configs/` |
|