@devalok/shilp-sutra 0.40.0 → 0.41.0
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 +28 -1
- package/BREAKING.json +66 -0
- package/BREAKING.schema.json +184 -0
- package/MIGRATION.md +26 -6
- package/README.md +1 -1
- package/docs/recipes/index.md +1 -0
- package/docs/recipes/install-next-app-router.md +55 -21
- package/docs/recipes/troubleshoot.md +3 -3
- package/docs/recipes/upgrading.md +71 -0
- package/llms-full.txt +1 -1
- package/llms-quick.txt +6 -2
- package/llms.txt +5 -2
- package/package.json +5 -1
- package/scripts/welcome.mjs +39 -6
- package/skill/SKILL.md +11 -8
- package/skill/references/components-full.md +1 -1
- package/skill/references/components.md +5 -2
- package/skill/references/setup-next-app-router.md +55 -21
- package/skill/references/troubleshoot.md +3 -3
- package/skill/references/upgrading.md +73 -0
package/AGENTS.md
CHANGED
|
@@ -24,7 +24,8 @@ 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`** —
|
|
27
|
+
5. **`packages/core/docs/recipes/upgrading.md`** + **`MIGRATION.md`** — read BOTH before any version bump. See the hard constraint below.
|
|
28
|
+
6. **`node_modules/@devalok/shilp-sutra/BREAKING.json`** — machine-readable manifest of every breaking change per version (moves, type narrowings, removals, renames). Read this programmatically when planning an upgrade — schema in `BREAKING.schema.json`. Lets you answer "does my code import any of these moved symbols?" without parsing CHANGELOG prose.
|
|
28
29
|
|
|
29
30
|
When the package is installed in a consumer project, the same files live at:
|
|
30
31
|
|
|
@@ -60,6 +61,7 @@ If the framework is not in the table, fall back to **`install-vite.md`** (closes
|
|
|
60
61
|
|
|
61
62
|
## Hard constraints (these break things if violated)
|
|
62
63
|
|
|
64
|
+
- **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. **Fastest path: parse `node_modules/@devalok/shilp-sutra/BREAKING.json` for the version range** — it has the structured break data so you can grep your codebase for affected symbols deterministically. Then 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
65
|
- **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
66
|
```css
|
|
65
67
|
@import "tailwindcss";
|
|
@@ -73,6 +75,31 @@ If the framework is not in the table, fall back to **`install-vite.md`** (closes
|
|
|
73
75
|
- **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
76
|
- **Default to `variant="soft"`** over `variant="outline"` for non-primary Button actions.
|
|
75
77
|
|
|
78
|
+
## Spacing cadence (when building layouts — forms, settings, login, anything with vertical rhythm)
|
|
79
|
+
|
|
80
|
+
The `--spacing-ds-*` scale runs `ds-01`→`ds-09` (2px→48px) with many adjacent steps. It is a *primitive* scale, not a layout recipe. When you compose a UI, **do not reach for every adjacent token** — that produces 5-6 near-identical gaps the eye can't group, and rhythm reads muddy.
|
|
81
|
+
|
|
82
|
+
**Pick a 3-tier cadence — each tier visibly ~1.5-2× the previous, so each gap is distinct:**
|
|
83
|
+
|
|
84
|
+
| Tier | Token (px) | Use for |
|
|
85
|
+
|---|---|---|
|
|
86
|
+
| Related (one unit) | `ds-03` (8) | label ↔ field, eyebrow ↔ title, items inside one micro-cluster |
|
|
87
|
+
| Grouped (a section's items) | `ds-05` (16) | header-stack → form, between stacked field-groups, multi-line footers |
|
|
88
|
+
| Section (independent blocks) | `ds-07` (32) | between page sections, form ↔ footer cluster |
|
|
89
|
+
| Hero (optional 4th) | `ds-08`+ (40+) | top page chrome, brand block → first content |
|
|
90
|
+
|
|
91
|
+
**Rules of thumb:**
|
|
92
|
+
- Before stamping a `gap-ds-*` / `space-y-ds-*`, decide which tier the spacing is. Use the matching token.
|
|
93
|
+
- Aim for **3-4 distinct spacing values total on one surface.** Using 5+ is the signal your rhythm is muddy.
|
|
94
|
+
- **Anti-pattern:** using `ds-02` (4) AND `ds-04` (12) as *different* relationship signals on the same surface — they collapse into "vaguely tight" instead of two clear tiers. Prefer `ds-03`; skip `ds-02`/`ds-04` unless deliberate (e.g. a chip gap at `ds-01`).
|
|
95
|
+
- The squint test must still reveal the grouping. (Refactoring UI / Apple HIG / Material all converge on 3-4 perceptual tiers; near-linear scales like 4/8/12/16 blur because the proportional jump shrinks as values grow.)
|
|
96
|
+
|
|
97
|
+
This is guidance, not a hard constraint — a deliberate exception is fine. The default keeps agent-built UIs from drifting into flat, ungrouped spacing.
|
|
98
|
+
|
|
99
|
+
## Linting + migration (recommend this to the user)
|
|
100
|
+
|
|
101
|
+
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.
|
|
102
|
+
|
|
76
103
|
## When something fails
|
|
77
104
|
|
|
78
105
|
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/BREAKING.json
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "./BREAKING.schema.json",
|
|
3
|
+
"package": "@devalok/shilp-sutra",
|
|
4
|
+
"versions": {
|
|
5
|
+
"0.40.0": {
|
|
6
|
+
"summary": "Barrel peer-cliff cleanup — 12 symbol families removed from /ui, /composed, /ai barrels (per-component subpath only). Icon API unification narrows the `icon` prop on 14 components from React.ReactNode to IconInput.",
|
|
7
|
+
"migrationDoc": "MIGRATION.md#v0400--barrel-peer-cliff-cleanup--icon-api-unification",
|
|
8
|
+
"moved": [
|
|
9
|
+
{ "symbol": "Toaster", "from": "@devalok/shilp-sutra/ui", "to": "@devalok/shilp-sutra/ui/toaster", "peer": "sonner", "eslintRule": "prefer-per-component-import" },
|
|
10
|
+
{ "symbol": "toast", "from": "@devalok/shilp-sutra/ui", "to": "@devalok/shilp-sutra/ui/toast", "peer": "sonner", "eslintRule": "prefer-per-component-import" },
|
|
11
|
+
{ "symbol": "InputOTP", "from": "@devalok/shilp-sutra/ui", "to": "@devalok/shilp-sutra/ui/input-otp", "peer": "input-otp", "eslintRule": "prefer-per-component-import" },
|
|
12
|
+
{ "symbol": "DatePicker", "from": "@devalok/shilp-sutra/composed", "to": "@devalok/shilp-sutra/composed/date-picker", "peer": "date-fns", "eslintRule": "prefer-per-component-import" },
|
|
13
|
+
{ "symbol": "DateRangePicker", "from": "@devalok/shilp-sutra/composed", "to": "@devalok/shilp-sutra/composed/date-picker", "peer": "date-fns", "eslintRule": "prefer-per-component-import" },
|
|
14
|
+
{ "symbol": "DateTimePicker", "from": "@devalok/shilp-sutra/composed", "to": "@devalok/shilp-sutra/composed/date-picker", "peer": "date-fns", "eslintRule": "prefer-per-component-import" },
|
|
15
|
+
{ "symbol": "TimePicker", "from": "@devalok/shilp-sutra/composed", "to": "@devalok/shilp-sutra/composed/date-picker", "peer": "date-fns", "eslintRule": "prefer-per-component-import" },
|
|
16
|
+
{ "symbol": "CalendarGrid", "from": "@devalok/shilp-sutra/composed", "to": "@devalok/shilp-sutra/composed/date-picker", "peer": "date-fns", "eslintRule": "prefer-per-component-import" },
|
|
17
|
+
{ "symbol": "MonthPicker", "from": "@devalok/shilp-sutra/composed", "to": "@devalok/shilp-sutra/composed/date-picker", "peer": "date-fns", "eslintRule": "prefer-per-component-import" },
|
|
18
|
+
{ "symbol": "YearPicker", "from": "@devalok/shilp-sutra/composed", "to": "@devalok/shilp-sutra/composed/date-picker", "peer": "date-fns", "eslintRule": "prefer-per-component-import" },
|
|
19
|
+
{ "symbol": "Presets", "from": "@devalok/shilp-sutra/composed", "to": "@devalok/shilp-sutra/composed/date-picker", "peer": "date-fns", "eslintRule": "prefer-per-component-import" },
|
|
20
|
+
{ "symbol": "useCalendar", "from": "@devalok/shilp-sutra/composed", "to": "@devalok/shilp-sutra/composed/date-picker", "peer": "date-fns", "eslintRule": "prefer-per-component-import" },
|
|
21
|
+
{ "symbol": "EmojiPicker", "from": "@devalok/shilp-sutra/composed", "to": "@devalok/shilp-sutra/composed/emoji-picker", "peer": "@emoji-mart/data", "eslintRule": "prefer-per-component-import" },
|
|
22
|
+
{ "symbol": "EmojiPickerPopover", "from": "@devalok/shilp-sutra/composed", "to": "@devalok/shilp-sutra/composed/emoji-picker", "peer": "@emoji-mart/data", "eslintRule": "prefer-per-component-import" },
|
|
23
|
+
{ "symbol": "EmojiNode", "from": "@devalok/shilp-sutra/composed", "to": "@devalok/shilp-sutra/composed/extensions/emoji-node", "peer": "@tiptap/react", "eslintRule": "prefer-per-component-import" },
|
|
24
|
+
{ "symbol": "createEmojiSuggestion", "from": "@devalok/shilp-sutra/composed", "to": "@devalok/shilp-sutra/composed/extensions/emoji-suggestion", "peer": "@tiptap/react", "eslintRule": "prefer-per-component-import" },
|
|
25
|
+
{ "symbol": "FilePreview", "from": "@devalok/shilp-sutra/composed", "to": "@devalok/shilp-sutra/composed/file-preview", "peer": "react-pdf", "eslintRule": "prefer-per-component-import" },
|
|
26
|
+
{ "symbol": "MarkdownViewer", "from": "@devalok/shilp-sutra/composed", "to": "@devalok/shilp-sutra/composed/markdown-viewer", "peer": "react-markdown", "eslintRule": "prefer-per-component-import" },
|
|
27
|
+
{ "symbol": "RichTextEditor", "from": "@devalok/shilp-sutra/composed", "to": "@devalok/shilp-sutra/composed/rich-text-editor", "peer": "@tiptap/react", "eslintRule": "prefer-per-component-import" },
|
|
28
|
+
{ "symbol": "RichTextViewer", "from": "@devalok/shilp-sutra/composed", "to": "@devalok/shilp-sutra/composed/rich-text-editor", "peer": "@tiptap/react", "eslintRule": "prefer-per-component-import" },
|
|
29
|
+
{ "symbol": "RichChatInput", "from": "@devalok/shilp-sutra/composed", "to": "@devalok/shilp-sutra/composed/rich-chat-input", "peer": "@tiptap/react", "eslintRule": "prefer-per-component-import" },
|
|
30
|
+
{ "symbol": "AudioPlayer", "from": "@devalok/shilp-sutra/composed", "to": "@devalok/shilp-sutra/composed/rich-chat-input", "peer": "@tiptap/react", "eslintRule": "prefer-per-component-import" },
|
|
31
|
+
{ "symbol": "AudioWaveform", "from": "@devalok/shilp-sutra/composed", "to": "@devalok/shilp-sutra/composed/rich-chat-input", "peer": "@tiptap/react", "eslintRule": "prefer-per-component-import" },
|
|
32
|
+
{ "symbol": "useVoiceRecorder", "from": "@devalok/shilp-sutra/composed", "to": "@devalok/shilp-sutra/composed/rich-chat-input", "peer": "@tiptap/react", "eslintRule": "prefer-per-component-import" },
|
|
33
|
+
{ "symbol": "BlockRenderer", "from": "@devalok/shilp-sutra/ai", "to": "@devalok/shilp-sutra/ai/block-renderer", "peer": "react-markdown", "eslintRule": "prefer-per-component-import" },
|
|
34
|
+
{ "symbol": "ErrorBlock", "from": "@devalok/shilp-sutra/ai/blocks", "to": "@devalok/shilp-sutra/ai/blocks/error", "peer": "react-markdown", "eslintRule": "prefer-per-component-import" },
|
|
35
|
+
{ "symbol": "TextBlock", "from": "@devalok/shilp-sutra/ai/blocks", "to": "@devalok/shilp-sutra/ai/blocks/text", "peer": "react-markdown", "eslintRule": "prefer-per-component-import" }
|
|
36
|
+
],
|
|
37
|
+
"narrowed": [
|
|
38
|
+
{
|
|
39
|
+
"prop": "icon",
|
|
40
|
+
"components": [
|
|
41
|
+
"Combobox option",
|
|
42
|
+
"Stepper step",
|
|
43
|
+
"TreeItem",
|
|
44
|
+
"OAuthButton",
|
|
45
|
+
"AppCommandPalette",
|
|
46
|
+
"CommandRegistry",
|
|
47
|
+
"BottomNavbar",
|
|
48
|
+
"Sidebar NavItem",
|
|
49
|
+
"Sidebar NavSubItem",
|
|
50
|
+
"Sidebar footer.promo",
|
|
51
|
+
"TopBar UserMenuItem",
|
|
52
|
+
"Chat.Message.Avatar",
|
|
53
|
+
"Chat.SystemMessage",
|
|
54
|
+
"AIConversation agent",
|
|
55
|
+
"ActivityFeed item",
|
|
56
|
+
"CommandPalette item",
|
|
57
|
+
"CommandBar item"
|
|
58
|
+
],
|
|
59
|
+
"from": "React.ReactNode",
|
|
60
|
+
"to": "IconInput",
|
|
61
|
+
"fix": "Retype the icon source from React.ReactNode to React.ReactElement (or import the IconInput type). Build-time only; runtime JSX is unaffected."
|
|
62
|
+
}
|
|
63
|
+
]
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://shilp-sutra.devalok.in/breaking-manifest.schema.json",
|
|
4
|
+
"title": "shilp-sutra BREAKING manifest",
|
|
5
|
+
"description": "Machine-readable record of breaking changes per published version. Lets AI agents and migration tools answer 'what breaks between X and Y?' programmatically instead of parsing CHANGELOG prose. Cumulative — every prior version's breaks remain so a consumer jumping 0.37 → 0.41 can see every break in range.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"required": ["package", "versions"],
|
|
8
|
+
"additionalProperties": false,
|
|
9
|
+
"properties": {
|
|
10
|
+
"$schema": { "type": "string" },
|
|
11
|
+
"package": {
|
|
12
|
+
"type": "string",
|
|
13
|
+
"description": "npm package name this manifest covers.",
|
|
14
|
+
"examples": ["@devalok/shilp-sutra"]
|
|
15
|
+
},
|
|
16
|
+
"versions": {
|
|
17
|
+
"type": "object",
|
|
18
|
+
"description": "Map of semver version → breaking-change record. A version only appears if it had at least one breaking change. Non-breaking releases are absent (an empty object would falsely signal 'this version had breaks I forgot to document').",
|
|
19
|
+
"patternProperties": {
|
|
20
|
+
"^\\d+\\.\\d+\\.\\d+$": { "$ref": "#/$defs/versionEntry" }
|
|
21
|
+
},
|
|
22
|
+
"additionalProperties": false
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"$defs": {
|
|
26
|
+
"versionEntry": {
|
|
27
|
+
"type": "object",
|
|
28
|
+
"additionalProperties": false,
|
|
29
|
+
"description": "All breaks shipped in this version. Every present field is an array; omit empty fields entirely. A consumer planning an upgrade reads the union of all entries between current and target version.",
|
|
30
|
+
"properties": {
|
|
31
|
+
"summary": {
|
|
32
|
+
"type": "string",
|
|
33
|
+
"description": "One-line human summary of the version's breaks. Mirrors the `> ⚠️ Breaking in X.Y.Z` callout in CHANGELOG."
|
|
34
|
+
},
|
|
35
|
+
"migrationDoc": {
|
|
36
|
+
"type": "string",
|
|
37
|
+
"description": "Relative path inside the package tarball to the human migration guide for this version.",
|
|
38
|
+
"examples": ["MIGRATION.md#v0400--barrel-peer-cliff-cleanup--icon-api-unification"]
|
|
39
|
+
},
|
|
40
|
+
"moved": {
|
|
41
|
+
"type": "array",
|
|
42
|
+
"description": "Symbols whose import path changed. Old path no longer exports the symbol; new path does. Consumer fix is a search-and-replace of the import path; prop/type signatures are unchanged.",
|
|
43
|
+
"items": { "$ref": "#/$defs/move" }
|
|
44
|
+
},
|
|
45
|
+
"narrowed": {
|
|
46
|
+
"type": "array",
|
|
47
|
+
"description": "Prop types that accept LESS than before. A consumer value valid under the old type may now fail tsc. Not a runtime break. The fix is typically retyping the value's source.",
|
|
48
|
+
"items": { "$ref": "#/$defs/narrowing" }
|
|
49
|
+
},
|
|
50
|
+
"removed": {
|
|
51
|
+
"type": "array",
|
|
52
|
+
"description": "Symbols (components, props, variants, exports) deleted with no replacement on the same path. If a renamed-with-deprecation, list under `renamed` instead.",
|
|
53
|
+
"items": { "$ref": "#/$defs/removal" }
|
|
54
|
+
},
|
|
55
|
+
"renamed": {
|
|
56
|
+
"type": "array",
|
|
57
|
+
"description": "Symbols whose name changed in place (same import path, new export name) or whose prop name changed (same component, new prop name).",
|
|
58
|
+
"items": { "$ref": "#/$defs/rename" }
|
|
59
|
+
},
|
|
60
|
+
"notes": {
|
|
61
|
+
"type": "array",
|
|
62
|
+
"description": "Free-form breaking changes that don't fit the structured categories above (e.g. visual-only changes, DOM structure shifts, default-value flips). Reader must check these manually — the migration is NOT one-shot autofixable.",
|
|
63
|
+
"items": { "type": "string" }
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
"move": {
|
|
68
|
+
"type": "object",
|
|
69
|
+
"required": ["symbol", "from", "to"],
|
|
70
|
+
"additionalProperties": false,
|
|
71
|
+
"properties": {
|
|
72
|
+
"symbol": {
|
|
73
|
+
"type": "string",
|
|
74
|
+
"description": "Exported name as it appears in an import statement.",
|
|
75
|
+
"examples": ["Toaster", "DatePicker"]
|
|
76
|
+
},
|
|
77
|
+
"from": {
|
|
78
|
+
"type": "string",
|
|
79
|
+
"description": "Old import path (no longer exports this symbol after this version).",
|
|
80
|
+
"examples": ["@devalok/shilp-sutra/ui"]
|
|
81
|
+
},
|
|
82
|
+
"to": {
|
|
83
|
+
"type": "string",
|
|
84
|
+
"description": "New import path (exports this symbol going forward).",
|
|
85
|
+
"examples": ["@devalok/shilp-sutra/ui/toaster"]
|
|
86
|
+
},
|
|
87
|
+
"peer": {
|
|
88
|
+
"type": "string",
|
|
89
|
+
"description": "Optional. If this move was driven by a peer-cliff cleanup, the peer dependency the new subpath pulls.",
|
|
90
|
+
"examples": ["sonner", "input-otp", "date-fns"]
|
|
91
|
+
},
|
|
92
|
+
"eslintRule": {
|
|
93
|
+
"type": "string",
|
|
94
|
+
"description": "Optional. The `@devalok/eslint-plugin-shilp-sutra` rule that autofixes this move.",
|
|
95
|
+
"examples": ["prefer-per-component-import"]
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
},
|
|
99
|
+
"narrowing": {
|
|
100
|
+
"type": "object",
|
|
101
|
+
"required": ["prop", "components", "from", "to", "fix"],
|
|
102
|
+
"additionalProperties": false,
|
|
103
|
+
"properties": {
|
|
104
|
+
"prop": {
|
|
105
|
+
"type": "string",
|
|
106
|
+
"description": "Prop name whose type narrowed.",
|
|
107
|
+
"examples": ["icon", "startIcon"]
|
|
108
|
+
},
|
|
109
|
+
"components": {
|
|
110
|
+
"type": "array",
|
|
111
|
+
"description": "Components whose this-prop narrowed. List each affected component explicitly — the same prop name on a component whose type was already narrower than the new type is NOT affected.",
|
|
112
|
+
"items": { "type": "string" },
|
|
113
|
+
"examples": [["CommandItem", "ActivityItem", "Chat.Message.Avatar"]]
|
|
114
|
+
},
|
|
115
|
+
"from": {
|
|
116
|
+
"type": "string",
|
|
117
|
+
"description": "Previous TypeScript type, as a string. Use the exact source form.",
|
|
118
|
+
"examples": ["React.ReactNode"]
|
|
119
|
+
},
|
|
120
|
+
"to": {
|
|
121
|
+
"type": "string",
|
|
122
|
+
"description": "New TypeScript type, as a string. Must be a subset of `from` (otherwise it's a widening, not a narrowing).",
|
|
123
|
+
"examples": ["IconInput"]
|
|
124
|
+
},
|
|
125
|
+
"fix": {
|
|
126
|
+
"type": "string",
|
|
127
|
+
"description": "One-line consumer fix. Should be actionable from the type alone.",
|
|
128
|
+
"examples": ["Retype the icon source from React.ReactNode to React.ReactElement."]
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
},
|
|
132
|
+
"removal": {
|
|
133
|
+
"type": "object",
|
|
134
|
+
"required": ["symbol", "where"],
|
|
135
|
+
"additionalProperties": false,
|
|
136
|
+
"properties": {
|
|
137
|
+
"symbol": {
|
|
138
|
+
"type": "string",
|
|
139
|
+
"examples": ["variant=\"filled\""]
|
|
140
|
+
},
|
|
141
|
+
"where": {
|
|
142
|
+
"type": "string",
|
|
143
|
+
"description": "What the symbol was on — a component, an import path, or a token namespace.",
|
|
144
|
+
"examples": ["Alert", "@devalok/shilp-sutra/tailwind"]
|
|
145
|
+
},
|
|
146
|
+
"replacement": {
|
|
147
|
+
"type": "string",
|
|
148
|
+
"description": "Optional. If a replacement exists at the same site, name it. If there is none, omit.",
|
|
149
|
+
"examples": ["variant=\"solid\""]
|
|
150
|
+
},
|
|
151
|
+
"deprecatedSince": {
|
|
152
|
+
"type": "string",
|
|
153
|
+
"description": "Optional. The version that first emitted a deprecation warning, if a cycle was observed.",
|
|
154
|
+
"examples": ["0.32.0"]
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
},
|
|
158
|
+
"rename": {
|
|
159
|
+
"type": "object",
|
|
160
|
+
"required": ["from", "to", "where"],
|
|
161
|
+
"additionalProperties": false,
|
|
162
|
+
"properties": {
|
|
163
|
+
"from": {
|
|
164
|
+
"type": "string",
|
|
165
|
+
"examples": ["isLoadingMore"]
|
|
166
|
+
},
|
|
167
|
+
"to": {
|
|
168
|
+
"type": "string",
|
|
169
|
+
"examples": ["loadingMore"]
|
|
170
|
+
},
|
|
171
|
+
"where": {
|
|
172
|
+
"type": "string",
|
|
173
|
+
"description": "Component or import path the rename applies to.",
|
|
174
|
+
"examples": ["MessageList"]
|
|
175
|
+
},
|
|
176
|
+
"kind": {
|
|
177
|
+
"type": "string",
|
|
178
|
+
"enum": ["prop", "export", "variant"],
|
|
179
|
+
"default": "prop"
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
package/MIGRATION.md
CHANGED
|
@@ -4,13 +4,21 @@ This page indexes all breaking changes across `@devalok/shilp-sutra` versions. F
|
|
|
4
4
|
|
|
5
5
|
> **Upgrading from < 0.36?** Start here, then read each intermediate version section. Breaking changes stack — skipping versions means stacking migrations.
|
|
6
6
|
|
|
7
|
+
## v0.41.0 — `BREAKING.json` manifest + recipe polish (no migration needed)
|
|
8
|
+
|
|
9
|
+
**Non-breaking minor.** No consumer code changes required.
|
|
10
|
+
|
|
11
|
+
- **New:** `packages/core/BREAKING.json` ships in the tarball — a machine-readable record of every breaking change per version. AI agents and migration tooling can `import manifest from '@devalok/shilp-sutra/BREAKING.json'` instead of parsing this file. Schema at `BREAKING.schema.json`.
|
|
12
|
+
- **Docs:** Next.js App Router install recipe gained a Tested-on matrix, explicit replace-the-whole-scaffold-globals.css guidance, Turbopack note, and three new gotchas (`pnpm-workspace.yaml`, auto-generated `AGENTS.md` markers, scaffold body-font cascade). No setup change required for existing consumers.
|
|
13
|
+
- **Internals:** release.yml now regenerates Agent Skill references before the pre-publish audit (kills the skill-drift email spam class). No impact on the published tarball.
|
|
14
|
+
|
|
7
15
|
## v0.40.0 — Barrel peer-cliff cleanup + Icon API unification
|
|
8
16
|
|
|
9
17
|
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
18
|
|
|
11
|
-
### Icon API unification (non-breaking)
|
|
19
|
+
### Icon API unification (mostly non-breaking — one narrowing)
|
|
12
20
|
|
|
13
|
-
**
|
|
21
|
+
**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
22
|
|
|
15
23
|
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
24
|
|
|
@@ -44,7 +52,7 @@ For every prop now typed as `IconInput`, all four shapes work identically:
|
|
|
44
52
|
<Button startIcon={<span>+</span>}>OK</Button> // custom node
|
|
45
53
|
```
|
|
46
54
|
|
|
47
|
-
**Calls
|
|
55
|
+
**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
56
|
|
|
49
57
|
**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
58
|
|
|
@@ -115,9 +123,19 @@ For each symbol below, change ONLY the import path. Prop / type signatures are u
|
|
|
115
123
|
|
|
116
124
|
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
125
|
|
|
118
|
-
#### Codemod helper
|
|
126
|
+
#### Codemod helper (recommended)
|
|
119
127
|
|
|
120
|
-
|
|
128
|
+
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:
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
pnpm add -D @devalok/eslint-plugin-shilp-sutra
|
|
132
|
+
# one-shot codemod across your source
|
|
133
|
+
pnpm eslint --fix --config node_modules/@devalok/eslint-plugin-shilp-sutra/migration src/
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
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.
|
|
137
|
+
|
|
138
|
+
<details><summary>Manual <code>sed</code> fallback (single-symbol lines only)</summary>
|
|
121
139
|
|
|
122
140
|
```bash
|
|
123
141
|
# Replace barrel imports of toast / Toaster with per-component imports
|
|
@@ -125,7 +143,9 @@ grep -rl "from '@devalok/shilp-sutra/ui'" src/ | xargs sed -i.bak \
|
|
|
125
143
|
-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
144
|
```
|
|
127
145
|
|
|
128
|
-
(Adjust per project — the regex assumes a single `toast` import on the line. For multi-symbol lines,
|
|
146
|
+
(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.)
|
|
147
|
+
|
|
148
|
+
</details>
|
|
129
149
|
|
|
130
150
|
#### Per-chart subpaths added (non-breaking)
|
|
131
151
|
|
package/README.md
CHANGED
package/docs/recipes/index.md
CHANGED
|
@@ -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
|
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
> Setup recipe for adding `@devalok/shilp-sutra` to a Next.js 13+ App Router project.
|
|
4
4
|
|
|
5
|
+
> **Tested cold-install on:** Next 16.2.6 + React 19.2 + Turbopack + pnpm 10.30 + Node 22 + Windows 11 (2026-05-25). Earlier versions of Next 13/14/15 are still supported on this recipe; deltas called out inline.
|
|
6
|
+
|
|
5
7
|
## 1. Detect the framework
|
|
6
8
|
|
|
7
9
|
You are in this recipe if **all** of these are true:
|
|
@@ -40,12 +42,6 @@ Add only if you will render `<Toaster />`:
|
|
|
40
42
|
pnpm add sonner
|
|
41
43
|
```
|
|
42
44
|
|
|
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
45
|
### 2a. Optional peer dependencies (install ONLY when importing the matching subpath)
|
|
50
46
|
|
|
51
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.).
|
|
@@ -65,27 +61,35 @@ Some components depend on third-party libraries that ship as optional peers. **I
|
|
|
65
61
|
|
|
66
62
|
## 3. Configure PostCSS
|
|
67
63
|
|
|
68
|
-
|
|
64
|
+
**Next 14+ scaffolds this for you.** Verify `postcss.config.mjs` (or `.js` / `.cjs` / `.json`) at the project root contains:
|
|
69
65
|
|
|
70
66
|
```js
|
|
71
|
-
|
|
67
|
+
const config = {
|
|
72
68
|
plugins: {
|
|
73
69
|
"@tailwindcss/postcss": {},
|
|
74
70
|
},
|
|
75
71
|
};
|
|
72
|
+
|
|
73
|
+
export default config;
|
|
76
74
|
```
|
|
77
75
|
|
|
78
|
-
If
|
|
76
|
+
If the file is missing, create it with the contents above. If another PostCSS file exists with different plugins, merge `@tailwindcss/postcss` in — do not delete the existing file.
|
|
79
77
|
|
|
80
78
|
## 4. Wire Tailwind 4 + design tokens
|
|
81
79
|
|
|
82
|
-
Locate the global CSS file
|
|
80
|
+
### 4a. Locate the global CSS file
|
|
81
|
+
|
|
82
|
+
Next 13+ default location depends on the `--src-dir` choice at scaffold time:
|
|
83
|
+
|
|
84
|
+
- `src/app/globals.css` — default since Next 14 when scaffolded without `--src-dir false`. **Most common in Next 16+ defaults.**
|
|
85
|
+
- `app/globals.css` — when scaffolded with `--src-dir false` (no `src/`).
|
|
86
|
+
- `app/global.css` — older Next 13 scaffolds.
|
|
83
87
|
|
|
84
|
-
|
|
85
|
-
- `src/app/globals.css`
|
|
86
|
-
- `app/global.css`
|
|
88
|
+
If none exists, create at whichever path matches the project's existing `app/` location.
|
|
87
89
|
|
|
88
|
-
|
|
90
|
+
### 4b. Replace the scaffold's CSS with the shilp-sutra setup
|
|
91
|
+
|
|
92
|
+
A fresh `create-next-app` scaffold writes a `globals.css` with `:root` color vars, an `@theme inline` block linked to Geist font vars, a `prefers-color-scheme` dark override, and a `body { font-family: Arial }` block. **Replace the entire file** with the shilp-sutra setup unless you have a specific reason to keep scaffold styles:
|
|
89
93
|
|
|
90
94
|
```css
|
|
91
95
|
@import "tailwindcss";
|
|
@@ -94,7 +98,11 @@ If none exists, create `app/globals.css`. Set the file contents to (or merge int
|
|
|
94
98
|
|
|
95
99
|
**Order matters.** `tailwindcss` MUST come first. The shilp-sutra `/css` entry registers `@theme` blocks that the Tailwind import must process.
|
|
96
100
|
|
|
97
|
-
If the
|
|
101
|
+
If you want to keep the scaffold's color/font vars alongside shilp-sutra tokens (rare — usually you want one or the other), put scaffold's `@theme inline` / `:root` / `body` blocks AFTER the shilp-sutra import. Otherwise the scaffold's `body { font-family: Arial }` will compete with shilp-sutra's font setup.
|
|
102
|
+
|
|
103
|
+
### 4c. Optional: add your own theme overrides
|
|
104
|
+
|
|
105
|
+
Place after both imports:
|
|
98
106
|
|
|
99
107
|
```css
|
|
100
108
|
@import "tailwindcss";
|
|
@@ -105,12 +113,16 @@ If the project has its own theme overrides, place them AFTER both imports:
|
|
|
105
113
|
}
|
|
106
114
|
```
|
|
107
115
|
|
|
108
|
-
|
|
116
|
+
### 4d. Ensure CSS is imported from the layout
|
|
117
|
+
|
|
118
|
+
The CSS import in `app/layout.tsx` (or `src/app/layout.tsx`) should already exist in a fresh scaffold:
|
|
109
119
|
|
|
110
120
|
```tsx
|
|
111
121
|
import "./globals.css";
|
|
112
122
|
```
|
|
113
123
|
|
|
124
|
+
If you removed the Geist-font import from layout.tsx (see § 6), keep this CSS import line.
|
|
125
|
+
|
|
114
126
|
## 5. Configure `transpilePackages`
|
|
115
127
|
|
|
116
128
|
Edit `next.config.{ts,js,mjs}`. Add the `transpilePackages` field:
|
|
@@ -119,7 +131,7 @@ Edit `next.config.{ts,js,mjs}`. Add the `transpilePackages` field:
|
|
|
119
131
|
import type { NextConfig } from "next";
|
|
120
132
|
|
|
121
133
|
const nextConfig: NextConfig = {
|
|
122
|
-
transpilePackages: ["@devalok/shilp-sutra"
|
|
134
|
+
transpilePackages: ["@devalok/shilp-sutra"],
|
|
123
135
|
};
|
|
124
136
|
|
|
125
137
|
export default nextConfig;
|
|
@@ -129,6 +141,8 @@ If a `transpilePackages` array already exists, append to it. Do not replace.
|
|
|
129
141
|
|
|
130
142
|
Without `transpilePackages`, Next will refuse to load our pre-built `dist/*.js` because it ships native ESM that does not match Next's CJS-leaning loader for `node_modules`.
|
|
131
143
|
|
|
144
|
+
**Turbopack:** as of Next 16, Turbopack is the default bundler (`next dev` and `next build`). `transpilePackages` is respected by both Turbopack and the Webpack backend. Tested on Turbopack 16.2 cold-install (2026-05-25); no extra config needed.
|
|
145
|
+
|
|
132
146
|
## 6. Scaffold the Providers wrapper
|
|
133
147
|
|
|
134
148
|
Create `app/providers.tsx`:
|
|
@@ -155,13 +169,21 @@ export function Providers({ children }: { children: ReactNode }) {
|
|
|
155
169
|
- Drop the `Toaster` import and its JSX usage
|
|
156
170
|
- Skip installing `sonner`
|
|
157
171
|
|
|
158
|
-
Mount `<Providers>` from `app/layout.tsx
|
|
172
|
+
Mount `<Providers>` from `app/layout.tsx` (or `src/app/layout.tsx`). **Replace the scaffold's layout** with the version below — the scaffold imports `next/font/google` (Geist) and applies font-variable classes to `<html>`, which you don't need when shilp-sutra ships its own fonts:
|
|
159
173
|
|
|
160
174
|
```tsx
|
|
175
|
+
import type { Metadata } from "next";
|
|
161
176
|
import "./globals.css";
|
|
162
177
|
import { Providers } from "./providers";
|
|
163
178
|
|
|
164
|
-
export
|
|
179
|
+
export const metadata: Metadata = {
|
|
180
|
+
title: "Your app",
|
|
181
|
+
description: "Built with shilp-sutra",
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
export default function RootLayout({
|
|
185
|
+
children,
|
|
186
|
+
}: Readonly<{ children: React.ReactNode }>) {
|
|
165
187
|
return (
|
|
166
188
|
<html lang="en" suppressHydrationWarning>
|
|
167
189
|
<body>
|
|
@@ -172,11 +194,20 @@ export default function RootLayout({ children }: { children: React.ReactNode })
|
|
|
172
194
|
}
|
|
173
195
|
```
|
|
174
196
|
|
|
197
|
+
Specifically, **remove these scaffold lines**:
|
|
198
|
+
|
|
199
|
+
- `import { Geist, Geist_Mono } from "next/font/google";`
|
|
200
|
+
- The `const geistSans = Geist({...})` and `const geistMono = Geist_Mono({...})` blocks
|
|
201
|
+
- The `className={`${geistSans.variable} ${geistMono.variable} h-full antialiased`}` on `<html>`
|
|
202
|
+
- The `className="min-h-full flex flex-col"` on `<body>`
|
|
203
|
+
|
|
204
|
+
If you want to keep Geist alongside shilp-sutra's fonts, leave the `Geist` imports — but know that shilp-sutra's `font-sans` token resolves to Inter (body) and Ranade (display), not to the scaffold's `--font-geist-sans`. Loading both is wasted bytes.
|
|
205
|
+
|
|
175
206
|
`suppressHydrationWarning` on `<html>` is required because `next-themes` writes the `class` attribute before React hydrates. Without it, every page logs a hydration warning.
|
|
176
207
|
|
|
177
208
|
## 7. Verify the install
|
|
178
209
|
|
|
179
|
-
Replace the contents of `app/page.tsx
|
|
210
|
+
Replace the contents of `app/page.tsx` (or `src/app/page.tsx`). The scaffold's `page.tsx` imports `next/image` and renders the Vercel marketing layout — replace the whole file:
|
|
180
211
|
|
|
181
212
|
```tsx
|
|
182
213
|
import { Button } from "@devalok/shilp-sutra/ui/button";
|
|
@@ -215,6 +246,7 @@ If anything is off, see [troubleshoot.md](./troubleshoot.md).
|
|
|
215
246
|
## 8. Common gotchas
|
|
216
247
|
|
|
217
248
|
- **CSS import order.** `tailwindcss` BEFORE `@devalok/shilp-sutra/css`. Reversing the order silently produces a build with no design-system utilities.
|
|
249
|
+
- **Scaffold's `body { font-family: Arial }` overrides shilp-sutra fonts.** The `create-next-app` `globals.css` template sets `font-family: Arial, Helvetica, sans-serif` on `<body>`. If you kept the scaffold's body styles, they win the cascade over shilp-sutra's `font-sans`. § 4b says to replace the whole file — follow it.
|
|
218
250
|
- **Multiple `framer-motion` copies.** Run `pnpm why framer-motion`. If it shows more than one resolved version, contexts (`MotionConfig`, `LayoutGroup`, `AnimatePresence`) silently break. Fix:
|
|
219
251
|
```jsonc
|
|
220
252
|
// package.json
|
|
@@ -228,7 +260,9 @@ If anything is off, see [troubleshoot.md](./troubleshoot.md).
|
|
|
228
260
|
```
|
|
229
261
|
For npm/yarn/bun equivalents, see [troubleshoot.md](./troubleshoot.md).
|
|
230
262
|
- **Per-component imports keep RSC fast AND avoid peer-dep cliffs.** Inside Server Components, prefer `@devalok/shilp-sutra/ui/text`, `…/composed/page-header`, etc. The barrel `@devalok/shilp-sutra/ui` re-exports many client components — including some with hard peer-dep imports (e.g. `input-otp`) — so it both inflates the client bundle and forces those peers to be installed even when you never render those components. See [server-components.md](./server-components.md) for the full RSC-safety matrix.
|
|
231
|
-
- **`p-3` vs `p-ds-03` — both are valid.** DS spacing uses the `--spacing-ds-*` namespace (`p-ds-04`, `gap-ds-03`); Tailwind 4's default numeric scale (`p-4`, `gap-2`) coexists by design. Pick `p-ds-*` for values that should track DS theme changes (card padding, form gaps); pick `p-N` for one-off layout values (section breathing room). Do NOT mass-codemod `p-4` → `p-ds-04` — that is not what the package intends.
|
|
263
|
+
- **`p-3` vs `p-ds-03` — both are valid.** DS spacing uses the `--spacing-ds-*` namespace (`p-ds-04`, `gap-ds-03`); Tailwind 4's default numeric scale (`p-4`, `gap-2`) coexists by design. Pick `p-ds-*` for values that should track DS theme changes (card padding, form gaps); pick `p-N` for one-off layout values (section breathing room). Do NOT mass-codemod `p-4` → `p-ds-04` — that is not what the package intends. For layout rhythm, pick a 3-tier cadence (`ds-03` related / `ds-05` grouped / `ds-07` section), not every adjacent token.
|
|
264
|
+
- **Auto-generated `pnpm-workspace.yaml`.** `pnpm 10+` writes a `pnpm-workspace.yaml` at the project root on first install with `ignoredBuiltDependencies` entries. This is harmless for a standalone app, but if you're nesting this project inside a larger monorepo, delete this file and use the parent monorepo's workspace config instead.
|
|
265
|
+
- **Auto-generated `AGENTS.md`.** `create-next-app` writes an `AGENTS.md` with managed `<!-- BEGIN:nextjs-agent-rules -->` / `<!-- END:nextjs-agent-rules -->` markers. Shilp Sutra's agent rules use `<!-- BEGIN:shilp-sutra-agent-rules -->` markers — they coexist cleanly. If you install the shilp-sutra Agent Skill (see the repo root `AGENTS.md` for the one-liner), it adds its block alongside Next's, not over it.
|
|
232
266
|
- **Bare `shadow` is dead.** Tailwind 4 has no `--shadow-DEFAULT`. Use `shadow-raised`, `shadow-overlay`, or `shadow-floating`.
|
|
233
267
|
|
|
234
268
|
## 9. What you should NOT do
|
|
@@ -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"
|
|
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)
|