@mhmo91/schmancy 0.10.9 → 0.10.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/src/CLAUDE.md CHANGED
@@ -1,20 +1,36 @@
1
- # Schmancy Library Source Patterns
1
+ # Schmancy library source — agent brief
2
2
 
3
- ## Component Architecture
3
+ This file lives in the source tree. It tells you what's specific to
4
+ **authoring schmancy components**. For the public surface (every tag,
5
+ every service, every convention downstream consumers see), the
6
+ authoritative docs are in `skills/schmancy/`. Cross-link rather than
7
+ duplicate — those docs are checked into the npm tarball and rendered
8
+ by the Claude Code plugin. Drift here means drift everywhere.
4
9
 
5
- ### Base Mixin Pattern
6
- All components extend `$LitElement(style?)` which provides:
7
- - `disconnecting` Subject for RxJS cleanup via `takeUntil(this.disconnecting)`
8
- - `classMap()` - enhanced to split space-separated keys
9
- - `styleMap()` - direct passthrough to Lit directive
10
- - `discover<T>(tag: string)` - event-based component discovery
11
- - Auto-response to `{tagName}-where-are-you` events
10
+ ## Where to read first
11
+
12
+ - `skills/schmancy/INDEX.md` full catalog of public tags and services.
13
+ - `skills/schmancy/SKILL.md` — non-negotiable conventions every consumer follows.
14
+ - `skills/schmancy/mixins.md` `SchmancyElement` base class (what every component extends).
15
+ - `skills/schmancy/state.md` — `state()` factory + `<schmancy-context>` scoping.
16
+ - `skills/schmancy/overlay.md` `show()` + `confirm()` + `prompt()`; the only overlay primitives.
17
+ - `skills/schmancy/directives.md` — Lit + schmancy directives.
18
+
19
+ ## Source-author rules (not in the consumer docs)
20
+
21
+ ### Component skeleton
22
+
23
+ Every concrete component:
12
24
 
13
- ### Component Registration
14
25
  ```typescript
26
+ import { SchmancyElement } from '@mixins/index'
27
+ import { customElement } from 'lit/decorators.js'
28
+ import { css, html } from 'lit'
29
+
15
30
  @customElement('schmancy-{name}')
16
- export class Schmancy{Name} extends $LitElement(style) {
17
- // Component implementation
31
+ export class Schmancy{Name} extends SchmancyElement {
32
+ static styles = [css`:host { display: block }`]
33
+ render() { return html`<slot></slot>` }
18
34
  }
19
35
 
20
36
  declare global {
@@ -24,396 +40,132 @@ declare global {
24
40
  }
25
41
  ```
26
42
 
27
- ## Styling Approach
43
+ - Never extend raw `LitElement`.
44
+ - Never wrap with `SignalWatcher` — `SchmancyElement` already does. Double-wrapping panics with "Detected cycle in computations" at runtime; the `NO_SIGNAL_WATCHER_WRAP` pre-edit lint rule blocks it.
45
+ - The `$LitElement(style?)` factory in `mixins/litElement.mixin.ts` is a deprecated alias kept for the migration window. **Do not use it in new files.** The `PREFER_SCHMANCY_ELEMENT` pre-edit lint rule flags new uses; the migration section in `skills/schmancy/mixins.md` documents the rewrite.
46
+ - Register the tag in `HTMLElementTagNameMap` so consumers get TypeScript completion on `<schmancy-{name}>`.
47
+ - Export through `src/{name}/index.ts` and surface from `src/index.ts`.
28
48
 
29
- ### Inline SCSS Import
30
- ```typescript
31
- import style from './component.scss?inline'
32
- export class Component extends $LitElement(style) {}
33
- ```
34
- - Components with complex styling use `.scss` files
35
- - Simple components use inline CSS template literals
36
- - All Tailwind classes work via TailwindMixin in base
49
+ ### Styling
37
50
 
