@tenphi/tasty 2.11.0 → 2.11.1

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.
Files changed (34) hide show
  1. package/README.md +1 -0
  2. package/dist/{collector-CW4jZxLo.js → collector-AHZaBSv8.js} +3 -3
  3. package/dist/{collector-CW4jZxLo.js.map → collector-AHZaBSv8.js.map} +1 -1
  4. package/dist/{config-DxBPtu-V.js → config-YDAcLaVf.js} +22 -22
  5. package/dist/config-YDAcLaVf.js.map +1 -0
  6. package/dist/core/index.js +5 -5
  7. package/dist/{core-BakA5V89.js → core-CS4bzqGu.js} +10 -10
  8. package/dist/core-CS4bzqGu.js.map +1 -0
  9. package/dist/{css-writer-w6bzEFb-.js → css-writer-BXSANVrq.js} +3 -3
  10. package/dist/{css-writer-w6bzEFb-.js.map → css-writer-BXSANVrq.js.map} +1 -1
  11. package/dist/{format-rules-BAJyIyAZ.js → format-rules-CYriCDwq.js} +2 -2
  12. package/dist/{format-rules-BAJyIyAZ.js.map → format-rules-CYriCDwq.js.map} +1 -1
  13. package/dist/{hydrate-B2BwrnYb.js → hydrate-GVTorHpU.js} +2 -2
  14. package/dist/{hydrate-B2BwrnYb.js.map → hydrate-GVTorHpU.js.map} +1 -1
  15. package/dist/index.js +6 -6
  16. package/dist/{keyframes-oSX5T5lm.js → keyframes-CtcSlw4k.js} +2 -2
  17. package/dist/{keyframes-oSX5T5lm.js.map → keyframes-CtcSlw4k.js.map} +1 -1
  18. package/dist/{merge-styles-RmK1DOSt.js → merge-styles-D4ITH4bc.js} +2 -2
  19. package/dist/{merge-styles-RmK1DOSt.js.map → merge-styles-D4ITH4bc.js.map} +1 -1
  20. package/dist/{resolve-recipes-CxyDLx9x.js → resolve-recipes-llr0COs8.js} +3 -3
  21. package/dist/{resolve-recipes-CxyDLx9x.js.map → resolve-recipes-llr0COs8.js.map} +1 -1
  22. package/dist/ssr/astro-client.js +1 -1
  23. package/dist/ssr/astro.js +3 -3
  24. package/dist/ssr/index.js +3 -3
  25. package/dist/ssr/next.js +4 -4
  26. package/dist/static/index.js +4 -4
  27. package/dist/static/index.js.map +1 -1
  28. package/dist/zero/babel.js +4 -4
  29. package/dist/zero/index.js +1 -1
  30. package/docs/README.md +1 -0
  31. package/docs/ai-agents.md +206 -0
  32. package/package.json +2 -1
  33. package/dist/config-DxBPtu-V.js.map +0 -1
  34. package/dist/core-BakA5V89.js.map +0 -1
