@mhmo91/schmancy 0.10.10 → 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/dist/agent/schmancy.agent.js.map +1 -1
- package/dist/handover/agent-runtime-followups.md +1 -1
- package/dist/handover/agent-runtime-v1.md +3 -3
- package/dist/handover/claude-design-brief.md +86 -46
- package/dist/handover/claude-design-setup.md +11 -7
- package/dist/handover/schmancy-token-reference.md +12 -6
- package/dist/skills/INDEX.md +1 -1
- package/dist/skills/SKILL.md +7 -6
- package/dist/skills/audio.md +1 -1
- package/dist/skills/discovery.md +3 -3
- package/dist/skills/menu.md +1 -1
- package/dist/skills/overlay.md +1 -1
- package/dist/skills/schmancy/INDEX.md +1 -1
- package/dist/skills/schmancy/SKILL.md +7 -6
- package/dist/skills/schmancy/audio.md +1 -1
- package/dist/skills/schmancy/discovery.md +3 -3
- package/dist/skills/schmancy/menu.md +1 -1
- package/dist/skills/schmancy/overlay.md +1 -1
- package/dist/skills/schmancy/state.md +42 -22
- package/dist/skills/state.md +42 -22
- package/dist/state-BusMG6sM.js.map +1 -1
- package/dist/state-DNdCPITt.cjs.map +1 -1
- package/package.json +1 -1
- package/skills/schmancy/INDEX.md +1 -1
- package/skills/schmancy/SKILL.md +7 -6
- package/skills/schmancy/audio.md +1 -1
- package/skills/schmancy/discovery.md +3 -3
- package/skills/schmancy/menu.md +1 -1
- package/skills/schmancy/overlay.md +1 -1
- package/skills/schmancy/state.md +42 -22
- package/src/CLAUDE.md +112 -354
- package/src/state/CLAUDE.md +26 -18
- package/src/state/SCOPING.md +23 -9
package/package.json
CHANGED
package/skills/schmancy/INDEX.md
CHANGED
|
@@ -8,7 +8,7 @@ The framework pieces — touch before components.
|
|
|
8
8
|
|
|
9
9
|
- [Area](./area.md) — `<schmancy-area>`, `<schmancy-route>`, `area.push()`, `lazy()` for routing.
|
|
10
10
|
- [State](./state.md) — `state()` factory (memory / session / local / idb), variant write APIs (`Object` / `Map` / `Set` / `Array` / `Scalar`), `bindState`, `computed`, `stateFromObservable`.
|
|
11
|
-
- [Mixins](./mixins.md) —
|
|
11
|
+
- [Mixins](./mixins.md) — `SchmancyElement` base class.
|
|
12
12
|
- [Theme](./theme.md) — `<schmancy-theme>`, color scheme, CSS variables.
|
|
13
13
|
- [Directives](./directives.md) — Lit directives for physics, effects, text, visibility, interaction.
|
|
14
14
|
- [Animation](./animation.md) — Spring presets (`SPRING_SMOOTH`, etc.), `createAnimation`.
|
package/skills/schmancy/SKILL.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: schmancy
|
|
3
|
-
description: UI patterns, component APIs, and conventions for the @mhmo91/schmancy web-component library (Lit + RxJS + Tailwind) — the exclusive UI stack in this repo's `web/` workspace. Fire this skill on ANY web-UI work, even when the user doesn't name schmancy explicitly — including adding or editing a component, building a form, showing a dialog / toast / side drawer / bottom sheet, wiring routing, reading or writing a
|
|
3
|
+
description: UI patterns, component APIs, and conventions for the @mhmo91/schmancy web-component library (Lit + RxJS + Tailwind) — the exclusive UI stack in this repo's `web/` workspace. Fire this skill on ANY web-UI work, even when the user doesn't name schmancy explicitly — including adding or editing a component, building a form, showing a dialog / toast / side drawer / bottom sheet, wiring routing, reading or writing a state, styling with theme tokens, adding a drop zone / file input / date picker / autocomplete, working with `SchmancyElement`, or touching any `<schmancy-*>` tag. Also fire on prompts like "build a page", "add a modal", "wire a route", "save user prefs in storage", "animate this", "style with our theme", "make a notification", "how do I do X in Lit", "my drag-and-drop", "dark mode toggle".
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Schmancy
|
|
@@ -56,19 +56,20 @@ Use component tags (`<schmancy-menu>`, `<schmancy-dropdown>`, `<schmancy-tooltip
|
|
|
56
56
|
Remediation: before reporting any file compliant, run a grep against each forbidden pattern named by the rule under audit (e.g. `grep -nE '\[(?:[^]]+)\]' <file>` for `TOKEN_FIRST_NO_ARBITRARY`; `grep -nE 'class="[^"]*\b(text-|bg-|border-|rounded-|shadow-|tracking-|leading-|p[xy]?-)' <file>` plus a manual scan for raw `<div>`/`<span>` carrying those classes for `PRIMITIVE_FIRST`). The compliance report's body is the concatenation of those grep outputs annotated with their rule name; an empty body across every pattern under every rule is the only form of "compliant". A claim of compliance unaccompanied by the grep evidence is not a compliance report and is treated as an unverified assertion that the audit loop rejects.
|
|
57
57
|
- **Schmancy primitive first** (`PRIMITIVE_FIRST`). Within `web/**`, every visible UI element is a custom element exported from `packages/schmancy/src/**`, and an element absent from that export set is added there before being imported into `web/**`. The rule sits above the styling rules: a `<div class="text-xs text-surface-on-variant">…</div>` whose role is typography is a violation even when every utility resolves to a registered token, because `<schmancy-typography>` already covers that role; the styling rules apply only to whatever class strings remain after the right primitive has been selected.
|
|
58
58
|
Sources: [packages/schmancy/skills/schmancy/INDEX.md](../INDEX.md) catalogues the export set by job (foundations / atoms / forms / navigation / overlays / interaction / feedback / display); each role's reference file (`typography.md`, `surface.md`, `button.md`, `overlay.md`, …) names the props, slots, and events that displace the equivalent `<div>` + utility-class pattern. The export set is the single source — a primitive that is not exported from `packages/schmancy/src/**` does not satisfy this rule even if it lives in a private file inside the schmancy tree.
|
|
59
|
-
Remediation: walk every `.ts` and `.html` file under `web/**` and list every raw HTML element whose class string carries design-system styling (typography, color, spacing-as-design-decision, surface, layout-as-design-decision, motion, overlay) — those are the violations. For each, look up the matching schmancy primitive in `INDEX.md` and rewrite the element through that primitive (`<schmancy-typography type=… token=…>` for type-scale text, `<schmancy-surface type=… fill=…>` for elevated/bounded surfaces, `<schmancy-grid>`/`<schmancy-flex>` for layout primitives with design intent, the imperative `show`/`$notify`/`schmancyContentDrawer.push` services for overlays, `<schmancy-scroll>` for scroll containers). When a needed primitive is absent from the export set, design and implement it as a new component under `packages/schmancy/src/<role>/` — extending
|
|
59
|
+
Remediation: walk every `.ts` and `.html` file under `web/**` and list every raw HTML element whose class string carries design-system styling (typography, color, spacing-as-design-decision, surface, layout-as-design-decision, motion, overlay) — those are the violations. For each, look up the matching schmancy primitive in `INDEX.md` and rewrite the element through that primitive (`<schmancy-typography type=… token=…>` for type-scale text, `<schmancy-surface type=… fill=…>` for elevated/bounded surfaces, `<schmancy-grid>`/`<schmancy-flex>` for layout primitives with design intent, the imperative `show`/`$notify`/`schmancyContentDrawer.push` services for overlays, `<schmancy-scroll>` for scroll containers). When a needed primitive is absent from the export set, design and implement it as a new component under `packages/schmancy/src/<role>/` — extending `SchmancyElement` with `static styles = [css\`...\`]`, registered in `HTMLElementTagNameMap`, exported through the package barrel, and documented with a sibling `.md` in the skill's reference set — and only then introduce the first call site in `web/**`. The audit subagent iterates the whole `web/**` tree, surfaces the violation list, applies the rewrites, runs `yarn workspace @momo/web tsc --noEmit` plus the colocated `*-view.test.ts` suites, and reports pre-existing violations that require a new schmancy primitive as a separate punch list for designer/architect approval before the implementation lands. The loop exits when every `web/**` file's visible UI elements are schmancy primitives and the typecheck plus the test suites pass.
|
|
60
60
|
|
|
61
61
|
## Non-negotiable conventions
|
|
62
62
|
|
|
63
63
|
**Component authoring**
|
|
64
|
-
- Every component extends `SchmancyElement` and declares its component-local CSS via `static styles = [css\`...\`]`. Never raw `LitElement`. Never wrap with `SignalWatcher` — the base already includes it; double-wrapping creates two nested Computeds and panics with "Detected cycle in computations" at runtime.
|
|
64
|
+
- Every component extends `SchmancyElement` and declares its component-local CSS via `static styles = [css\`...\`]`. Never raw `LitElement`. Never wrap with `SignalWatcher` — the base already includes it; double-wrapping creates two nested Computeds and panics with "Detected cycle in computations" at runtime.
|
|
65
65
|
- Every RxJS subscription ends with `.pipe(takeUntil(this.disconnecting))`.
|
|
66
66
|
- Register the tag in `HTMLElementTagNameMap` for TypeScript.
|
|
67
67
|
|
|
68
68
|
**State**
|
|
69
|
-
-
|
|
70
|
-
-
|
|
71
|
-
-
|
|
69
|
+
- States live at module scope. Many small states beat one monolith. Use `state('feature/name').{memory,session,local,idb}(initial)` from `@mhmo91/schmancy/state`.
|
|
70
|
+
- Reading `state.value` inside `render()` auto-tracks via the base class's `SignalWatcher` — no decorator or binding needed for the default case.
|
|
71
|
+
- `await state.ready` (or `if (state.loaded)`) before reading persisted-backend values that hydrate asynchronously.
|
|
72
|
+
- Storage tiers: `.memory()` (regenerable) · `.session()` (per-tab) · `.local()` (user prefs) · `.idb()` (>100-entry collections).
|
|
72
73
|
|
|
73
74
|
**Routing**
|
|
74
75
|
- Route guards are `Observable<boolean>`, never cached booleans.
|
package/skills/schmancy/audio.md
CHANGED
|
@@ -36,7 +36,7 @@ sound.muted$.subscribe(m => {})
|
|
|
36
36
|
```
|
|
37
37
|
|
|
38
38
|
## Settings Persistence
|
|
39
|
-
Volume, mute, and custom theme persist to `localStorage` under key `schmancy-sound-settings` (via `
|
|
39
|
+
Volume, mute, and custom theme persist to `localStorage` under key `schmancy-sound-settings` (via `state(...).local(...)` from `@mhmo91/schmancy/state`).
|
|
40
40
|
|
|
41
41
|
## AI-Generated Themes
|
|
42
42
|
```typescript
|
|
@@ -38,7 +38,7 @@ discoverAnyComponent('schmancy-navigation-rail', 'schmancy-navigation-bar')
|
|
|
38
38
|
```
|
|
39
39
|
|
|
40
40
|
### `discoverElement(selector, timeout = 150)`
|
|
41
|
-
Finds any element by CSS selector across shadow DOM. Uses a request ID + universal `schmancy-discover` event. Every
|
|
41
|
+
Finds any element by CSS selector across shadow DOM. Uses a request ID + universal `schmancy-discover` event. Every `SchmancyElement` responds if it finds a match in its shadow root.
|
|
42
42
|
```typescript
|
|
43
43
|
discoverElement('[data-section="pricing"]').subscribe(section => section?.scrollIntoView())
|
|
44
44
|
```
|
|
@@ -51,12 +51,12 @@ discoverAllElements('.flagged').subscribe(all => console.log(all.length))
|
|
|
51
51
|
|
|
52
52
|
## How the Handshake Works
|
|
53
53
|
1. Caller creates a unique `requestId` and broadcasts `schmancy-discover` on `window` with `{ selector, requestId }`.
|
|
54
|
-
2. Every
|
|
54
|
+
2. Every `SchmancyElement` listens for this event (wired up in the base class).
|
|
55
55
|
3. Any matching element dispatches `schmancy-discover-response` with `{ requestId, element }`.
|
|
56
56
|
4. Caller collects responses for the timeout window and emits via RxJS.
|
|
57
57
|
|
|
58
58
|
## Pattern in Base Class
|
|
59
|
-
Every
|
|
59
|
+
Every `SchmancyElement` inherits auto-response: `discover<T>(tag)` (method on the component) and `{tagName}-where-are-you`/`{tagName}-here-i-am` events. See [mixins.md](./mixins.md).
|
|
60
60
|
|
|
61
61
|
## When to Use
|
|
62
62
|
- Cross-shadow coordination between unrelated components.
|
package/skills/schmancy/menu.md
CHANGED
|
@@ -36,4 +36,4 @@ Auto-dismisses the dialog when clicked. Renders as a `schmancy-list-item` intern
|
|
|
36
36
|
</schmancy-menu>
|
|
37
37
|
```
|
|
38
38
|
|
|
39
|
-
Uses
|
|
39
|
+
Uses `show(overlay, { anchor })` from `@mhmo91/schmancy/overlay` internally. `<schmancy-menu-item>` auto-closes the menu by dispatching a `'close'` event on click. Custom components in the default slot dismiss the menu the same way: `this.dispatchEvent(new CustomEvent('close', { bubbles: true, composed: true }))`.
|
|
@@ -40,7 +40,7 @@ Centered is the fallback (no anchor given). Sheet is the responsive adaptation (
|
|
|
40
40
|
|
|
41
41
|
## Subscription IS the overlay lifecycle
|
|
42
42
|
|
|
43
|
-
Inside any
|
|
43
|
+
Inside any `SchmancyElement`, pipe `takeUntil(this.disconnecting)`. When the caller unmounts, the overlay auto-dismisses — no handles to track, no leaks.
|
|
44
44
|
|
|
45
45
|
```ts
|
|
46
46
|
show(MyForm, { props: { id }, anchor: ev })
|
package/skills/schmancy/state.md
CHANGED
|
@@ -29,8 +29,8 @@ cart.replace({ items: [], total: 0 })
|
|
|
29
29
|
cart.update(d => { d.items.push(item) }) // immer
|
|
30
30
|
cart.delete('total')
|
|
31
31
|
|
|
32
|
-
// Subscribe in a
|
|
33
|
-
class CartView extends
|
|
32
|
+
// Subscribe in a SchmancyElement — direct read auto-tracks via SignalWatcher
|
|
33
|
+
class CartView extends SchmancyElement {
|
|
34
34
|
render() { return html`Items: ${cart.value.items.length}` }
|
|
35
35
|
}
|
|
36
36
|
|
|
@@ -207,18 +207,18 @@ with zero ceremony.
|
|
|
207
207
|
|
|
208
208
|
### (1) Default — direct read inside `render()`
|
|
209
209
|
|
|
210
|
-
|
|
210
|
+
`SchmancyElement` composes `SignalWatcher` from `@lit-labs/signals`.
|
|
211
211
|
Every signal read inside `render()` auto-tracks; the host re-renders
|
|
212
212
|
on change. No decorator, no field, no binding code.
|
|
213
213
|
|
|
214
214
|
```ts
|
|
215
|
-
import {
|
|
215
|
+
import { html } from 'lit'
|
|
216
216
|
import { customElement } from 'lit/decorators.js'
|
|
217
|
-
import {
|
|
218
|
-
import { cart } from './cart.
|
|
217
|
+
import { SchmancyElement } from '@mhmo91/schmancy/mixins'
|
|
218
|
+
import { cart } from './cart.state'
|
|
219
219
|
|
|
220
220
|
@customElement('cart-view')
|
|
221
|
-
export class CartView extends
|
|
221
|
+
export class CartView extends SchmancyElement {
|
|
222
222
|
render() {
|
|
223
223
|
return html`<span>Items: ${cart.value.items.length}</span>`
|
|
224
224
|
}
|
|
@@ -243,7 +243,7 @@ derived methods, DevTools inspection, or readability:
|
|
|
243
243
|
import { observe } from '@mhmo91/schmancy/state'
|
|
244
244
|
|
|
245
245
|
@customElement('cart-view')
|
|
246
|
-
export class CartView extends
|
|
246
|
+
export class CartView extends SchmancyElement {
|
|
247
247
|
@observe(cart) cart!: CartState
|
|
248
248
|
|
|
249
249
|
onClick() {
|
|
@@ -268,12 +268,12 @@ works under the existing tsconfig with no migration.
|
|
|
268
268
|
|
|
269
269
|
### (3) `bindState(host, source)` — imperative form
|
|
270
270
|
|
|
271
|
-
For hosts that aren't
|
|
271
|
+
For hosts that aren't `SchmancyElement` subclasses (rare):
|
|
272
272
|
|
|
273
273
|
```ts
|
|
274
274
|
import { bindState } from '@mhmo91/schmancy/state'
|
|
275
275
|
|
|
276
|
-
class CustomHost extends LitElement { // not a
|
|
276
|
+
class CustomHost extends LitElement { // not a SchmancyElement subclass
|
|
277
277
|
cart = bindState(this, cart)
|
|
278
278
|
render() {
|
|
279
279
|
return html`<span>Items: ${this.cart.value.items.length}</span>`
|
|
@@ -376,7 +376,7 @@ two side-by-side checkout flows, an `<iframe>`-like wizard, an
|
|
|
376
376
|
embedded preview — wrap the subtree in `<schmancy-context>`.
|
|
377
377
|
|
|
378
378
|
```ts
|
|
379
|
-
class App extends
|
|
379
|
+
class App extends SchmancyElement {
|
|
380
380
|
render() {
|
|
381
381
|
return html`
|
|
382
382
|
<schmancy-context .provides=${[cart]}>
|
|
@@ -402,7 +402,7 @@ after an `await fetch(...)` — all of it auto-resolves to the right
|
|
|
402
402
|
instance based on tree position.
|
|
403
403
|
|
|
404
404
|
```ts
|
|
405
|
-
class CartView extends
|
|
405
|
+
class CartView extends SchmancyElement {
|
|
406
406
|
render() {
|
|
407
407
|
return html`
|
|
408
408
|
<button @click=${() => cart.set({ total: 0 })}>Clear</button>
|
|
@@ -418,22 +418,42 @@ class CartView extends $LitElement() {
|
|
|
418
418
|
}
|
|
419
419
|
```
|
|
420
420
|
|
|
421
|
-
Coverage of the call paths is provided by `SchmancyElement
|
|
421
|
+
Coverage of the call paths is provided by `SchmancyElement` and
|
|
422
|
+
`<schmancy-context>`:
|
|
422
423
|
|
|
423
|
-
- `render()` and every Lit lifecycle hook — wrapped at construction
|
|
424
|
+
- `render()` and every Lit lifecycle hook — wrapped at construction
|
|
425
|
+
via the `_activeHost.run(host, fn)` stack.
|
|
424
426
|
- Class methods (`handleAdd`, `handleSubmit`) — wrapped at construction.
|
|
425
|
-
- `await` continuations inside class methods — propagated via the
|
|
426
|
-
`Promise.then` patch in `state/active-host.ts`.
|
|
427
427
|
- `addEventListener(type, fn)` on the host — wrapped (and unwrapped on
|
|
428
428
|
`removeEventListener`).
|
|
429
|
-
-
|
|
430
|
-
via the `
|
|
431
|
-
`
|
|
429
|
+
- Explicit `.then(...)` continuations off a class-method Promise —
|
|
430
|
+
propagated via the `Promise.prototype.then` patch in
|
|
431
|
+
`state/active-host.ts`, which captures the active host at chain time
|
|
432
|
+
and restores it inside the callback.
|
|
433
|
+
- Inline arrow handlers in templates (`@click=${() => …}`) and any
|
|
434
|
+
other DOM event handler attached inside a `<schmancy-context>`
|
|
435
|
+
subtree — resolved via the capture-phase event listener that
|
|
436
|
+
`<schmancy-context>` installs on itself for ~18 common event types.
|
|
437
|
+
The listener publishes the event's target as the active host through
|
|
438
|
+
the `_publishEventHost(node)` slot for the duration of the
|
|
439
|
+
synchronous handler chain (slot self-clears in the next microtask).
|
|
440
|
+
|
|
441
|
+
**Known limitation — native `await` on a native Promise.** V8's await
|
|
442
|
+
optimization (since 7.x) skips the spec-prescribed
|
|
443
|
+
`Promise.resolve(x).then(continuation)` step, so the `Promise.then`
|
|
444
|
+
patch does not see the resumption. Class methods that mutate state
|
|
445
|
+
across an `await` boundary fall back to the module-scoped global, not
|
|
446
|
+
the active-host's isolated copy. To preserve the host across awaits,
|
|
447
|
+
either keep the mutation in the synchronous prelude before the first
|
|
448
|
+
`await`, or chain explicitly with `.then(...)` (which still routes
|
|
449
|
+
through the patched method). A real fix requires either a build-time
|
|
450
|
+
async-function transform or native `AsyncContext.Variable` in the
|
|
451
|
+
runtime.
|
|
432
452
|
|
|
433
453
|
Pure async callbacks with no DOM origin — a websocket `onmessage`,
|
|
434
|
-
`setInterval` with no triggering user event — fall through to
|
|
435
|
-
module-scoped global. That is the correct semantic: those
|
|
436
|
-
have no tree position to resolve to.
|
|
454
|
+
`setInterval` with no triggering user event — also fall through to
|
|
455
|
+
the module-scoped global. That is the correct semantic: those
|
|
456
|
+
callbacks have no tree position to resolve to.
|
|
437
457
|
|
|
438
458
|
### Lifecycle
|
|
439
459
|
|