38
- ### Tailwind Integration
39
- - Theme CSS variables: `--schmancy-sys-color-{path}`
40
- - Tailwind config maps to theme tokens
41
- - Use Tailwind utility classes directly in templates
51
+ Two patterns, both end up in `static styles`:
42
52
 
43
- ## State Management
44
-
45
- ### RxJS Patterns
46
53
  ```typescript
47
- // Subject for internal state
48
- private _value$ = new BehaviorSubject<T>(initial)
54
+ // Simple: inline css template literal
55
+ static styles = [css`:host { display: block }`]
49
56
 
50
- // Combine streams and auto-cleanup
51
- combineLatest([stream1$, stream2$])
52
- .pipe(takeUntil(this.disconnecting))
53
- .subscribe(...)
57
+ // Complex: external SCSS via Vite ?inline import
58
+ import style from './component.scss?inline'
59
+ static styles = [unsafeCSS(style)] // unsafeCSS converts the string → CSSResult
54
60
  ```
55
61
 
56
- ### Property Binding with Display Sync
57
- For form components (select, autocomplete, chips):
58
- 1. Track explicit property setting with flags: `_valueSet`, `_valuesSet`
59
- 2. Implement `_updateInputDisplay()` to sync label with value
60
- 3. Call in `firstUpdated()` for initial sync
61
- 4. Call when value changes programmatically
62
+ Tailwind utilities work in templates without import — `SchmancyElement`
63
+ injects the project Tailwind sheet into every shadow root. `static
64
+ styles` only carries component-local CSS.
62
65
 
63
- ### Context/Store Pattern
64
- ```typescript
65
- // Create typed context
66
- const Context = createContext<T>(initial, 'local|memory|indexeddb', 'key')
67
-
68
- // Use in component
69
- @select(Context)
70
- property!: T
71
-
72
- // Or create compound selectors
73
- const selector = createCompoundSelector(
74
- [Context1, Context2],
75
- [a => a.field, b => b.field],
76
- (val1, val2) => ({ combined: val1 + val2 })
77
- )
78
- ```
66
+ ### Slot content processing
79
67
 
80
- ## Event Patterns
68
+ Components that consume slotted children (select, autocomplete, chips,
69
+ menu) use `@queryAssignedElements` and wire handlers in `firstUpdated`:
81
70
 
