@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.
@@ -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.
@@ -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 type validation
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 typecheck` |
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
- Lives in `src/utils/realtimeSync.js`:
414
-
415
- ```javascript
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
- `store.clear(Model)` fully invalidates the cache for that model type.
437
- Any component bound to the model via `store()` will automatically re-fetch
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
- For the local user, form submit handlers also call `store.clear(Model)`
442
- immediately after a successful save. The SSE event that follows is
443
- redundant for the local user but ensures other connected clients update.
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's `connect` descriptor:
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
- import { connectRealtime } from '../utils/realtimeSync.js';
456
- import UserModel from '../store/UserModel.js';
457
-
458
- export default define({
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
- render: ({ stack }) => html` <template layout="column height::100vh">${stack}</template> `,
471
- });
477
+ },
472
478
  ```
473
479
 
474
- When the backend sends `{ type: "user", id: "42" }` over SSE, the store
475
- cache for `UserModel` is cleared, and any component displaying that user
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