@@ -0,0 +1,206 @@
1
+ # Tasty Style Rules for AI Agents
2
+
3
+ A compact ruleset for **writing correct `@tenphi/tasty` styles**. It is not an API tour — every rule here is machine-checked by [`@tenphi/eslint-plugin-tasty`](https://www.npmjs.com/package/@tenphi/eslint-plugin-tasty), so following it means clean lint output. For explanations and the complete API, see [Style DSL](dsl.md), [Style Properties](styles.md), [React API](react-api.md).
4
+
5
+ Notation: ❌ wrong → ✅ correct.
6
+
7
+ ---
8
+
9
+ ## 0. Read the project config first
10
+
11
+ Color tokens (`#name`), custom properties (`$name`), `preset` names, `recipe` names, state aliases (`@mobile`) and extra units are **project-defined** in `tasty.config.ts` or a `configure({ … })` call. Look them up before writing styles. Never invent a name — reuse an existing one, or add it to the config.
12
+
13
+ ## 1. Where styles go
14
+
15
+ ```jsx
16
+ const Card = tasty({ as: 'div', styles: { … }, styleProps: ['padding'] }); // new component
17
+ const Hero = tasty(Card, { styles: { … } }); // extend a component
18
+ const cls = useStyles({ … }); // ad-hoc class name
19
+ tastyStatic('.card', { … }); // build-time, zero runtime
20
+ ```
21
+
22
+ - Wrap, don't pass styles per instance: ❌ `<Box styles={{ padding: '2x' }} />` → ✅ `const Box = tasty({ styles: { padding: '2x' } })`.
23
+ - Style values are **static**. Route anything conditional through a state map plus `mods` (or `tokens` / `styleProps`):
24
+ ❌ `fill: isOpen ? '#primary' : '#surface'` → ✅ `fill: { '': '#surface', open: '#primary' }` with `mods={{ open: isOpen }}`.
25
+
26
+ ## 2. Property names
27
+
28
+ Keys are camelCase — a Tasty property or a real CSS property. No kebab-case, no invented names.
29
+
30
+ Prefer the Tasty shorthand over its CSS equivalents:
31
+
32
+ | Use | Instead of |
33
+ |-----|------------|
34
+ | `fill` | `backgroundColor`, `background` |
35
+ | `image` | `backgroundImage` |
36
+ | `border` | `borderColor`, `borderWidth`, `borderStyle`, `borderTop`/`Right`/`Bottom`/`Left` |
37
+ | `radius` | `borderRadius` |
38
+ | `outline` | `outlineColor`, `outlineWidth`, `outlineStyle`, `outlineOffset` |
39
+ | `shadow` | `boxShadow` |
40
+ | `padding` | `paddingTop`/`Right`/`Bottom`/`Left` |
41
+ | `margin` | `marginTop`/`Right`/`Bottom`/`Left` |
42
+ | `inset` | `top`, `right`, `bottom`, `left` |
43
+ | `width` / `height` | `minWidth`/`maxWidth`, `minHeight`/`maxHeight` |
44
+ | `flow` | `flexDirection`, `flexWrap`, `flexFlow`, `gridAutoFlow` |
45
+ | `preset` | `fontSize`, `lineHeight`, `letterSpacing`, `fontWeight`, `fontStyle`, `textTransform` |
46
+ | `font` | `fontFamily` |
47
+ | `scrollbar` | `scrollbarWidth`, `scrollbarColor`, `scrollbarGutter` |
48
+ | `gridColumns` / `gridRows` / `gridAreas` | `gridTemplateColumns` / `Rows` / `Areas` |
49
+ | `textOverflow: 'ellipsis / 3'` | `lineClamp` |
50
+ | `hide: true` | `display: 'none'` |
51
+ | `flexGrow` / `flexShrink` / `flexBasis` | `flex` |
52
+
53
+ The last row goes the other way — longhands over the shorthand — because `flex` is lossy: it resets the components you omit to non-initial values (`flex: '0'` silently sets `flex-basis: 0%`) and cannot express `flexShrink: 0` at all. The longhands also carry separate state maps.
54
+
55
+ ## 3. Values
56
+
57
+ ### Tokens
58
+
59
+ | Syntax | Meaning |
60
+ |--------|---------|
61
+ | `#name` | color token → `var(--name-color)` |
62
+ | `#name.50` | token at 50% opacity (`0`–`100`) |
63
+ | `#name.$opacity` | opacity from a custom property |
64
+ | `#clear` / `#current` | `transparent` / `currentcolor` |
65
+ | `$name` | custom property → `var(--name)` |
66
+ | `(#a, #b)` | fallback chain |
67
+ | `$$name` / `##name` | the property *name* — only inside `transition` |
68
+
69
+ - ❌ `#f5f5f5`, `rgb(0 0 0)`, `oklch(…)`, `okhsl(…)`, `red` → ✅ `#surface` (add the token to the config if it doesn't exist).
70
+ - ❌ `var(--gap)` → ✅ `$gap` · ❌ `$accent-color` → ✅ `#accent` · ❌ `transparent` → ✅ `#clear` · ❌ `currentColor` → ✅ `#current`.
71
+ - A `$name` must be declared as a `'$name': value` key in the same styles object, or in the config.
72
+
73
+ ### Units
74
+
75
+ Tasty units: `x` (gap multiple), `r` (radius), `cr` (card radius), `bw` (border width), `ow` (outline width), `sf` (`minmax(0, Nfr)`). All CSS units work too, plus anything in `units` in the config. Any other unit is an error.
76
+
77
+ Prefer units over raw pixels: `8px` → `1x` … `64px` → `8x`; `radius: '6px'` → `'1r'`; `border: '1px …'` → `'1bw …'`.
78
+
79
+ ### Math
80
+
81
+ ❌ `calc(100% - 2x)` → ✅ `(100% - 2x)`. Parentheses are wrapped in `calc()` automatically — and must be balanced.
82
+
83
+ ### `true`
84
+
85
+ `true` means "the design-system default" and is accepted **only** by: `border`, `radius`, `outline`, `shadow`, `padding`, `margin`, `gap`, `inset`, `width`, `height`, `fill`, `color`, `preset`, `font`, `scrollbar`, `hide`. Anywhere else it is an error.
86
+
87
+ ### `!important`
88
+
89
+ Never use it. Tasty owns specificity through doubled selectors and state ordering; `!important` breaks that. Express the exception as a state instead.
90
+
91
+ ### Modifiers
92
+
93
+ A value is `[values…] [modifiers…]`, and several groups can be comma-separated (later groups override earlier ones). Only the modifiers a property knows are valid:
94
+
95
+ | Property | Modifiers |
96
+ |----------|-----------|
97
+ | `padding`, `margin`, `inset`, `fade` | `top` `right` `bottom` `left` |
98
+ | `border` | the four directions + `solid` `dashed` `dotted` `double` `groove` `ridge` `inset` `outset` `none` `hidden` |
99
+ | `outline` | the style keywords above |
100
+ | `radius` | `top` `right` `bottom` `left` + shapes `round` `ellipse` `leaf` `backleaf` |
101
+ | `width`, `height` | `min` `max` `fixed` |
102
+ | `flow` | `row` `column` `row-reverse` `column-reverse` `wrap` `nowrap` `dense` |
103
+ | `overflow` | `visible` `hidden` `scroll` `clip` `auto` `overlay` |
104
+ | `position` | `static` `relative` `absolute` `fixed` `sticky` |
105
+ | `shadow` | `inset` |
106
+ | `preset` | `name / strong` (or `bold`) `italic` `icon` `tight` |
107
+ | box properties | `longhand` — emit CSS longhands instead of the shorthand |
108
+
109
+ Directional modifiers beat placeholder zeros:
110
+
111
+ - ❌ `padding: '0 0 2x 0'` → ✅ `padding: '2x bottom'`
112
+ - ❌ `padding: '1x 1x 2x 1x'` → ✅ `padding: '1x, 2x bottom'`
113
+ - ❌ `border: '0 0 1bw 0'` — four tokens parse as *one* border value, so this renders no border at all → ✅ `border: '1bw bottom'`
114
+
115
+ Value-only properties reject both colors and modifiers: `gap`, `columnGap`, `rowGap`, `opacity`, `zIndex`, `order`, `flexGrow`, `flexShrink`, `flexBasis`, `aspectRatio`, `lineClamp`, `tabSize`, `paddingInline`, `paddingBlock`. `fill` and `color` take a color (plus `none` / `transparent`); `caretColor` and `accentColor` take a color only.
116
+
117
+ ### `transition`
118
+
119
+ Use semantic names, not CSS property names: `fade` `fill` `color` `theme` `border` `radius` `shadow` `outline` `preset` `text` `gap` `opacity` `translate` `rotate` `scale` `filter` `image` `background` `width` `height` `zIndex` `inset` `flow` `dimension`.
120
+
121
+ ❌ `transition: 'background-color 0.2s'` → ✅ `transition: 'fill 0.2s'`
122
+
123
+ ## 4. State maps
124
+
125
+ A property value can be an object of `state: value`. Key order is priority — later keys win.
126
+
127
+ | Key | Generated selector |
128
+ |-----|--------------------|
129
+ | `hovered` | `[data-hovered]` (boolean modifier from `mods`) |
130
+ | `theme=danger` | `[data-theme="danger"]` (value modifier) |
131
+ | `:hover` | pseudo-class |
132
+ | `.active` | class selector |
133
+ | `[aria-expanded="true"]` | attribute selector |
134
+ | `hovered & .active` | AND |
135
+ | `hovered \| focused` | OR (`,` also means OR) |
136
+ | `!disabled` | NOT |
137
+ | `hovered ^ focused` | XOR — exactly one (keep chains ≤ 4 operands) |
138
+
139
+ Precedence `!` > `^` > `|` > `&`; use parentheses to override.
140
+
141
+ Rules:
142
+
143
+ 1. **`''` comes first.** The bare default is the lowest-priority state; placing it later would override everything above it.
144
+ 2. **Every state map needs `''` or `_`** — except when extending (`tasty(Parent, …)`), where omitting `''` merges into the parent's states and including `''` replaces them wholesale.
145
+ 3. **`_` is standalone-only** and always first (with `''` right after it, if present). `_` is a never-negated fallback floor for cases where a higher-priority branch may be *unknown* (`@supports`, container queries). If a map contains only `_` and `''`, drop the `''`.
146
+ 4. **No nested maps:** ❌ `{ hovered: { pressed: 'x' } }` → ✅ `{ 'hovered & pressed': 'x' }`
147
+ 5. **State keys never sit at the top level** of a styles object — `:hover`, `.active`, `[open]` belong inside a property value.
148
+
149
+ ```jsx
150
+ color: { '': '#text', hovered: '#accent', disabled: '#text.40' }
151
+ ```
152
+
153
+ Advanced states:
154
+
155
+ | Prefix | Use | Example |
156
+ |--------|-----|---------|
157
+ | `@media(…)` | media query; dimensions `w` `h`; types `@media:print` `:screen` `:all` `:speech` | `@media(w < 768px)`, `@media(600px <= w < 1200px)` |
158
+ | `@(…)` | container query; dimensions `w` `h` `is` `bs` | `@(layout, w >= 600px)`, `@($variant=primary)` |
159
+ | `@supports(…)` | feature query; `$` first argument tests a selector | `@supports(display: grid)`, `@supports($, :has(*))` |
160
+ | `@root(…)` | condition on `:root` | `@root(schema=dark)` |
161
+ | `@parent(…)` | condition on an ancestor; `, >` for the direct parent | `@parent(hovered, >)` |
162
+ | `@own(…)` | a sub-element's own state — **only inside sub-element styles** | `@own(:hover)` |
163
+ | `@starting` | `@starting-style` entry animation | `@starting` |
164
+ | `@name` | project state alias | `@mobile` |
165
+
166
+ - At root level write the selector directly: ❌ `'@own(:hover)'` → ✅ `':hover'`.
167
+ - `@name` aliases must exist in `states` in the config or be declared locally as an `'@name': '<state expression>'` key; alias keys start with `@` and their value must be a valid state expression.
168
+ - `:is()` / `:has()` / `:not()` / `:where()` work in state keys but support at most 2 levels of nested parentheses, and `:has()` is expensive — prefer `@parent()`, `@own()` and modifiers.
169
+
170
+ When extending a parent's state map: `'@inherit'` reuses the parent's value for that state, `null` removes a state (or resets a property, letting recipes fill in), `false` is a tombstone that blocks it entirely.
171
+
172
+ ## 5. Sub-elements
173
+
174
+ A **capitalized** key targets `[data-element="Name"]`, and its value must be a style object.
175
+
176
+ ```jsx
177
+ styles: { Title: { preset: 'h3' }, Icon: { $: '>@:last-child', color: '#accent' } }
178
+ ```
179
+
180
+ ❌ nested-selector keys (`'& .title'`, `'&:hover'`) → ✅ sub-elements and state maps. Use the `$` affix property inside a sub-element to control how its selector attaches (`>` direct child, `@` placeholder for the element itself, `&::before` for a root pseudo-element).
181
+
182
+ ## 6. Special top-level keys
183
+
184
+ | Key | Shape |
185
+ |-----|-------|
186
+ | `@keyframes` | `{ name: { '0%': styles, … } }` |
187
+ | `@properties` | `{ '$name': { syntax, inherits, initialValue } }` |
188
+ | `@fontFace` | `{ 'Family Name': descriptors \| descriptors[] }` |
189
+ | `@counterStyle` | `{ name: descriptors }` |
190
+ | `recipe` | a **string** of configured recipe names: `'card elevated'`, `'reset input / autofill'`, `'none / disabled'` |
191
+
192
+ ## 7. `tastyStatic()`
193
+
194
+ The selector must be a string literal and valid CSS. Values must be static — strings, numbers, booleans, `null`, or objects/arrays of those. No variables, template literals, function calls, or spreads.
195
+
196
+ ## 8. Checklist
197
+
198
+ - Token, preset, recipe, unit and `@alias` names exist in the project config.
199
+ - Tasty shorthand chosen over CSS longhands; `flexGrow`/`flexShrink`/`flexBasis` over `flex`; `hide: true` over `display: 'none'`.
200
+ - Colors are `#tokens`, not hex/rgb/oklch/named; `$prop` not `var(--prop)`.
201
+ - Spacing uses `x`/`r`/`bw`/`ow` units; math uses `(…)`, not `calc(…)`.
202
+ - `true` only on the properties that accept it; no `!important`.
203
+ - Modifiers valid for the property; directional shorthand instead of placeholder zeros.
204
+ - Every state map starts with `''` (or `_`), is flat, and lives inside a property value.
205
+ - `@own()` only inside sub-elements; sub-element keys are capitalized and hold objects.
206
+ - Values are static; dynamic behavior comes from `mods` / `tokens` / `styleProps`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tenphi/tasty",
3
- "version": "2.11.0",
3
+ "version": "2.11.1",
4
4
  "description": "A design-system-integrated styling system and DSL for concise, state-aware UI styling",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -139,6 +139,7 @@
139
139
  "@eslint/js": "^10.0.1",
140
140
  "@size-limit/esbuild": "^12.0.0",
141
141
  "@size-limit/file": "^12.0.0",
142
+ "@tenphi/eslint-plugin-tasty": "^0.11.3",
142
143
  "@testing-library/jest-dom": "^6.9.1",
143
144
  "@testing-library/react": "^16.3.2",
144
145
  "@types/babel__core": "^7.20.5",