82
- ### Custom Event Types
83
71
  ```typescript
84
- export type ComponentChangeEvent = CustomEvent<{
85
- value: string
86
- additionalData?: any
87
- }>
72
+ @queryAssignedElements({ flatten: true })
73
+ private _options!: SchmancyOption[]
88
74
 
89
- // Dispatch
90
- this.dispatchEvent(
91
- new CustomEvent<ComponentChangeEvent['detail']>('change', {
92
- detail: { value: this.value },
93
- bubbles: true,
94
- composed: true,
75
+ firstUpdated() {
76
+ this._options.forEach((option, index) => {
77
+ option.tabIndex = -1
78
+ if (!option.id) option.id = `${this.id}-option-${index}`
95
79
  })
96
- )
97
- ```
98
-
99
- ### Discovery Events
100
- Components respond to `{tag}-where-are-you` by emitting `{tag}-here-i-am` with self reference.
101
-
102
- ## Accessibility Patterns
103
-
104
- ### Form Components
105
- ```typescript
106
- // ARIA attributes
107
- role="combobox"
108
- aria-haspopup="listbox"
109
- aria-expanded=${this._open}
110
- aria-controls="listbox-id"
111
-
112
- // Screen reader announcements
113
- private _announceToScreenReader(message: string) {
114
- const liveRegion = this.shadowRoot?.querySelector('#live-status')
115
- if (liveRegion) liveRegion.textContent = message
116
80
  }
117
-
118
- // Live region in template
119
- <div id="live-status" role="status" aria-live="polite" class="sr-only"></div>
120
- ```
121
-
122
- ### Focus Management
123
- ```typescript
124
- protected static shadowRootOptions = {
125
- ...LitElement.shadowRootOptions,
126
- mode: 'open',
127
- delegatesFocus: true, // Auto-focus first focusable element
128
- }
129
- ```
130
-
131
- ## Lit Directives - Complete Reference
132
-
133
- ### Core Principles
134
- - Import only directives you use (modular design prevents bundle bloat)
135
- - Directives are functions that customize rendering behavior
136
- - Each directive is a separate module from `lit/directives/`
137
-
138
- ### Conditional Rendering Directives
139
-
140
- **`when(condition, trueCase, falseCase?)`** - Best for clean inline conditionals
141
- ```typescript
142
- import { when } from 'lit/directives/when.js'
143
- ${when(this.isExpanded, () => html`<div>Content</div>`, () => html`<div>Summary</div>`)}
144
- ```
145
- - Cleaner than ternary operators
146
- - Lazy evaluation of templates
147
- - Use when readability matters
148
-
149
- **`choose(value, cases, default?)`** - Template-level switch statement
150
- ```typescript
151
- import { choose } from 'lit/directives/choose.js'
152
- ${choose(this.state, [
153
- ['loading', () => html`<spinner></spinner>`],
154
- ['error', () => html`<error-msg></error-msg>`],
155
- ['success', () => html`<content></content>`]
156
- ])}
157
- ```
158
- - Strict equality matching
159
- - Better than nested ternaries
160
-
161
- **`ifDefined(value)`** - Conditional attributes
162
- ```typescript
163
- import { ifDefined } from 'lit/directives/if-defined.js'
164
- <img src=${ifDefined(this.imageUrl)}>
165
- ```
166
- - Sets attribute only when value is defined
167
- - Essential for URL attributes where undefined should prevent rendering
168
-
169
- ### List Rendering Directives
170
-
171
- **`repeat(items, keyFn?, templateFn)`** - **USE THIS FOR ALL LISTS**
172
- ```typescript
173
- import { repeat } from 'lit/directives/repeat.js'
174
- ${repeat(
175
- this.items,
176
- (item) => item.id, // Key function for DOM stability
177
- (item, index) => html`<div>${item.name}</div>`
178
- )}
179
- ```
180
- - **MANDATORY for Schmancy**: Enables DOM diffing and stability
181
- - Maintains DOM node association during list updates
182
- - Most efficient for insertions/removals/reordering
183
- - Prevents unnecessary re-renders
184
-
185
- **`map(items, templateFn)`** - Simple iteration (use sparingly)
186
- ```typescript
187
- import { map } from 'lit/directives/map.js'
188
- ${map(this.items, (item) => html`<div>${item}</div>`)}
189
- ```
190
- - Smaller and faster than repeat, but NO keying
191
- - Use ONLY when list never changes order
192
- - Prefer `repeat` in 99% of cases
193
-
194
- **`join(items, joiner)`** - Interleave with separator
195
- ```typescript
196
- import { join } from 'lit/directives/join.js'
197
- ${join(this.tags.map(t => html`<span>${t}</span>`), html`, `)}
198
- ```
199
-
200
- **`range(start, end?, step?)`** - Sequential integers
201
- ```typescript
202
- import { range } from 'lit/directives/range.js'
203
- ${map(range(5), i => html`<item>${i}</item>`)}
204
- ```
205
-
206
- ### Performance Optimization Directives
207
-
208
- **`cache(value)`** - **CRITICAL FOR TEMPLATE SWITCHING**
209
- ```typescript
210
- import { cache } from 'lit/directives/cache.js'
211
- ${cache(
212
- this.view === 'detail'
213
- ? html`<detail-view>${content}</detail-view>`
214
- : html`<summary-view>${summary}</summary-view>`
215
- )}
216
- ```
217
- - Preserves DOM nodes when switching between templates
218
- - Avoids re-creation costs for large, complex templates
219
- - **USE WHEN:** Frequently toggling between views
220
- - **AVOID WHEN:** Templates are simple or rarely toggle
221
- - **PITFALL:** Cached DOM retains internal state (form values, scroll position)
222
- - **WITH REFS:** Refs remain valid across switches since DOM is preserved
223
- - **MEMORY:** Trades memory for rendering speed
224
-
225
- **`guard(dependencies, valueFn)`** - Prevents unnecessary computation
226
- ```typescript
227
- import { guard } from 'lit/directives/guard.js'
228
- ${guard([this.data], () => this.expensiveCalculation(this.data))}
229
- ```
230
- - Only re-runs when dependency **identity** changes
231
- - Perfect for immutable data patterns
232
- - Implements memoization at template level
233
- - Use for expensive calculations, transformations, hashing
234
-
235
- **`keyed(key, value)`** - Forces DOM recreation on key change
236
- ```typescript
237
- import { keyed } from 'lit/directives/keyed.js'
238
- ${keyed(this.userId, html`<user-profile .user=${this.user}></user-profile>`)}
239
- ```
240
- - Removes old DOM before rendering new value
241
- - Useful for clearing element state or resetting animations
242
- - Opposite of cache - forces fresh DOM
243
-
244
- ### DOM Reference Directives
245
-
246
- **`ref(refObject)`** - Access rendered elements
247
- ```typescript
248
- import { createRef, ref, Ref } from 'lit/directives/ref.js'
249
- private containerRef: Ref<HTMLDivElement> = createRef()
250
-
251
- // In template
252
- <div ${ref(this.containerRef)}></div>
253
-
254
- // Access via
255
- this.containerRef.value?.focus()
256
81
  ```
