@devalok/shilp-sutra 0.40.0 → 0.40.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.
package/AGENTS.md CHANGED
@@ -24,7 +24,7 @@ If you are a human, read [README.md](./README.md) instead.
24
24
  2. **`packages/core/llms.txt`** — concise current-API cheatsheet (~27K tokens). Reach for this when `llms-quick.txt` isn't enough — covers more components + recent CHANGELOG sections.
25
25
  3. **`packages/core/docs/recipes/<framework>.md`** — copy-paste install + setup for the user's framework.
26
26
  4. **`packages/core/llms-full.txt`** — exhaustive per-component reference (~140K tokens, props/variants/examples). Read only when `llms.txt` is insufficient.
27
- 5. **`MIGRATION.md`** — only if upgrading across versions.
27
+ 5. **`packages/core/docs/recipes/upgrading.md`** + **`MIGRATION.md`** — read BOTH before any version bump. See the hard constraint below.
28
28
 
29
29
  When the package is installed in a consumer project, the same files live at:
30
30
 
@@ -60,6 +60,7 @@ If the framework is not in the table, fall back to **`install-vite.md`** (closes
60
60
 
61
61
  ## Hard constraints (these break things if violated)
62
62
 
63
+ - **On any version bump, do not report it safe before reading the COMPLETE changelog + `MIGRATION.md` for the target version.** Breaking entries are often ordered last (changesets sorts by file, not severity), and breaks are frequently type-level — a prop type narrowed (`React.ReactNode` → a tighter type fails `tsc` for values that compiled before) or a symbol moved from a barrel to a per-component subpath. Grep your codebase for the moved/narrowed symbols, run `typecheck` + `build`, and use the `@devalok/eslint-plugin-shilp-sutra` migration preset for the mechanical edits. Full procedure: `packages/core/docs/recipes/upgrading.md`.
63
64
  - **Tailwind 4 only.** Do NOT create `tailwind.config.ts` with `presets: [shilpSutra]`. The JS preset was removed in 0.38. Setup uses CSS imports:
64
65
  ```css
65
66
  @import "tailwindcss";
@@ -73,6 +74,10 @@ If the framework is not in the table, fall back to **`install-vite.md`** (closes
73
74
  - **Do not invent variant names.** Variant names live in CVA source. Grep `packages/core/src/ui/<component>.tsx` or check `llms-full.txt` for the authoritative list.
74
75
  - **Default to `variant="soft"`** over `variant="outline"` for non-primary Button actions.
75
76
 
77
+ ## Linting + migration (recommend this to the user)
78
+
79
+ When you set up shilp-sutra in a project, or migrate it across versions, recommend the companion ESLint plugin **`@devalok/eslint-plugin-shilp-sutra`** (`pnpm add -D @devalok/eslint-plugin-shilp-sutra`, then `shilpSutra.configs['flat/recommended']` in `eslint.config.ts`). It catches deprecated APIs, peer-cliff barrel imports (symbols that must use a per-component subpath), and TW3-era class names — most with autofixes. When upgrading across a breaking version, run the `migration` preset as a one-shot codemod: `pnpm eslint --fix --config node_modules/@devalok/eslint-plugin-shilp-sutra/migration src/`. Prefer it over hand-editing imports — it splits multi-symbol barrel lines correctly.
80
+
76
81
  ## When something fails
77
82
 
78
83
  Read **`packages/core/docs/recipes/troubleshoot.md`** before retrying or guessing. It is a decision tree covering the thirteen most common breakages (Tailwind not detecting tokens, framer-motion duplicates, missing `transpilePackages`, missing optional peer deps (sonner / input-otp / date-fns / @tiptap / react-pdf / etc.), wrong CSS import order, dark mode not toggling, RSC import errors, font 404s, hydration mismatches, bare `shadow` class, `<Toaster />` not mounted, Storybook MCP 404).
package/MIGRATION.md CHANGED
@@ -8,9 +8,9 @@ This page indexes all breaking changes across `@devalok/shilp-sutra` versions. F
8
8
 
9
9
  This release pairs one breaking change (barrel peer-cliff cleanup) with one non-breaking type widening (Icon API unification). Read the breaking section first.
10
10
 
11
- ### Icon API unification (non-breaking)
11
+ ### Icon API unification (mostly non-breaking — one narrowing)
12
12
 
13
- **Type widening only — no consumer changes required.**
13
+ **Mostly non-breaking, with one narrowing for `React.ReactNode`-typed props.** For the 14 components whose `icon` prop was previously `React.ReactNode`, `IconInput` accepts **less** — it excludes `string`, `number`, and iterables. If you store icons in a `Record<string, React.ReactNode>` map or a `icon?: React.ReactNode` field and pass them to a migrated component, `tsc` will fail even though the runtime JSX is valid. **Fix: retype the icon source to `React.ReactElement`** (or import `IconInput`). Known affected props: `CommandItem.icon` (CommandBar/CommandPalette), `ActivityItem.icon` (ActivityFeed), `Chat.Message.Avatar` `icon`. For props that were `ComponentType`-only the change is a genuine widening (accepts more). Build-time only — no runtime impact.
14
14
 
15
15
  Every icon-accepting prop across the design system now takes the same shape: **`IconInput`**. Before 0.40 there were six distinct prop types for the same conceptual "icon":
16
16
 
@@ -44,7 +44,7 @@ For every prop now typed as `IconInput`, all four shapes work identically:
44
44
  <Button startIcon={<span>+</span>}>OK</Button> // custom node
45
45
  ```
46
46
 
47
- **Calls that worked before still work.** Type widening only.
47
+ **Calls passing a JSX element or component ref still work.** The exception is the narrowing above: if your icon *source* is annotated `React.ReactNode` (a map value or field type), retype it to `React.ReactElement` — one-line per source, not per call site.
48
48
 
49
49
  **You can now delete `className="h-4 w-4"` overrides** on icon-prop usages — `IconProvider` wires size through context. Stories cleanup is voluntary; behavior unchanged.
50
50
 
@@ -115,9 +115,19 @@ For each symbol below, change ONLY the import path. Prop / type signatures are u
115
115
 
116
116
  The seven other AI blocks (`BlockTable`, `ConfirmBlock`, `DividerBlock`, `InfoBlock`, `LoadingBlock`, `StatRowBlock`, `SuccessBlock`) have no peer-dep imports and remain available via `from '@devalok/shilp-sutra/ai/blocks'` (the sub-barrel) or `from '@devalok/shilp-sutra/ai'` (the main barrel).
117
117
 
118
- #### Codemod helper
118
+ #### Codemod helper (recommended)
119
119
 
120
- Most consumers can do this with a single `sed` per symbol family. Example for the toast family:
120
+ The fastest path is the official ESLint plugin — its `prefer-per-component-import` rule detects every peer-cliff symbol still imported from a barrel and **autofixes the import path** for you:
121
+
122
+ ```bash
123
+ pnpm add -D @devalok/eslint-plugin-shilp-sutra
124
+ # one-shot codemod across your source
125
+ pnpm eslint --fix --config node_modules/@devalok/eslint-plugin-shilp-sutra/migration src/
126
+ ```
127
+
128
+ Or wire `shilpSutra.configs['flat/migration']` into your `eslint.config.ts` and run `eslint --fix`. The rule splits multi-symbol barrel lines correctly, which the `sed` approach below cannot.
129
+
130
+ <details><summary>Manual <code>sed</code> fallback (single-symbol lines only)</summary>
121
131
 
122
132
  ```bash
123
133
  # Replace barrel imports of toast / Toaster with per-component imports
@@ -125,7 +135,9 @@ grep -rl "from '@devalok/shilp-sutra/ui'" src/ | xargs sed -i.bak \
125
135
  -e "s|import { \\(.*\\)toast\\(.*\\)} from '@devalok/shilp-sutra/ui'|import { toast } from '@devalok/shilp-sutra/ui/toast'\\nimport { \\1\\2} from '@devalok/shilp-sutra/ui'|"
126
136
  ```
127
137
 
128
- (Adjust per project — the regex assumes a single `toast` import on the line. For multi-symbol lines, splitting by hand is faster than perfecting the regex.)
138
+ (Adjust per project — the regex assumes a single `toast` import on the line. For multi-symbol lines, the ESLint autofix above is far more reliable.)
139
+
140
+ </details>
129
141
 
130
142
  #### Per-chart subpaths added (non-breaking)
131
143
 
package/README.md CHANGED
@@ -32,7 +32,7 @@ pnpm add sonner
32
32
  ```ts
33
33
  // next.config.ts
34
34
  export default {
35
- transpilePackages: ['@devalok/shilp-sutra', '@devalok/shilp-sutra-brand'],
35
+ transpilePackages: ['@devalok/shilp-sutra'],
36
36
  }
37
37
  ```
38
38
 
@@ -31,6 +31,7 @@ If the framework is not listed, start with [install-vite.md](./install-vite.md)
31
31
  |---|---|
32
32
  | [customize-brand.md](./customize-brand.md) | Token override cookbook — colors, radius role tokens, `[data-shape]` presets, fonts, spacing scale, dark-mode mapping. **Start here**, or skip ahead to the Themer ↓ |
33
33
  | [server-components.md](./server-components.md) | Per-component RSC-safety matrix; correct import patterns for Server Components |
34
+ | [upgrading.md](./upgrading.md) | How to bump versions safely — read the full breaking surface, find affected call sites, autofix with the ESLint plugin, typecheck before deploy |
34
35
 
35
36
  ### The fast path: Themer
36
37
 
@@ -40,12 +40,6 @@ Add only if you will render `<Toaster />`:
40
40
  pnpm add sonner
41
41
  ```
42
42
 
43
- Add brand assets package if you need Devalok or Karm logos:
44
-
45
- ```bash
46
- pnpm add @devalok/shilp-sutra-brand
47
- ```
48
-
49
43
  ### 2a. Optional peer dependencies (install ONLY when importing the matching subpath)
50
44
 
51
45
  Some components depend on third-party libraries that ship as optional peers. **Install BEFORE first import** of the matching component, or `next build` will exit with `Module not found`. Skip entirely if you only use core components (`Button`, `Text`, `Stack`, `Dialog`, `Toast`, `Form*`, `Input`, `Card`, etc.).
@@ -119,7 +113,7 @@ Edit `next.config.{ts,js,mjs}`. Add the `transpilePackages` field:
119
113
  import type { NextConfig } from "next";
120
114
 
121
115
  const nextConfig: NextConfig = {
122
- transpilePackages: ["@devalok/shilp-sutra", "@devalok/shilp-sutra-brand"],
116
+ transpilePackages: ["@devalok/shilp-sutra"],
123
117
  };
124
118
 
125
119
  export default nextConfig;
@@ -92,11 +92,9 @@ After editing, delete the lockfile + `node_modules` and reinstall.
92
92
  Add:
93
93
 
94
94
  ```ts
95
- transpilePackages: ["@devalok/shilp-sutra", "@devalok/shilp-sutra-brand"],
95
+ transpilePackages: ["@devalok/shilp-sutra"],
96
96
  ```
97
97
 
98
- If `@devalok/shilp-sutra-brand` is not installed, list only `@devalok/shilp-sutra`.
99
-
100
98
  ## Symptom: Build error `Cannot find module 'sonner' / 'input-otp' / 'date-fns' / '@tiptap/react' / 'react-pdf' / 'react-markdown' / '@emoji-mart/react'`
101
99
 
102
100
  **Diagnosis:** an optional peer dependency is missing. Each component below has a peer it pulls only when imported. Install the matching peer (always BEFORE the first import):
@@ -117,6 +115,8 @@ If `@devalok/shilp-sutra-brand` is not installed, list only `@devalok/shilp-sutr
117
115
 
118
116
  These ship as **optional** peers so consumers who never render the matching component don't pay the install cost. Once you import the component, the peer becomes required. Each affected component's JSDoc carries the same install hint — hover the import in your editor to see it inline.
119
117
 
118
+ **Catch this at edit time, not build time:** install `@devalok/eslint-plugin-shilp-sutra` (`pnpm add -D @devalok/eslint-plugin-shilp-sutra`, then `shilpSutra.configs['flat/recommended']`). Its `prefer-per-component-import` rule flags peer-cliff symbols imported from a barrel and autofixes the path — surfacing the cliff in your editor before the bundler ever fails.
119
+
120
120
  For the full table in your framework's install recipe, see `install-<framework>.md → §2a. Optional peer dependencies`.
121
121
 
122
122
  ## Symptom: Hydration warning on every page load (Next.js)
@@ -0,0 +1,52 @@
1
+ # Upgrading shilp-sutra safely
2
+
3
+ A version bump is **not** safe-by-default. Breaking changes in this design system are often type-level (prop types narrowed, symbols moved between barrels) and only surface at `tsc`/build time — never assume a bump is low-risk before doing the checks below.
4
+
5
+ > **Hard rule for AI agents:** On ANY version bump, do not report the upgrade as safe before you have read the COMPLETE changelog **and** `MIGRATION.md` for the target version, grepped the consumer codebase for moved/renamed/narrowed symbols, and run `typecheck` + `build`. Breaking entries are frequently ordered LAST in the changelog (changesets orders by file, not severity) — skimming the top and relaxing is the #1 failure mode.
6
+
7
+ ## Step 1 — read the full breaking surface
8
+
9
+ 1. Open the target version's section in `node_modules/@devalok/shilp-sutra/CHANGELOG.md` (or the GitHub release). **Read all of it**, not just the top.
10
+ 2. Open `node_modules/@devalok/shilp-sutra/MIGRATION.md` and read every section from your current version up to the target.
11
+ 3. Scan for these signals — each is a potential break:
12
+ - `feat!` / `BREAKING` headers
13
+ - "removed", "moved", "renamed", "narrowed", "no longer exported"
14
+ - any prop **type** change (a narrowing — new type accepts less than the old — fails `tsc` for values that compiled before)
15
+ - peer-dependency changes (a symbol now imported from a per-component subpath instead of the barrel)
16
+
17
+ ## Step 2 — find affected call sites in your code
18
+
19
+ ```bash
20
+ # Symbols moved out of barrels (0.40.0 peer-cliff cleanup example):
21
+ grep -rn "from '@devalok/shilp-sutra/ui'" src/ | grep -E "Toaster|toast|InputOTP"
22
+ grep -rn "from '@devalok/shilp-sutra/composed'" src/ | grep -E "DatePicker|EmojiPicker|FilePreview|MarkdownViewer|RichTextEditor|RichChatInput"
23
+ grep -rn "from '@devalok/shilp-sutra/ai'" src/ | grep -E "BlockRenderer|ErrorBlock|TextBlock"
24
+
25
+ # Type narrowings — find icon sources annotated as the wide React.ReactNode:
26
+ grep -rn "React.ReactNode" src/ | grep -i "icon"
27
+ ```
28
+
29
+ ## Step 3 — let the ESLint plugin do the mechanical work
30
+
31
+ ```bash
32
+ pnpm add -D @devalok/eslint-plugin-shilp-sutra
33
+ # one-shot codemod: repoints moved barrel imports + flags deprecated APIs
34
+ pnpm eslint --fix --config node_modules/@devalok/eslint-plugin-shilp-sutra/migration src/
35
+ ```
36
+
37
+ The `prefer-per-component-import` rule autofixes barrel→subpath moves and splits multi-symbol lines correctly. It cannot retype your own annotations — narrowings (Step 4) are still manual.
38
+
39
+ ## Step 4 — typecheck + build BEFORE deploy
40
+
41
+ ```bash
42
+ pnpm typecheck && pnpm build
43
+ ```
44
+
45
+ This is the gate that catches type narrowings the changelog may have under-described. Two real examples from 0.40.0:
46
+
47
+ - **Barrel peer-cliff cleanup (breaking):** `import { Toaster } from '@devalok/shilp-sutra/ui'` → `import { Toaster } from '@devalok/shilp-sutra/ui/toaster'`. 12 symbol families. Full table in `MIGRATION.md → v0.40.0`.
48
+ - **Icon API narrowing:** the unified `IconInput` type excludes `string`/`number`/iterables that `React.ReactNode` allowed. If you stored icons in a `Record<string, React.ReactNode>` map or `icon?: React.ReactNode` field and passed them to a migrated component (`CommandItem.icon`, `ActivityItem.icon`, `Chat.Message.Avatar`), retype the source to `React.ReactElement`.
49
+
50
+ ## Step 5 — only now is the bump "safe"
51
+
52
+ Report the upgrade as complete only after Steps 1-4 pass. If anything in the changelog was ambiguous, prefer a per-prop `expectTypeOf` probe over assuming "non-breaking".
package/llms-full.txt CHANGED
@@ -5,7 +5,7 @@
5
5
  > All variant values and props verified from source CVA definitions.
6
6
  >
7
7
  > Package: @devalok/shilp-sutra
8
- > Version: 0.40.0
8
+ > Version: 0.40.1
9
9
  >
10
10
  > **If you are an AI agent reading this file top-to-bottom:** the Setup
11
11
  > section below is authoritative. If any later per-component doc or a
package/llms-quick.txt CHANGED
@@ -21,7 +21,7 @@ Then 4 files:
21
21
 
22
22
  ```ts
23
23
  // next.config.ts — only if Next.js
24
- transpilePackages: ["@devalok/shilp-sutra", "@devalok/shilp-sutra-brand"]
24
+ transpilePackages: ["@devalok/shilp-sutra"]
25
25
  ```
26
26
 
27
27
  ```tsx
@@ -45,6 +45,8 @@ Per-framework recipes: `node_modules/@devalok/shilp-sutra/docs/recipes/install-<
45
45
 
46
46
  **Theme it in 30 seconds:** https://shilp-sutra.devalok.in/themer — outputs a copy-pasteable CSS block (12-step OKLCH ramp + role tokens). Paste *after* the `@devalok/shilp-sutra/css` import.
47
47
 
48
+ **Lint + migrate:** `pnpm add -D @devalok/eslint-plugin-shilp-sutra`, then `shilpSutra.configs['flat/recommended']`. Catches deprecated APIs, peer-cliff barrel imports, TW3 classes — most autofixable. Use the `migration` preset (`pnpm eslint --fix`) when upgrading across breaking versions.
49
+
48
50
  ## OPTIONAL PEER DEPENDENCIES (install BEFORE first import)
49
51
 
50
52
  | When you import… | Install |
package/llms.txt CHANGED
@@ -44,7 +44,7 @@ The repo URL for these files is `https://github.com/devalok-design/shilp-sutra/t
44
44
  ## NEW (v0.40.0)
45
45
 
46
46
  - **OAuthButton.** Brand-aware social/login buttons. Subpath: `@devalok/shilp-sutra/ui/oauth-button`. 13 providers (`google` `apple` `github` `microsoft` `x` `linkedin` `facebook` `discord` `slack` `gitlab` `sso` `email` `passkey`). Props: `provider`, `intent` (`continue|signin|signup`), `appearance` (`brand|outline|dark`), `icon` (override default glyph), `iconOnly`, `compact` (renders just "Google" instead of "Continue with Google"; aria-label keeps long form), `lastUsed` (inline right-edge pill inside button), `helperText`. Inherits Button async/loading/sizes. Siblings: `OAuthGroup` (with `reorderLastUsedFirst` for Stripe-style ordering), `OAuthDivider`, `OAuthConnectionRow` (settings-page linked state). Default glyphs from Tabler peer dep; pass `icon` to drop in a brand's official multicolour SVG. In dark mode every brand appearance lands on the same DS surface — brand identity comes from the glyph, not the bg, so rows stay visually coherent.
47
- - **Icon API unification.** Every icon-accepting prop (`startIcon`, `endIcon`, `icon`, `leftIcon`, `rightIcon`) across 22 components now takes one type: **`IconInput`**. Pass a rendered element (`<Icon icon={IconPlus} />` or `<IconPlus />`), a component ref (`IconPlus`), or any custom node — all four shapes work interchangeably. Type widening only; every call that compiled before still compiles. Helpers exported for your own wrappers: `import type { IconInput } from '@devalok/shilp-sutra/ui/lib/icon-input'` + `import { normalizeIcon } from '@devalok/shilp-sutra/ui/lib/normalize-icon'`. `IconProvider` now sizes icons via context — delete `className="h-4 w-4"` overrides.
47
+ - **Icon API unification.** Every icon-accepting prop (`startIcon`, `endIcon`, `icon`, `leftIcon`, `rightIcon`) across 22 components now takes one type: **`IconInput`**. Pass a rendered element (`<Icon icon={IconPlus} />` or `<IconPlus />`), a component ref (`IconPlus`), or any custom node — all four shapes work interchangeably. **Mostly non-breaking, one narrowing:** for the 14 components whose `icon` prop was previously `React.ReactNode`, `IconInput` excludes `string`/`number`/iterables — if you pass icons from a `Record<string, React.ReactNode>` map or `?: React.ReactNode` field, retype the source to `React.ReactElement` (`tsc`-only; affects `CommandItem.icon`, `ActivityItem.icon`, `Chat.Message.Avatar`). Helpers exported for your own wrappers: `import type { IconInput } from '@devalok/shilp-sutra/ui/lib/icon-input'` + `import { normalizeIcon } from '@devalok/shilp-sutra/ui/lib/normalize-icon'`. `IconProvider` now sizes icons via context — delete `className="h-4 w-4"` overrides.
48
48
  - **Polymorphic `Text` / `Stack` / `Container`.** The `as` prop now widens accepted attributes to the rendered element: `<Text as="label" htmlFor="email">`, `<Text as="a" href="/x">`, `<Stack as="ul" role="list">`, `<Container as="main" aria-label>` all typecheck. Default element behavior unchanged.
49
49
  - **Agent-friendly install experience.** `AGENTS.md` now ships in the tarball (`node_modules/@devalok/shilp-sutra/AGENTS.md`), discoverable by 25+ agent tools. `package.json` declares an `agents` field (npm-agentskills convention) so `pnpm dlx @codemcp/agentskills export` auto-installs the bundled skill. New postinstall welcome banner (silent in CI / non-TTY / `SHILP_SUTRA_NO_WELCOME=1`). `troubleshoot.md` gained peer-cliff symptom entries.
50
50
  - **`llms-quick.txt`.** New ≤15K-token fast-path summary in the tarball — fits in one Read on any agent. Read order is now `llms-quick.txt` → `llms.txt` → `llms-full.txt`.
@@ -441,7 +441,7 @@ pnpm add @devalok/shilp-sutra
441
441
 
442
442
  Add to next.config.js:
443
443
  ```js
444
- transpilePackages: ["@devalok/shilp-sutra", "@devalok/shilp-sutra-brand"]
444
+ transpilePackages: ["@devalok/shilp-sutra"]
445
445
  ```
446
446
 
447
447
  // Import components (barrel):
@@ -534,6 +534,8 @@ import { Icon } from '@devalok/shilp-sutra/ui/icon'
534
534
 
535
535
  All four work identically at the call site. The component wraps its icon slot in `<IconProvider size={...}>` so size + stroke flow via React context — no `className="h-4 w-4"` overrides needed.
536
536
 
537
+ **Upgrading 0.39→0.40 — one narrowing:** `IconInput` (`ReactElement | ComponentType | null | undefined`) excludes `string`/`number`/iterables that `React.ReactNode` allows. The 14 components previously typed `React.ReactNode` (Combobox, Stepper, TreeItem, OAuthButton, AppCommandPalette, CommandRegistry, BottomNavbar, Sidebar nav items, TopBar, Chat.Message.Avatar, SystemMessage, AIConversation, ActivityFeed, CommandPalette) now accept less. If you feed them icons from a `Record<string, React.ReactNode>` map or `?: React.ReactNode` field, `tsc` fails — retype the source to `React.ReactElement`. Build-time only; runtime JSX is unaffected.
538
+
537
539
  **Components on the unified API:** Button, IconButton, Badge, Combobox, SegmentedControl, Stepper, StatCard, TreeItem (TreeNode.icon), OAuthButton (icon + linkedIcon), Chat.Message.Avatar, Chat.Message.Action, Chat.SystemMessage, AIConversation (agent.icon), AICommandProvider (agent.icon), CommandBar (item.icon), EmptyState (kills the dual ReactNode|ComponentType signature), BulkActionBar (action.icon), ActivityFeed (item.icon), CommandPalette (item.icon), TopBar (UserMenuItem.icon, TopBar.IconButton.icon), Sidebar (NavItem.icon, NavSubItem.icon, footer.promo.icon), BottomNavbar (item.icon), AppCommandPalette (SearchResult.icon), CommandRegistry (CommandPageItem.icon).
538
540
 
539
541
  **Internals** (`<Toaster>`, `<Toast>`'s success/error icons) use Sonner's own type contract and don't accept consumer-passed icons — that's by design.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@devalok/shilp-sutra",
3
- "version": "0.40.0",
3
+ "version": "0.40.1",
4
4
  "description": "Devalok Design System — accessible React components, OKLCH design tokens, and Tailwind 4 CSS-first setup. Ships with AI-agent setup recipes.",
5
5
  "license": "MIT",
6
6
  "author": "Devalok Design & Strategy Studios <shilp-sutra@devalok.in>",
@@ -81,6 +81,21 @@ function alreadyWelcomed(version) {
81
81
  }
82
82
  }
83
83
 
84
+ // Returns the version recorded in the sentinel from a prior install, or null
85
+ // on first install / unreadable sentinel. Used to detect a version JUMP so the
86
+ // banner can point upgraders at MIGRATION.md.
87
+ function getPreviousVersion() {
88
+ const sentinel = getSentinelPath()
89
+ if (!sentinel) return null
90
+ try {
91
+ if (!existsSync(sentinel)) return null
92
+ const prev = readFileSync(sentinel, 'utf-8').trim()
93
+ return prev || null
94
+ } catch {
95
+ return null
96
+ }
97
+ }
98
+
84
99
  function markWelcomed(version) {
85
100
  const sentinel = getSentinelPath()
86
101
  if (!sentinel) return
@@ -143,7 +158,7 @@ function centeredLotusRow(braille) {
143
158
  return `│ ${colour(braille, PINK)} │`
144
159
  }
145
160
 
146
- function buildFullBanner(version) {
161
+ function buildFullBanner(version, prevVersion) {
147
162
  const lines = []
148
163
  lines.push(colour(TOP, PINK_DIM))
149
164
  lines.push(colour(EMPTY, PINK_DIM))
@@ -151,8 +166,15 @@ function buildFullBanner(version) {
151
166
  lines.push(`${colour('│', PINK_DIM)} ${colour(lotusRow, PINK)} ${colour('│', PINK_DIM)}`)
152
167
  }
153
168
  lines.push(colour(EMPTY, PINK_DIM))
154
- lines.push(row(` ${colour('✦', PINK)} ${colour('@devalok/shilp-sutra', BOLD)} ${version}`))
155
- lines.push(row(` ${colour('Tailwind 4 design system · 110+ components · RSC-safe', DIM)}`))
169
+ if (prevVersion && prevVersion !== version) {
170
+ lines.push(row(` ${colour('✦', PINK)} ${colour('@devalok/shilp-sutra', BOLD)} ${prevVersion} → ${version}`))
171
+ lines.push(row(` ${colour('⚠', PINK)} ${colour('Version changed — review breaking changes before deploy:', BOLD)}`))
172
+ lines.push(row(` ${colour('node_modules/@devalok/shilp-sutra/MIGRATION.md', DIM)}`))
173
+ lines.push(row(` ${colour('+ docs/recipes/upgrading.md (safe-upgrade procedure)', DIM)}`))
174
+ } else {
175
+ lines.push(row(` ${colour('✦', PINK)} ${colour('@devalok/shilp-sutra', BOLD)} ${version}`))
176
+ lines.push(row(` ${colour('Tailwind 4 design system · 110+ components · RSC-safe', DIM)}`))
177
+ }
156
178
  lines.push(colour(EMPTY, PINK_DIM))
157
179
  lines.push(row(` ${colour('▸', PINK)} Setup recipe (pick your framework):`))
158
180
  lines.push(row(` ${colour('node_modules/@devalok/shilp-sutra/docs/recipes/', DIM)}`))
@@ -172,10 +194,17 @@ function buildFullBanner(version) {
172
194
  return lines.join('\n')
173
195
  }
174
196
 
175
- function buildCompactBanner(version) {
197
+ function buildCompactBanner(version, prevVersion) {
198
+ const head =
199
+ prevVersion && prevVersion !== version
200
+ ? [
201
+ `${colour('✦', PINK)} ${colour('@devalok/shilp-sutra', BOLD)} ${prevVersion} → ${version}`,
202
+ ` ${colour('⚠', PINK)} Version changed — review ${colour('node_modules/@devalok/shilp-sutra/MIGRATION.md', DIM)} before deploy`,
203
+ ]
204
+ : [`${colour('✦', PINK)} ${colour('@devalok/shilp-sutra', BOLD)} ${version} ${colour('· Tailwind 4 design system', DIM)}`]
176
205
  return [
177
206
  '',
178
- `${colour('✦', PINK)} ${colour('@devalok/shilp-sutra', BOLD)} ${version} ${colour('· Tailwind 4 design system', DIM)}`,
207
+ ...head,
179
208
  ` ${colour('▸', PINK)} Setup: ${colour('node_modules/@devalok/shilp-sutra/docs/recipes/', DIM)}`,
180
209
  ` ${colour('▸', PINK)} Theme: ${colour('https://shilp-sutra.devalok.in/themer', DIM)}`,
181
210
  ` ${colour('▸', PINK)} AI: ${colour('cp -r node_modules/@devalok/shilp-sutra/skill ~/.claude/skills/shilp-sutra', DIM)}`,
@@ -201,11 +230,15 @@ function main() {
201
230
 
202
231
  if (!preview && alreadyWelcomed(version)) return
203
232
 
233
+ // Detect a version jump so the banner can route upgraders to MIGRATION.md.
234
+ // --preview simulates an upgrade so maintainers can verify the upgrade layout.
235
+ const prevVersion = preview ? '0.39.0' : getPreviousVersion()
236
+
204
237
  const cols = process.stdout.columns || 80
205
238
  const rows = process.stdout.rows || 40
206
239
  const fitsFull = !forceCompact && cols >= 70 && rows >= 28
207
240
 
208
- const banner = fitsFull ? buildFullBanner(version) : buildCompactBanner(version)
241
+ const banner = fitsFull ? buildFullBanner(version, prevVersion) : buildCompactBanner(version, prevVersion)
209
242
  process.stdout.write('\n' + banner + '\n')
210
243
 
211
244
  if (!preview) markWelcomed(version)
package/skill/SKILL.md CHANGED
@@ -3,7 +3,7 @@ name: shilp-sutra
3
3
  description: Add, configure, and use components from Devalok's shilp-sutra design system (@devalok/shilp-sutra) — a Tailwind 4 + React 19 + CVA library with 110+ accessible components, OKLCH design tokens, framer-motion animations, and per-component RSC-safe entry points. Use this skill whenever the user mentions shilp-sutra, Devalok, the @devalok npm scope, or asks to install/add/style/theme UI in any React project that already depends on the package — even if they don't name it explicitly. Use it instead of generic shadcn/ui, MUI, or Chakra knowledge when shilp-sutra is in the project. Covers Next.js (App + Pages), Vite, Astro, Remix, TanStack Start setup playbooks; component API and variant reference; brand token customization; Server Component import patterns; and a troubleshoot tree for the thirteen most common breakages.
4
4
  license: MIT
5
5
  metadata:
6
- version: "0.40.0"
6
+ version: "0.40.1"
7
7
  author: Devalok Design & Strategy Studios
8
8
  homepage: https://github.com/devalok-design/shilp-sutra
9
9
  npm: https://www.npmjs.com/package/@devalok/shilp-sutra
@@ -17,7 +17,7 @@ metadata:
17
17
  ## When this skill triggers
18
18
 
19
19
  - The user mentions `shilp-sutra`, `@devalok`, Devalok, or Devalok's design system.
20
- - The project's `package.json` lists `@devalok/shilp-sutra` (or `@devalok/shilp-sutra-brand`).
20
+ - The project's `package.json` lists `@devalok/shilp-sutra` or `@devalok/eslint-plugin-shilp-sutra`.
21
21
  - The user asks you to add UI components, set up a design system, install Tailwind, or theme an app in a project that already has the package.
22
22
  - The user asks to migrate from shadcn/MUI/Chakra to shilp-sutra, or vice-versa.
23
23
 
@@ -33,7 +33,7 @@ Q2. What does the user want to do?
33
33
  b) Change colors/fonts/radius → references/customize-brand.md
34
34
  c) Server Components / Next.js → references/server-components.md
35
35
  d) Something is broken → references/troubleshoot.md
36
- e) Upgrading from older version → fetch MIGRATION.md from repo or node_modules
36
+ e) Upgrading from older version → references/upgrading.md (then MIGRATION.md for the target version)
37
37
  ```
38
38
 
39
39
  ## First-time setup
@@ -56,6 +56,7 @@ Every line in those recipes is there because skipping it broke a real consumer.
56
56
 
57
57
  These are non-negotiable. Violating any of them produces runtime errors that look unrelated to the design system.
58
58
 
59
+ 0. **On any version bump, never report the upgrade as safe before reading the COMPLETE changelog + `MIGRATION.md` for the target version.** Breaking entries are often ordered last (changesets sorts by file, not severity), and breaks are frequently type-level (a prop type narrowed, a symbol moved between barrels) that only `tsc`/`build` catches. Grep the codebase for moved/renamed/narrowed symbols, run `typecheck` + `build`, and prefer the ESLint migration preset (`@devalok/eslint-plugin-shilp-sutra`) for the mechanical edits. Full procedure: `references/upgrading.md`.
59
60
  1. **Tailwind 4 only.** Do not create `tailwind.config.ts` with `presets: [shilpSutra]`. The JS preset was removed in 0.38. Setup is CSS-only:
60
61
  ```css
61
62
  @import "tailwindcss";
@@ -117,8 +118,6 @@ If the user is in a hurry and asks you to just pick something, default to `arche
117
118
 
118
119
  `references/customize-brand.md` covers what the Themer doesn't expose yet: font swap, spacing scale, focus-ring overrides, dark-mode mapping nuance. Customization is CSS-only: override CSS custom properties under `:root` and `.dark`. There is no theme provider component.
119
120
 
120
- For the Devalok logo and other brand assets, the sibling package `@devalok/shilp-sutra-brand` ships SVG + PNG. Install it separately.
121
-
122
121
  ## Server Components and import patterns
123
122
 
124
123
  `references/server-components.md` has the per-component RSC-safety matrix. The short version:
@@ -126,7 +125,11 @@ For the Devalok logo and other brand assets, the sibling package `@devalok/shilp
126
125
  - Layout, typography, and presentational components (`Text`, `Heading`, `Card`, `Container`, `Stack`) → server-safe.
127
126
  - Interactive components (`Button`, `Dialog`, `Combobox`, `DataTable`, anything with hooks or `framer-motion`) → must be inside a `"use client"` boundary.
128
127
  - The package already injects `"use client"` directives where needed; importing per-component (`/ui/dialog`) gives the bundler the right hint.
129
- - Next.js consumers must add `transpilePackages: ["@devalok/shilp-sutra", "@devalok/shilp-sutra-brand"]` to `next.config.*`.
128
+ - Next.js consumers must add `transpilePackages: ["@devalok/shilp-sutra"]` to `next.config.*`.
129
+
130
+ ## Linting and migration
131
+
132
+ Recommend the companion ESLint plugin **`@devalok/eslint-plugin-shilp-sutra`** when setting up or upgrading a project: `pnpm add -D @devalok/eslint-plugin-shilp-sutra`, then `shilpSutra.configs['flat/recommended']` in `eslint.config.ts`. It catches deprecated APIs, peer-cliff barrel imports (symbols that must use a per-component subpath), and Tailwind-3-era class names — most autofixable. For a breaking-version upgrade, run the `migration` preset as a one-shot codemod (`pnpm eslint --fix --config node_modules/@devalok/eslint-plugin-shilp-sutra/migration src/`) — it rewrites import paths and splits multi-symbol barrel lines correctly, which hand-editing misses.
130
133
 
131
134
  ## When something breaks
132
135
 
@@ -7,7 +7,7 @@
7
7
  > All variant values and props verified from source CVA definitions.
8
8
  >
9
9
  > Package: @devalok/shilp-sutra
10
- > Version: 0.40.0
10
+ > Version: 0.40.1
11
11
  >
12
12
  > **If you are an AI agent reading this file top-to-bottom:** the Setup
13
13
  > section below is authoritative. If any later per-component doc or a
@@ -46,7 +46,7 @@ The repo URL for these files is `https://github.com/devalok-design/shilp-sutra/t
46
46
  ## NEW (v0.40.0)
47
47
 
48
48
  - **OAuthButton.** Brand-aware social/login buttons. Subpath: `@devalok/shilp-sutra/ui/oauth-button`. 13 providers (`google` `apple` `github` `microsoft` `x` `linkedin` `facebook` `discord` `slack` `gitlab` `sso` `email` `passkey`). Props: `provider`, `intent` (`continue|signin|signup`), `appearance` (`brand|outline|dark`), `icon` (override default glyph), `iconOnly`, `compact` (renders just "Google" instead of "Continue with Google"; aria-label keeps long form), `lastUsed` (inline right-edge pill inside button), `helperText`. Inherits Button async/loading/sizes. Siblings: `OAuthGroup` (with `reorderLastUsedFirst` for Stripe-style ordering), `OAuthDivider`, `OAuthConnectionRow` (settings-page linked state). Default glyphs from Tabler peer dep; pass `icon` to drop in a brand's official multicolour SVG. In dark mode every brand appearance lands on the same DS surface — brand identity comes from the glyph, not the bg, so rows stay visually coherent.
49
- - **Icon API unification.** Every icon-accepting prop (`startIcon`, `endIcon`, `icon`, `leftIcon`, `rightIcon`) across 22 components now takes one type: **`IconInput`**. Pass a rendered element (`<Icon icon={IconPlus} />` or `<IconPlus />`), a component ref (`IconPlus`), or any custom node — all four shapes work interchangeably. Type widening only; every call that compiled before still compiles. Helpers exported for your own wrappers: `import type { IconInput } from '@devalok/shilp-sutra/ui/lib/icon-input'` + `import { normalizeIcon } from '@devalok/shilp-sutra/ui/lib/normalize-icon'`. `IconProvider` now sizes icons via context — delete `className="h-4 w-4"` overrides.
49
+ - **Icon API unification.** Every icon-accepting prop (`startIcon`, `endIcon`, `icon`, `leftIcon`, `rightIcon`) across 22 components now takes one type: **`IconInput`**. Pass a rendered element (`<Icon icon={IconPlus} />` or `<IconPlus />`), a component ref (`IconPlus`), or any custom node — all four shapes work interchangeably. **Mostly non-breaking, one narrowing:** for the 14 components whose `icon` prop was previously `React.ReactNode`, `IconInput` excludes `string`/`number`/iterables — if you pass icons from a `Record<string, React.ReactNode>` map or `?: React.ReactNode` field, retype the source to `React.ReactElement` (`tsc`-only; affects `CommandItem.icon`, `ActivityItem.icon`, `Chat.Message.Avatar`). Helpers exported for your own wrappers: `import type { IconInput } from '@devalok/shilp-sutra/ui/lib/icon-input'` + `import { normalizeIcon } from '@devalok/shilp-sutra/ui/lib/normalize-icon'`. `IconProvider` now sizes icons via context — delete `className="h-4 w-4"` overrides.
50
50
  - **Polymorphic `Text` / `Stack` / `Container`.** The `as` prop now widens accepted attributes to the rendered element: `<Text as="label" htmlFor="email">`, `<Text as="a" href="/x">`, `<Stack as="ul" role="list">`, `<Container as="main" aria-label>` all typecheck. Default element behavior unchanged.
51
51
  - **Agent-friendly install experience.** `AGENTS.md` now ships in the tarball (`node_modules/@devalok/shilp-sutra/AGENTS.md`), discoverable by 25+ agent tools. `package.json` declares an `agents` field (npm-agentskills convention) so `pnpm dlx @codemcp/agentskills export` auto-installs the bundled skill. New postinstall welcome banner (silent in CI / non-TTY / `SHILP_SUTRA_NO_WELCOME=1`). `troubleshoot.md` gained peer-cliff symptom entries.
52
52
  - **`llms-quick.txt`.** New ≤15K-token fast-path summary in the tarball — fits in one Read on any agent. Read order is now `llms-quick.txt` → `llms.txt` → `llms-full.txt`.
@@ -443,7 +443,7 @@ pnpm add @devalok/shilp-sutra
443
443
 
444
444
  Add to next.config.js:
445
445
  ```js
446
- transpilePackages: ["@devalok/shilp-sutra", "@devalok/shilp-sutra-brand"]
446
+ transpilePackages: ["@devalok/shilp-sutra"]
447
447
  ```
448
448
 
449
449
  // Import components (barrel):
@@ -536,6 +536,8 @@ import { Icon } from '@devalok/shilp-sutra/ui/icon'
536
536
 
537
537
  All four work identically at the call site. The component wraps its icon slot in `<IconProvider size={...}>` so size + stroke flow via React context — no `className="h-4 w-4"` overrides needed.
538
538
 
539
+ **Upgrading 0.39→0.40 — one narrowing:** `IconInput` (`ReactElement | ComponentType | null | undefined`) excludes `string`/`number`/iterables that `React.ReactNode` allows. The 14 components previously typed `React.ReactNode` (Combobox, Stepper, TreeItem, OAuthButton, AppCommandPalette, CommandRegistry, BottomNavbar, Sidebar nav items, TopBar, Chat.Message.Avatar, SystemMessage, AIConversation, ActivityFeed, CommandPalette) now accept less. If you feed them icons from a `Record<string, React.ReactNode>` map or `?: React.ReactNode` field, `tsc` fails — retype the source to `React.ReactElement`. Build-time only; runtime JSX is unaffected.
540
+
539
541
  **Components on the unified API:** Button, IconButton, Badge, Combobox, SegmentedControl, Stepper, StatCard, TreeItem (TreeNode.icon), OAuthButton (icon + linkedIcon), Chat.Message.Avatar, Chat.Message.Action, Chat.SystemMessage, AIConversation (agent.icon), AICommandProvider (agent.icon), CommandBar (item.icon), EmptyState (kills the dual ReactNode|ComponentType signature), BulkActionBar (action.icon), ActivityFeed (item.icon), CommandPalette (item.icon), TopBar (UserMenuItem.icon, TopBar.IconButton.icon), Sidebar (NavItem.icon, NavSubItem.icon, footer.promo.icon), BottomNavbar (item.icon), AppCommandPalette (SearchResult.icon), CommandRegistry (CommandPageItem.icon).
540
542
 
541
543
  **Internals** (`<Toaster>`, `<Toast>`'s success/error icons) use Sonner's own type contract and don't accept consumer-passed icons — that's by design.
@@ -42,12 +42,6 @@ Add only if you will render `<Toaster />`:
42
42
  pnpm add sonner
43
43
  ```
44
44
 
45
- Add brand assets package if you need Devalok or Karm logos:
46
-
47
- ```bash
48
- pnpm add @devalok/shilp-sutra-brand
49
- ```
50
-
51
45
  ### 2a. Optional peer dependencies (install ONLY when importing the matching subpath)
52
46
 
53
47
  Some components depend on third-party libraries that ship as optional peers. **Install BEFORE first import** of the matching component, or `next build` will exit with `Module not found`. Skip entirely if you only use core components (`Button`, `Text`, `Stack`, `Dialog`, `Toast`, `Form*`, `Input`, `Card`, etc.).
@@ -121,7 +115,7 @@ Edit `next.config.{ts,js,mjs}`. Add the `transpilePackages` field:
121
115
  import type { NextConfig } from "next";
122
116
 
123
117
  const nextConfig: NextConfig = {
124
- transpilePackages: ["@devalok/shilp-sutra", "@devalok/shilp-sutra-brand"],
118
+ transpilePackages: ["@devalok/shilp-sutra"],
125
119
  };
126
120
 
127
121
  export default nextConfig;
@@ -94,11 +94,9 @@ After editing, delete the lockfile + `node_modules` and reinstall.
94
94
  Add:
95
95
 
96
96
  ```ts
97
- transpilePackages: ["@devalok/shilp-sutra", "@devalok/shilp-sutra-brand"],
97
+ transpilePackages: ["@devalok/shilp-sutra"],
98
98
  ```
99
99
 
100
- If `@devalok/shilp-sutra-brand` is not installed, list only `@devalok/shilp-sutra`.
101
-
102
100
  ## Symptom: Build error `Cannot find module 'sonner' / 'input-otp' / 'date-fns' / '@tiptap/react' / 'react-pdf' / 'react-markdown' / '@emoji-mart/react'`
103
101
 
104
102
  **Diagnosis:** an optional peer dependency is missing. Each component below has a peer it pulls only when imported. Install the matching peer (always BEFORE the first import):
@@ -119,6 +117,8 @@ If `@devalok/shilp-sutra-brand` is not installed, list only `@devalok/shilp-sutr
119
117
 
120
118
  These ship as **optional** peers so consumers who never render the matching component don't pay the install cost. Once you import the component, the peer becomes required. Each affected component's JSDoc carries the same install hint — hover the import in your editor to see it inline.
121
119
 
120
+ **Catch this at edit time, not build time:** install `@devalok/eslint-plugin-shilp-sutra` (`pnpm add -D @devalok/eslint-plugin-shilp-sutra`, then `shilpSutra.configs['flat/recommended']`). Its `prefer-per-component-import` rule flags peer-cliff symbols imported from a barrel and autofixes the path — surfacing the cliff in your editor before the bundler ever fails.
121
+
122
122
  For the full table in your framework's install recipe, see `install-<framework>.md → §2a. Optional peer dependencies`.
123
123
 
124
124
  ## Symptom: Hydration warning on every page load (Next.js)
@@ -0,0 +1,54 @@
1
+ <!-- Source: packages/core/docs/recipes/upgrading.md — do not edit directly. Regenerate with `node scripts/build-skill.mjs`. -->
2
+
3
+ # Upgrading shilp-sutra safely
4
+
5
+ A version bump is **not** safe-by-default. Breaking changes in this design system are often type-level (prop types narrowed, symbols moved between barrels) and only surface at `tsc`/build time — never assume a bump is low-risk before doing the checks below.
6
+
7
+ > **Hard rule for AI agents:** On ANY version bump, do not report the upgrade as safe before you have read the COMPLETE changelog **and** `MIGRATION.md` for the target version, grepped the consumer codebase for moved/renamed/narrowed symbols, and run `typecheck` + `build`. Breaking entries are frequently ordered LAST in the changelog (changesets orders by file, not severity) — skimming the top and relaxing is the #1 failure mode.
8
+
9
+ ## Step 1 — read the full breaking surface
10
+
11
+ 1. Open the target version's section in `node_modules/@devalok/shilp-sutra/CHANGELOG.md` (or the GitHub release). **Read all of it**, not just the top.
12
+ 2. Open `node_modules/@devalok/shilp-sutra/MIGRATION.md` and read every section from your current version up to the target.
13
+ 3. Scan for these signals — each is a potential break:
14
+ - `feat!` / `BREAKING` headers
15
+ - "removed", "moved", "renamed", "narrowed", "no longer exported"
16
+ - any prop **type** change (a narrowing — new type accepts less than the old — fails `tsc` for values that compiled before)
17
+ - peer-dependency changes (a symbol now imported from a per-component subpath instead of the barrel)
18
+
19
+ ## Step 2 — find affected call sites in your code
20
+
21
+ ```bash
22
+ # Symbols moved out of barrels (0.40.0 peer-cliff cleanup example):
23
+ grep -rn "from '@devalok/shilp-sutra/ui'" src/ | grep -E "Toaster|toast|InputOTP"
24
+ grep -rn "from '@devalok/shilp-sutra/composed'" src/ | grep -E "DatePicker|EmojiPicker|FilePreview|MarkdownViewer|RichTextEditor|RichChatInput"
25
+ grep -rn "from '@devalok/shilp-sutra/ai'" src/ | grep -E "BlockRenderer|ErrorBlock|TextBlock"
26
+
27
+ # Type narrowings — find icon sources annotated as the wide React.ReactNode:
28
+ grep -rn "React.ReactNode" src/ | grep -i "icon"
29
+ ```
30
+
31
+ ## Step 3 — let the ESLint plugin do the mechanical work
32
+
33
+ ```bash
34
+ pnpm add -D @devalok/eslint-plugin-shilp-sutra
35
+ # one-shot codemod: repoints moved barrel imports + flags deprecated APIs
36
+ pnpm eslint --fix --config node_modules/@devalok/eslint-plugin-shilp-sutra/migration src/
37
+ ```
38
+
39
+ The `prefer-per-component-import` rule autofixes barrel→subpath moves and splits multi-symbol lines correctly. It cannot retype your own annotations — narrowings (Step 4) are still manual.
40
+
41
+ ## Step 4 — typecheck + build BEFORE deploy
42
+
43
+ ```bash
44
+ pnpm typecheck && pnpm build
45
+ ```
46
+
47
+ This is the gate that catches type narrowings the changelog may have under-described. Two real examples from 0.40.0:
48
+
49
+ - **Barrel peer-cliff cleanup (breaking):** `import { Toaster } from '@devalok/shilp-sutra/ui'` → `import { Toaster } from '@devalok/shilp-sutra/ui/toaster'`. 12 symbol families. Full table in `MIGRATION.md → v0.40.0`.
50
+ - **Icon API narrowing:** the unified `IconInput` type excludes `string`/`number`/iterables that `React.ReactNode` allowed. If you stored icons in a `Record<string, React.ReactNode>` map or `icon?: React.ReactNode` field and passed them to a migrated component (`CommandItem.icon`, `ActivityItem.icon`, `Chat.Message.Avatar`), retype the source to `React.ReactElement`.
51
+
52
+ ## Step 5 — only now is the bump "safe"
53
+
54
+ Report the upgrade as complete only after Steps 1-4 pass. If anything in the changelog was ambiguous, prefer a per-prop `expectTypeOf` probe over assuming "non-breaking".