257
- - Enables imperative DOM manipulation
258
- - Use for focus management, third-party library integration
259
- - Callback form available: `ref((el) => { /* use el */ })`
260
-
261
- ### State Synchronization Directives
262
82
 
263
- **`live(value)`** - Compares against live DOM value
264
- ```typescript
265
- import { live } from 'lit/directives/live.js'
266
- <input .value=${live(this.value)}>
267
- ```
268
- - Critical for input elements that modify their own state
269
- - Compares against actual DOM value, not last-rendered value
270
- - Use with strict equality checks
271
- - Essential for contenteditable or custom elements with external state
83
+ ### RxJS internals
272
84
 
273
- ### Asynchronous Rendering Directives
85
+ Long-lived component state is a `BehaviorSubject` plus a derived stream:
274
86
 
275
- **`until(...values)`** - Renders while promises resolve
276
87
  ```typescript
277
- import { until } from 'lit/directives/until.js'
278
- ${until(
279
- fetch('/api/data').then(r => r.json()),
280
- html`<spinner></spinner>` // Placeholder
281
- )}
282
- ```
283
- - Highest-priority promises render on resolution
284
- - Lower-priority values show during pending states
88
+ private _value$ = new BehaviorSubject<T>(initial)
285
89
 
286
- **`asyncAppend(asyncIterable)`** - Append values as they yield
287
- ```typescript
288
- import { asyncAppend } from 'lit/directives/async-append.js'
289
- ${asyncAppend(this.streamData(), (item) => html`<div>${item}</div>`)}
90
+ connectedCallback() {
91
+ super.connectedCallback()
92
+ combineLatest([this._value$, this._open$])
93
+ .pipe(takeUntil(this.disconnecting))
94
+ .subscribe(([value, open]) => { /* … */ })
95
+ }
290
96
  ```
291
97
 
292
- **`asyncReplace(asyncIterable)`** - Replace with each new value
293
- ```typescript
294
- import { asyncReplace } from 'lit/directives/async-replace.js'
295
- ${asyncReplace(this.counter(), (count) => html`<div>${count}</div>`)}
296
- ```
98
+ - Every subscription ends with `.pipe(takeUntil(this.disconnecting))`.
99
+ - Form components (`select`, `autocomplete`, `chips`) track explicit
100
+ property assignment with flags (`_valueSet`, `_valuesSet`) and
101
+ resync the visible label via `_updateInputDisplay()` in
102
+ `firstUpdated()` and on every programmatic value change.
297
103
 
298
- ### Unsafe Content Directives (Use with Caution)
104
+ ### Custom events
299
105
 
300
- **`unsafeHTML(string)`** - Render trusted HTML strings
301
- ```typescript
302
- import { unsafeHTML } from 'lit/directives/unsafe-html.js'
303
- ${unsafeHTML(this.trustedContent)}
304
- ```
305
- - **ONLY for developer-controlled content**
306
- - Enables XSS, CSS injection if misused
307
- - Use for database-stored HTML from trusted sources
106
+ Type the detail and dispatch with `bubbles: true, composed: true`:
308
107
 
309
- **`unsafeSVG(string)`** - Render trusted SVG strings
310
108
  ```typescript
311
- import { unsafeSVG } from 'lit/directives/unsafe-svg.js'
312
- ${unsafeSVG(this.svgContent)}
313
- ```
314
- - Same security constraints as unsafeHTML
109
+ export type SchmancyChangeEvent = CustomEvent<{ value: string }>
315
110
 
316
- **`templateContent(templateElement)`** - Clone from template elements
317
- ```typescript
318
- import { templateContent } from 'lit/directives/template-content.js'
319
- ${templateContent(this.shadowRoot.querySelector('template'))}
111
+ this.dispatchEvent(
112
+ new CustomEvent<SchmancyChangeEvent['detail']>('change', {
113
+ detail: { value: this.value },
114
+ bubbles: true,
115
+ composed: true,
116
+ }),
117
+ )
320
118
  ```
321
119
 
322
- ### Schmancy Best Practices
120
+ ### Accessibility
323
121
 
324
- 1. **Always use `repeat` with key functions** for lists - prevents bugs and improves performance
325
- 2. **Use `cache` for view switching** (expanded/minimized, tabs, modals)
326
- 3. **Apply `guard` with immutable data** for expensive transformations
327
- 4. **Use `when` over ternaries** for readability
328
- 5. **Use `ref` for animations** and imperative DOM access
329
- 6. **Combine `cache` + `ref` carefully** - refs stay valid, but ensure proper lifecycle
330
- 7. **Use `live` for form inputs** that modify themselves
331
- 8. **Avoid `map`** unless you're certain list order never changes
332
-
333
- ### Common Patterns in Schmancy
334
-
335
- **List with stable DOM:**
336
- ```typescript
337
- ${repeat(
338
- this.employees,
339
- (e) => e.id,
340
- (e, i) => html`<employee-card .employee=${e}></employee-card>`
341
- )}
342
- ```
122
+ Form components carry the ARIA combobox pattern (`role="combobox"`,
123
+ `aria-haspopup="listbox"`, `aria-expanded`, `aria-controls`) and a
124
+ visually-hidden live region for status announcements:
343
125
 
344
- **Expensive calculation with caching:**
345
- ```typescript
346
- ${guard([this.data], () => this.calculateComplexStats(this.data))}
126
+ ```html
127
+ <div id="live-status" role="status" aria-live="polite" class="sr-only"></div>
347
128
  ```
348
129
 
349
- **View switching with cache:**
350
- ```typescript
351
- ${cache(
352
- this.isExpanded
353
- ? html`<expanded-content ${ref(this.contentRef)}></expanded-content>`
354
- : html``
355
- )}
356
- ```
130
+ Use `delegatesFocus: true` in `shadowRootOptions` for components whose
131
+ internal first-focusable element should receive focus when the host
132
+ does:
357
133
 
358
- **Animation target with ref:**
359
134
  ```typescript
360
- private iconRef: Ref<HTMLElement> = createRef()
361
- <schmancy-icon ${ref(this.iconRef)}>close</schmancy-icon>
362
- // Later: this.iconRef.value?.animate(...)
135
+ protected static shadowRootOptions = {
136
+ ...LitElement.shadowRootOptions,
137
+ mode: 'open',
138
+ delegatesFocus: true,
139
+ }
363
140
  ```
364
141
 
365
- ## Common Utilities
142
+ ### State within a component
366
143
 
367
- ### Slot Content Processing
368
- ```typescript
369
- @queryAssignedElements({ flatten: true })
370
- private _options!: SchmancyOption[]
144
+ Module-scoped reactive state lives on `state(...)` from
145
+ `@mhmo91/schmancy/state` — see `skills/schmancy/state.md`. Inside a
146
+ component instance, use `@state` (Lit) for private template-driving
147
+ fields and `@property` for public attributes. There is no
148
+ `createContext` / `@select` / `createCompoundSelector`; those v1 APIs
149
+ were removed and replaced wholesale by `state()` / `@observe` /
150
+ `computed`. The migration cheatsheet is `src/state/MIGRATION.md`.
371
151
 
372
- // Setup handlers in firstUpdated()
373
- this._options.forEach((option, index) => {
374
- option.tabIndex = -1
375
- if (!option.id) option.id = `${this.id}-option-${index}`
376
- // Add event listeners
377
- })
378
- ```
379
-
380
- ### Debouncing & Filtering
381
- ```typescript
382
- this._inputValue$.pipe(
383
- distinctUntilChanged(),
384
- debounceTime(this.debounceMs),
385
- tap(value => this._handleChange(value)),
386
- takeUntil(this.disconnecting)
387
- ).subscribe()
388
- ```
152
+ ### Theme consumption
389
153
 
390
- ## Theme Integration
154
+ Components that need theme tokens consume the Lit context:
391
155
 
392
- Components consuming theme:
393
156
  ```typescript
394
157
  @consume({ context: themeContext })
395
158
  theme!: Partial<TSchmancyTheme>
396
159
  ```
397
160
 
398
- Theme CSS custom properties are auto-generated from theme object structure as `--schmancy-{path}`.
161
+ Theme CSS variables are auto-generated as `--schmancy-{path}`. Prefer
162
+ the Tailwind shortcut utilities (`bg-surface-default`,
163
+ `text-error-default`, `border-outline-variant`) over raw
164
+ `var(--schmancy-sys-color-X)` references — the token map lives in
165
+ `skills/schmancy/theme.md § Tailwind utilities`.
399
166
 
400
- ## Area Router (Navigation)
167
+ ## Validation patterns (form components)
401
168
 
402
- ```typescript
403
- // Navigate programmatically
404
- area.push({
405
- area: 'main',
406
- component: MyComponent,
407
- params?: { id: '123' }
408
- })
409
-
410
- // Lazy load
411
- const LazyComponent = lazy(() => import('./component'))
412
- ```
413
-
414
- ## Testing Helpers
415
-
416
- ### Value Validation
417
169
  ```typescript
418
170
  public checkValidity(): boolean {
419
171
  if (!this.required) return true
@@ -426,3 +178,9 @@ public reportValidity(): boolean {
426
178
  return this._inputElementRef.value?.reportValidity() ?? this.checkValidity()
427
179
  }
428
180
  ```
181
+
182
+ ## Pointers
183
+
184
+ - **State module brief:** `src/state/CLAUDE.md` — invariants for code under `src/state/`.
185
+ - **Migration off v1 contexts:** `src/state/MIGRATION.md`.
186
+ - **Lab acceptance criterion:** `lab/README.md` — what does and doesn't belong in `@mhmo91/schmancy-lab`.
@@ -19,7 +19,7 @@ A reactive state primitive for the schmancy library:
19
19
  needed AS a class field (event handlers, derived methods, DevTools).
20
20
  - `bindState(host, source)` — `ReactiveController` helper. Same
21
21
  guarantees as `@observe`, no decorator. Use when the host isn't a
22
- `$LitElement` subclass.
22
+ `SchmancyElement` subclass.
23
23
  - `stateFromObservable(observable, namespace, initial)` — bridges an
24
24
  RxJS source into a `state()`.
25
25
 
@@ -28,13 +28,13 @@ type exports.
28
28
 
29
29
  ## Default subscription pattern
30
30
 
31
- `$LitElement()` already composes `SignalWatcher` over its mixin chain.
31
+ `SchmancyElement` already composes `SignalWatcher` over its mixin chain.
32
32
  Every signal read inside `render()` auto-tracks. **The default consumer
33
33
  needs zero binding code:**
34
34
 
35
35
  ```ts
36
36
  @customElement('cart-view')
37
- class CartView extends $LitElement() {
37
+ class CartView extends SchmancyElement {
38
38
  render() { return html`Items: ${cart.value.items.length}` }
39
39
  }
40
40
  ```
@@ -59,25 +59,33 @@ the element auto-resolve to a per-element isolated copy via the
59
59
  Two infrastructure files own the mechanics:
60
60
 
61
61
  - `state/active-host.ts` — `_activeHost` Variable + `Promise.then`
62
- patch + `resolveActiveHost()` 4-tier fallback (stack →
63
- `window.event` → `document.activeElement` → undefined). Hand-rolled
64
- TC39 AsyncContext.Variable polyfill (~30 lines). The patch is
65
- idempotent and uses `_origThen.call(this, )` so Promise subclassing
66
- / `Symbol.species` are untouched. Decommissions the day a real
62
+ patch + `_publishEventHost(node)` slot + `resolveActiveHost()`
63
+ 4-tier fallback (stack → event-host slot → `document.activeElement`
64
+ → undefined). Hand-rolled TC39 AsyncContext.Variable polyfill
65
+ (~30 lines for the Promise patch alone). The patch is idempotent and
66
+ uses `_origThen.call(this, …)` so Promise subclassing /
67
+ `Symbol.species` are untouched. Decommissions the day a real
67
68
  polyfill or native AsyncContext lands — drop the file, swap the
68
69
  `_activeHost` export.
69
70
  - `state/schmancy-context.ts` — the `<schmancy-context>` element. One
70
71
  `ContextProvider` per state in `provides`; all destroyed on
71
- disconnect.
72
-
73
- `mixins/SchmancyElement.ts` carries the integration points: prototype-
74
- chain wrap of every concrete subclass at first construction (caches in
75
- a `WeakSet`), and `addEventListener` / `removeEventListener` overrides
76
- that wrap host listeners.
77
-
78
- Inline arrow handlers in templates (`@click=${() => …}`) are not
79
- wrapped they attach to child elements, not the host. They resolve
80
- via the `window.event.composedPath()` fallback.
72
+ disconnect. Also installs capture-phase listeners on itself for
73
+ ~18 common event types and calls `_publishEventHost(target)` from
74
+ each that's how inline arrow handlers attached to descendants
75
+ resolve to the closest enclosing context.
76
+
77
+ `mixins/SchmancyElement.ts` carries the host-side integration points:
78
+ prototype-chain wrap of every concrete subclass at first construction
79
+ (caches in a `WeakSet`), and `addEventListener` / `removeEventListener`
80
+ overrides that wrap host listeners.
81
+
82
+ **Known limitation — native `await`.** V8's await optimization (since
83
+ 7.x) skips the spec-prescribed `Promise.resolve(x).then(continuation)`
84
+ step, so the Promise.then patch does not see the resumption of an
85
+ `await` on a native Promise. Class methods that mutate state across an
86
+ `await` boundary fall back to the module-scoped global. To preserve
87
+ the host across awaits, keep the mutation in the synchronous prelude
88
+ before the first `await`, or chain explicitly with `.then(...)`.
81
89
 
82
90
  ## Rules for code in this directory
83
91