@devalok/shilp-sutra 0.51.0 → 0.53.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.
Files changed (81) hide show
  1. package/AGENTS.md +1 -1
  2. package/MIGRATION.md +12 -0
  3. package/dist/_chunks/motion-provider.js +7 -6
  4. package/dist/_chunks/motion-provider.js.map +1 -1
  5. package/dist/_chunks/success.js +53 -53
  6. package/dist/ai/command-bar.js +158 -158
  7. package/dist/ai/command-bar.js.map +1 -1
  8. package/dist/ai/conversation.js +5 -5
  9. package/dist/composed/command-palette.js +17 -17
  10. package/dist/composed/command-palette.js.map +1 -1
  11. package/dist/composed/priority-indicator.d.ts +15 -6
  12. package/dist/composed/priority-indicator.d.ts.map +1 -1
  13. package/dist/composed/priority-indicator.js +37 -88
  14. package/dist/composed/priority-indicator.js.map +1 -1
  15. package/dist/composed/schedule-view.d.ts +17 -2
  16. package/dist/composed/schedule-view.d.ts.map +1 -1
  17. package/dist/composed/schedule-view.js +163 -64
  18. package/dist/composed/schedule-view.js.map +1 -1
  19. package/dist/motion/motion-provider.d.ts.map +1 -1
  20. package/dist/shell/bottom-navbar.d.ts +32 -6
  21. package/dist/shell/bottom-navbar.d.ts.map +1 -1
  22. package/dist/shell/bottom-navbar.js +156 -129
  23. package/dist/shell/bottom-navbar.js.map +1 -1
  24. package/dist/tokens/primitives.css +6 -2
  25. package/dist/tokens/semantic.css +20 -1
  26. package/dist/tokens/utilities.css +6 -0
  27. package/dist/ui/autocomplete.d.ts +34 -32
  28. package/dist/ui/autocomplete.d.ts.map +1 -1
  29. package/dist/ui/autocomplete.js +115 -110
  30. package/dist/ui/autocomplete.js.map +1 -1
  31. package/dist/ui/button-group.js +2 -0
  32. package/dist/ui/button-group.js.map +1 -1
  33. package/dist/ui/button-processing.d.ts.map +1 -1
  34. package/dist/ui/button-processing.js +1 -0
  35. package/dist/ui/button-processing.js.map +1 -1
  36. package/dist/ui/button.d.ts +3 -3
  37. package/dist/ui/button.d.ts.map +1 -1
  38. package/dist/ui/button.js +26 -0
  39. package/dist/ui/button.js.map +1 -1
  40. package/dist/ui/color-input.d.ts.map +1 -1
  41. package/dist/ui/color-input.js +82 -82
  42. package/dist/ui/color-input.js.map +1 -1
  43. package/dist/ui/combobox.js +4 -4
  44. package/dist/ui/icon.d.ts +2 -0
  45. package/dist/ui/icon.d.ts.map +1 -1
  46. package/dist/ui/icon.js +31 -26
  47. package/dist/ui/icon.js.map +1 -1
  48. package/dist/ui/index.js +30 -30
  49. package/dist/ui/search-input.d.ts.map +1 -1
  50. package/dist/ui/search-input.js +5 -4
  51. package/dist/ui/search-input.js.map +1 -1
  52. package/dist/ui/segmented-control.d.ts +28 -6
  53. package/dist/ui/segmented-control.d.ts.map +1 -1
  54. package/dist/ui/segmented-control.js +61 -43
  55. package/dist/ui/segmented-control.js.map +1 -1
  56. package/dist/ui/sidebar.js +7 -7
  57. package/dist/ui/split-button.d.ts.map +1 -1
  58. package/dist/ui/split-button.js +7 -0
  59. package/dist/ui/split-button.js.map +1 -1
  60. package/docs/components/composed/priority-indicator.md +22 -11
  61. package/docs/components/composed/schedule-view.md +20 -5
  62. package/docs/components/shell/bottom-navbar.md +24 -5
  63. package/docs/components/ui/autocomplete.md +26 -10
  64. package/docs/components/ui/button.md +2 -2
  65. package/docs/components/ui/segmented-control.md +31 -11
  66. package/docs/recipes/install-remix.md +3 -3
  67. package/docs/recipes/install-vite.md +3 -3
  68. package/docs/recipes/server-components.md +2 -2
  69. package/llms.txt +1 -1
  70. package/make-kit/foundations/color.md +20 -0
  71. package/make-kit/foundations/dark-mode.md +6 -9
  72. package/make-kit/foundations/icons.md +3 -3
  73. package/make-kit/setup.md +4 -4
  74. package/mcp-manifest.json +239 -35
  75. package/package.json +1 -1
  76. package/scripts/welcome.mjs +59 -1
  77. package/skill/SKILL.md +1 -1
  78. package/skill/references/components.md +1 -1
  79. package/skill/references/server-components.md +2 -2
  80. package/skill/references/setup-remix.md +3 -3
  81. package/skill/references/setup-vite.md +3 -3
package/mcp-manifest.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "$schema": "./mcp-manifest.schema.json",
3
3
  "manifestVersion": "1.2.0",
4
4
  "package": "@devalok/shilp-sutra",
5
- "packageVersion": "0.51.0",
5
+ "packageVersion": "0.53.0",
6
6
  "components": {
7
7
  "accordion": {
8
8
  "displayName": "Accordion",
@@ -386,7 +386,16 @@
386
386
  "name": "union",
387
387
  "raw": "AutocompleteOption | null"
388
388
  },
389
- "required": false
389
+ "required": false,
390
+ "description": "controlled"
391
+ },
392
+ "defaultValue": {
393
+ "type": {
394
+ "name": "union",
395
+ "raw": "AutocompleteOption | null"
396
+ },
397
+ "required": false,
398
+ "description": "uncontrolled initial"
390
399
  },
391
400
  "onValueChange": {
392
401
  "type": {
@@ -414,6 +423,46 @@
414
423
  },
415
424
  "required": false
416
425
  },
426
+ "size": {
427
+ "type": {
428
+ "name": "object",
429
+ "raw": "(forwarded to Input)"
430
+ },
431
+ "required": false
432
+ },
433
+ "state": {
434
+ "type": {
435
+ "name": "enum",
436
+ "value": [
437
+ "default",
438
+ "error",
439
+ "warning",
440
+ "success"
441
+ ]
442
+ },
443
+ "required": false,
444
+ "description": "forwarded to Input"
445
+ },
446
+ "isLoading": {
447
+ "type": {
448
+ "name": "boolean"
449
+ },
450
+ "required": false
451
+ },
452
+ "loadingText": {
453
+ "type": {
454
+ "name": "string"
455
+ },
456
+ "required": false,
457
+ "defaultValue": "Loading…"
458
+ },
459
+ "renderOption": {
460
+ "type": {
461
+ "name": "function",
462
+ "raw": "(option, query) => ReactNode"
463
+ },
464
+ "required": false
465
+ },
417
466
  "className": {
418
467
  "type": {
419
468
  "name": "string"
@@ -429,25 +478,34 @@
429
478
  },
430
479
  "composition": {
431
480
  "notes": [
432
- "**Autocomplete vs Combobox:** Autocomplete allows free-text input (users can type anything); Combobox enforces selection from the list. Pick by whether \"off-list\" values are valid (e.g. city field that accepts typos → Autocomplete; tag picker from a fixed vocabulary → Combobox).",
433
- "**Value shape is an object** (`{ value, label }`), not a plain string this preserves label/value decoupling for display-vs-storage.",
434
- "**FormField:** Does NOT auto-consume FormField state. Set explicit error styling via className if needed.",
481
+ "**Composes `Input`** the field is the DS `Input`, so it inherits `size`, error/`state` painting, read-only, hover, and FormField wiring. Autocomplete owns only the dropdown + behavior.",
482
+ "**Autocomplete vs Combobox:** Autocomplete allows free-text input (users can type anything); Combobox enforces selection from the list.",
483
+ "**Value shape is an object** (`{ value, label }`), not a plain string. Controlled via `value`, or uncontrolled via `defaultValue`.",
484
+ "**FormField:** auto-consumes FormField state (via the composed Input) — inside a FormField, error border + `aria-invalid`/`aria-describedby`/`required` are wired automatically. Pass `state` to override.",
485
+ "**Async / \"type to search\":** set `isLoading` to show a spinner (in the field + the listbox) with `loadingText`.",
486
+ "**Matched-text highlight:** the query substring is bolded in each option by default; override the whole row with `renderOption`.",
435
487
  "**Portal rendering:** Dropdown portals to body with z-popover (1400) — stacks above Dialog/Sheet.",
436
- "**Keyboard:** ArrowDown/Up navigate suggestions, Enter selects, Esc closes. Typeahead is the input's native filtering."
488
+ "**Keyboard:** ArrowDown/Up/Home/End navigate, Enter selects, Esc closes."
437
489
  ]
438
490
  },
439
491
  "docPath": "docs/components/ui/autocomplete.md",
440
492
  "defaults": {
441
- "emptyText": "No options"
493
+ "emptyText": "No options",
494
+ "loadingText": "Loading…"
442
495
  },
443
496
  "examples": [
444
497
  "<Autocomplete\n options={[{ value: 'mumbai', label: 'Mumbai' }]}\n value={selectedCity}\n onValueChange={setSelectedCity}\n placeholder=\"Search cities...\"\n/>"
445
498
  ],
446
499
  "gotchas": [
447
500
  "Allows free-text input (no forced selection) — use Combobox for forced selection",
448
- "value is an object { value, label }, NOT just a string"
501
+ "value is an object { value, label }, NOT just a string",
502
+ "Client-side filtering only (known list). For huge/remote lists, drive `options` yourself with `isLoading` — no built-in virtualization."
449
503
  ],
450
504
  "changes": [
505
+ {
506
+ "version": "0.53.0",
507
+ "summary": "**Changed** Re-parented onto the DS `Input` primitive — inherits `size`, error/`state` painting, read-only, hover, and FormField auto-consumption (previously re-rolled its own `<input>` and read FormField error but never painted it)."
508
+ },
451
509
  {
452
510
  "version": "0.18.0",
453
511
  "summary": "**Fixed** Added `useEffect` to sync query when external value changes"
@@ -1192,6 +1250,7 @@
1192
1250
  "error",
1193
1251
  "success",
1194
1252
  "warning",
1253
+ "info",
1195
1254
  "neutral"
1196
1255
  ]
1197
1256
  },
@@ -1302,6 +1361,7 @@
1302
1361
  "error",
1303
1362
  "success",
1304
1363
  "warning",
1364
+ "info",
1305
1365
  "neutral"
1306
1366
  ]
1307
1367
  },
@@ -5250,7 +5310,7 @@
5250
5310
  "type": {
5251
5311
  "name": "enum",
5252
5312
  "value": [
5253
- "default",
5313
+ "soft",
5254
5314
  "solid"
5255
5315
  ]
5256
5316
  },
@@ -5263,16 +5323,24 @@
5263
5323
  },
5264
5324
  "required": true
5265
5325
  },
5266
- "selectedId": {
5326
+ "value": {
5267
5327
  "type": {
5268
- "name": "string"
5328
+ "name": "object",
5329
+ "raw": "string // controlled"
5269
5330
  },
5270
- "required": true
5331
+ "required": false
5271
5332
  },
5272
- "onSelect": {
5333
+ "defaultValue": {
5334
+ "type": {
5335
+ "name": "object",
5336
+ "raw": "string // uncontrolled initial"
5337
+ },
5338
+ "required": false
5339
+ },
5340
+ "onValueChange": {
5273
5341
  "type": {
5274
5342
  "name": "function",
5275
- "raw": "(id: string) => void (REQUIRED)"
5343
+ "raw": "(id: string) => void"
5276
5344
  },
5277
5345
  "required": false
5278
5346
  },
@@ -5281,6 +5349,26 @@
5281
5349
  "name": "boolean"
5282
5350
  },
5283
5351
  "required": false
5352
+ },
5353
+ "fullWidth": {
5354
+ "type": {
5355
+ "name": "boolean"
5356
+ },
5357
+ "required": false
5358
+ },
5359
+ "selectedId": {
5360
+ "type": {
5361
+ "name": "object",
5362
+ "raw": "string // @deprecated — use value"
5363
+ },
5364
+ "required": false
5365
+ },
5366
+ "onSelect": {
5367
+ "type": {
5368
+ "name": "function",
5369
+ "raw": "(id: string) => void // @deprecated — use onValueChange"
5370
+ },
5371
+ "required": false
5284
5372
  }
5285
5373
  },
5286
5374
  "composition": {
@@ -5288,20 +5376,29 @@
5288
5376
  "**Data-driven, not compound** — unlike Tabs/ToggleGroup, SegmentedControl takes an `options` array rather than children. This makes it easier to render from a list but harder to customize per-option styling; use Tabs if you need compound children.",
5289
5377
  "**When to use vs Tabs:** SegmentedControl is for mutually-exclusive VIEW-MODE toggles (List/Grid/Kanban) — short labels, no associated content panel. Tabs is for content switching where each tab has a corresponding TabsContent. SegmentedControl renders `role=\"radiogroup\"` with `role=\"radio\"` segments (a panel-less single-select); Tabs renders `role=\"tablist\"`.",
5290
5378
  "**Option icons** auto-size based on the `size` prop — don't set explicit icon sizes.",
5291
- "Fully controlled there's no `defaultSelectedId`. Manage state in parent.",
5379
+ "**`fullWidth`** switches segments from content-hug (default) to equal-fill: each segment takes an equal share of the container (a 2-item toggle splits 50/50, a 3-item switcher gives each a third). Use for view switchers and toolbar toggles that should fill their column; leave off for compact inline toolbars.",
5380
+ "**Visual model:** a rounded-rect track (not a full pill) — a translucent recessed groove with a single soft-shadowed sliding thumb. The track has no border/inset shadow; the thumb carries the only edge. Elevation inverts in dark so the groove stays visible.",
5381
+ "**Controlled or uncontrolled** — pass `value` + `onValueChange` to control it, or `defaultValue` (optional; falls back to the first option) to let it own state. Matches the Tabs/ToggleGroup vocabulary. `selectedId`/`onSelect` are deprecated aliases that still work.",
5382
+ "**Option labels accept `ReactNode`** — a segment can hold a count badge or custom node, not just a string. `text` is optional: omit it for an **icon-only** segment and set `ariaLabel` so the segment still has an accessible name.",
5383
+ "**Touch targets** — each segment has a 44px minimum hit area (via `touch-target`) even though the visual height stays dense.",
5384
+ "**RTL** — Arrow-key navigation tracks reading order: in a right-to-left context `ArrowLeft` moves to the next option and `ArrowRight` to the previous (detected from the nearest `dir` attribute).",
5292
5385
  "Built from scratch (no Radix primitive) — standard HTML buttons with `role=\"radio\"` + `aria-checked` and roving tabindex."
5293
5386
  ]
5294
5387
  },
5295
5388
  "docPath": "docs/components/ui/segmented-control.md",
5296
5389
  "examples": [
5297
- "<SegmentedControl\n size=\"md\"\n variant=\"default\"\n options={[\n { id: 'list', text: 'List' },\n { id: 'grid', text: 'Grid' },\n ]}\n selectedId={viewMode}\n onSelect={setViewMode}\n/>"
5390
+ "<SegmentedControl\n size=\"md\"\n variant=\"soft\"\n options={[\n { id: 'list', text: 'List' },\n { id: 'grid', text: 'Grid' },\n ]}\n value={viewMode}\n onValueChange={setViewMode}\n/>"
5298
5391
  ],
5299
5392
  "gotchas": [
5300
- "Controlled only — selectedId + onSelect are required",
5393
+ "Controlled (`value`) or uncontrolled (`defaultValue`) `selectedId`/`onSelect` are deprecated aliases",
5301
5394
  "Uses data-driven API (options prop), not compound children",
5302
5395
  "Use Tabs (not SegmentedControl) when you need associated content panels per option"
5303
5396
  ],
5304
5397
  "changes": [
5398
+ {
5399
+ "version": "0.52.0",
5400
+ "summary": "**Changed** Visual rebuild — rounded-rect track (was full pill), translucent recessed track with no border/inset, single ring-less soft-shadow thumb. Dark-mode elevation inverts so the groove stays visible. New tokens: `--color-segment-track`, `--color-segment-thumb`, `--shadow-segment`."
5401
+ },
5305
5402
  {
5306
5403
  "version": "0.38.0",
5307
5404
  "summary": "**Removed** (BREAKING) deprecated `variant=\"accent\"` alias. Use `variant=\"solid\"`."
@@ -10929,7 +11026,7 @@
10929
11026
  "displayName": "PriorityIndicator",
10930
11027
  "tier": "composed",
10931
11028
  "import": "@devalok/shilp-sutra/composed/priority-indicator",
10932
- "serverSafe": true,
11029
+ "serverSafe": false,
10933
11030
  "description": "",
10934
11031
  "props": {
10935
11032
  "priority": {
@@ -10939,6 +11036,13 @@
10939
11036
  },
10940
11037
  "required": false
10941
11038
  },
11039
+ "iconOnly": {
11040
+ "type": {
11041
+ "name": "boolean"
11042
+ },
11043
+ "required": false,
11044
+ "description": "icon-only chip, no visible text"
11045
+ },
10942
11046
  "display": {
10943
11047
  "type": {
10944
11048
  "name": "enum",
@@ -10948,31 +11052,41 @@
10948
11052
  ]
10949
11053
  },
10950
11054
  "required": false,
10951
- "defaultValue": "full"
11055
+ "description": "@deprecated — use iconOnly"
11056
+ },
11057
+ "children": {
11058
+ "type": {
11059
+ "name": "ReactNode"
11060
+ },
11061
+ "required": false,
11062
+ "description": "override the label, e.g. i18n"
10952
11063
  }
10953
11064
  },
10954
11065
  "composition": {
10955
11066
  "notes": [
10956
- "**Server-safe priority label** icon + color + text for task / issue priority.",
11067
+ "**Composes `Badge`**radius, color semantics, a11y labelling, and reduced-motion handling all come from one place (no bespoke re-roll).",
10957
11068
  "**Composes inside list rows, DataTable cells, Card headers, task panels** — anywhere a priority flag fits.",
10958
- "**display=\"compact\"** shows only the icon (with priority text as title attribute for tooltip). Use in tight cells; use `display=\"full\"` (default) in free space.",
10959
- "**Case-insensitive priority** — accepts both UPPERCASE (LOW/MEDIUM/HIGH/URGENT) and lowercase. Designed to match both backend conventions without manual coercion.",
10960
- "Color semantics: LOW=success, MEDIUM=warning, HIGH=error, URGENT=error with bolder icon."
11069
+ "**`iconOnly`** shows only the icon with a real accessible name (`role=\"img\"` + `aria-label`), for tight cells. Omit it (default) for icon + label.",
11070
+ "**Severity by weight, not motion** — URGENT renders as a solid fill so the top tier reads at a glance; the others are soft. No animation (removes the prior perpetual pulse).",
11071
+ "**Case-insensitive priority** accepts both UPPERCASE and lowercase; unknown values fall back to MEDIUM instead of throwing.",
11072
+ "**`children`** overrides the label for i18n / custom copy.",
11073
+ "Color semantics: LOW = slate (neutral), MEDIUM = warning, HIGH = error (soft), URGENT = error (solid)."
10961
11074
  ]
10962
11075
  },
10963
11076
  "docPath": "docs/components/composed/priority-indicator.md",
10964
- "defaults": {
10965
- "display": "full"
10966
- },
10967
11077
  "examples": [
10968
- "<PriorityIndicator priority=\"HIGH\" />\n<PriorityIndicator priority=\"low\" display=\"compact\" />"
11078
+ "<PriorityIndicator priority=\"HIGH\" />\n<PriorityIndicator priority=\"low\" iconOnly />\n<PriorityIndicator priority=\"URGENT\">Critical</PriorityIndicator>"
10969
11079
  ],
10970
11080
  "gotchas": [
10971
- "Case-insensitive — \"low\" and \"LOW\" both work",
10972
- "Server-safe: can be imported directly in Next.js Server Components",
10973
- "`compact` display shows only the icon; `full` shows icon + text label"
11081
+ "Case-insensitive — \"low\" and \"LOW\" both work; unknown values fall back to MEDIUM",
11082
+ "`iconOnly` shows only the icon (accessible-named); default shows icon + text label",
11083
+ "`display` is deprecated use `iconOnly`"
10974
11084
  ],
10975
11085
  "changes": [
11086
+ {
11087
+ "version": "0.53.0",
11088
+ "summary": "**Changed** Recomposed on the `Badge` primitive (was a bespoke re-rolled chip): inherits pill radius, color semantics, accessible labelling."
11089
+ },
10976
11090
  {
10977
11091
  "version": "0.2.0",
10978
11092
  "summary": "**Added** Identified as server-safe component"
@@ -11557,13 +11671,55 @@
11557
11671
  "required": false,
11558
11672
  "description": "minutes, default: 30",
11559
11673
  "defaultValue": 30
11674
+ },
11675
+ "selectedEventId": {
11676
+ "type": {
11677
+ "name": "string"
11678
+ },
11679
+ "required": false,
11680
+ "description": "rings the active event"
11681
+ },
11682
+ "renderEvent": {
11683
+ "type": {
11684
+ "name": "function",
11685
+ "raw": "(event) => ReactNode (custom event body)"
11686
+ },
11687
+ "required": false
11688
+ },
11689
+ "header": {
11690
+ "type": {
11691
+ "name": "ReactNode"
11692
+ },
11693
+ "required": false,
11694
+ "description": "toolbar slot above the grid"
11695
+ },
11696
+ "emptyState": {
11697
+ "type": {
11698
+ "name": "ReactNode"
11699
+ },
11700
+ "required": false,
11701
+ "description": "shown when events is empty"
11702
+ },
11703
+ "height": {
11704
+ "type": {
11705
+ "name": "union",
11706
+ "raw": "number | string"
11707
+ },
11708
+ "required": false,
11709
+ "description": "grid body height, default 480",
11710
+ "defaultValue": 480
11560
11711
  }
11561
11712
  },
11562
11713
  "composition": {
11563
11714
  "notes": [
11564
11715
  "**Day / Week calendar view** for time-block display (meetings, shifts, availability). Not a full calendar app — no month view, no drag-to-create.",
11565
11716
  "**Event data is consumer-owned:** You pass `events` as an array; ScheduleView doesn't fetch, doesn't cache, doesn't expand recurring events. All scheduling logic lives in your app.",
11566
- "**Event click + slot click** — `onEventClick` for existing events; `onSlotClick` for creating new events (fires with start/end of the empty slot).",
11717
+ "**Event click + slot click** — `onEventClick` for existing events; `onSlotClick` for creating new events (fires with start/end of the empty slot). **Slots are only interactive (focusable + keyboard-navigable) when `onSlotClick` is set** — otherwise they render as inert grid lines, so a read-only schedule adds no keyboard/AT tab stops.",
11718
+ "**Keyboard (interactive slots):** roving tabindex — Arrow keys move between slots (up/down within a day, left/right across days, RTL-aware), Home/End jump within the day; only one slot is in the tab order at a time.",
11719
+ "**Overlapping events** are partitioned into side-by-side columns automatically so double-booked times stay legible.",
11720
+ "**Live now-line** ticks every minute and scrolls into view on mount.",
11721
+ "**`renderEvent`** customizes the event block body; **`header`** adds a toolbar; **`selectedEventId`** rings the active event; **`emptyState`** shows when there are no events.",
11722
+ "**Composes the DS card shell tokens** (`surface-2` + `rounded-surface` + border) and is RTL-safe (logical properties throughout).",
11567
11723
  "**Color vocabulary matches the DS** — `accent/success/warning/error/info/neutral`. Map your event types to these at the data layer.",
11568
11724
  "**endHour is exclusive:** `endHour=18` means the last visible slot starts at 17:30 (with 30min slots). Match your UX expectation: 9-5 typically means `startHour=9, endHour=18`.",
11569
11725
  "**Pairs with date-picker/composed** — use DatePicker or DateRangePicker to choose which date to show; pass that as ScheduleView's `date`."
@@ -11573,7 +11729,8 @@
11573
11729
  "defaults": {
11574
11730
  "startHour": 8,
11575
11731
  "endHour": 18,
11576
- "slotDuration": 30
11732
+ "slotDuration": 30,
11733
+ "height": 480
11577
11734
  },
11578
11735
  "examples": [
11579
11736
  "<ScheduleView\n view=\"week\"\n date={new Date()}\n events={calendarEvents}\n onEventClick={(e) => openEvent(e.id)}\n/>"
@@ -11584,6 +11741,10 @@
11584
11741
  "Events that span outside `startHour`/`endHour` may be clipped"
11585
11742
  ],
11586
11743
  "changes": [
11744
+ {
11745
+ "version": "0.53.0",
11746
+ "summary": "**Changed** Read-only schedules no longer flood the tab order — slots are interactive only when `onSlotClick` is set; otherwise inert grid lines. Interactive slots use roving tabindex + Arrow/Home/End keyboard navigation (RTL-aware)."
11747
+ },
11587
11748
  {
11588
11749
  "version": "0.49.0",
11589
11750
  "summary": "**BREAKING** `ScheduleEvent.color` value `\"primary\"` renamed `\"accent\"` (DS colour vocabulary). It was the default, so untyped events are unaffected."
@@ -11928,7 +12089,7 @@
11928
12089
  "raw": "BottomNavbarUser | null"
11929
12090
  },
11930
12091
  "required": false,
11931
- "description": "optional"
12092
+ "description": "drives per-item role gating, optional"
11932
12093
  },
11933
12094
  "primaryItems": {
11934
12095
  "type": {
@@ -11944,7 +12105,30 @@
11944
12105
  "raw": "BottomNavItem[]"
11945
12106
  },
11946
12107
  "required": false,
11947
- "description": "overflow items in \"More\" menu, optional"
12108
+ "description": "overflow items in \"More\" sheet, optional"
12109
+ },
12110
+ "indicator": {
12111
+ "type": {
12112
+ "name": "enum",
12113
+ "value": [
12114
+ "pill",
12115
+ "underline",
12116
+ "tint",
12117
+ "none"
12118
+ ]
12119
+ },
12120
+ "required": false,
12121
+ "description": "active-item indicator; default 'pill'"
12122
+ },
12123
+ "labelVisibility": {
12124
+ "type": {
12125
+ "name": "enum",
12126
+ "value": [
12127
+ "always",
12128
+ "selected"
12129
+ ]
12130
+ },
12131
+ "required": false
11948
12132
  },
11949
12133
  "className": {
11950
12134
  "type": {
@@ -11959,7 +12143,11 @@
11959
12143
  "**Responsive switch pattern:** Use `useIsMobile()` hook to conditionally render AppSidebar (desktop) or BottomNavbar (mobile). Example: ```jsx const isMobile = useIsMobile() return isMobile ? <BottomNavbar ... /> : <AppSidebar ... /> ```",
11960
12144
  "**Primary vs overflow:** `primaryItems` (max 4) for the always-visible slots; `moreItems` go into a \"More\" sheet that opens on tap. Don't exceed 4 primary — the bar becomes cramped.",
11961
12145
  "**Router integration via LinkProvider:** Each nav item is rendered using the framework-specific Link component registered in LinkProvider. Without LinkProvider, you get full-page reloads on tap.",
11962
- "**Badge numbers** cap at 99+ (same as BadgeIndicator pattern).",
12146
+ "**Badge numbers** cap at 99+ (composes the `Badge` primitive).",
12147
+ "**Role gating:** each item may declare `roles: string[]` (shown only when `user.role` matches) or a `canView(user)` predicate for arbitrary logic (`canView` wins). Items with neither are always visible. Gating applies to both `primaryItems` and `moreItems`.",
12148
+ "**Overflow sheet:** the \"More\" surface is the DS `Sheet` (`side=\"bottom\"`) — it inherits focus trap, scroll lock, return-focus, and `aria-modal`; the trigger is wired with `aria-haspopup`/`aria-controls` automatically.",
12149
+ "**Indicator (animated):** the active indicator slides to the selected item (shared-element `layoutId`) and fades in on first appearance. Modes: `pill` (default, Material-3 tonal pill behind the icon), `underline` (top accent bar), `tint` (subtle bg on the whole active cell), `none` (no shape — pair with `activeIcon` for the iOS filled-icon look). `labelVisibility=\"selected\"` shows labels only for the active item.",
12150
+ "**Filled-when-active:** set `activeIcon` on an item (e.g. a Tabler `*Filled` variant) to swap the icon for a filled version while it's the active route; falls back to `icon`.",
11963
12151
  "**Not for desktop:** The viewport-fixed positioning + touch-optimized sizing don't translate well to desktop. Hide behind `md:hidden`."
11964
12152
  ]
11965
12153
  },
@@ -11974,6 +12162,10 @@
11974
12162
  "Requires LinkProvider for framework-specific link components (e.g., Next.js Link)"
11975
12163
  ],
11976
12164
  "changes": [
12165
+ {
12166
+ "version": "0.53.0",
12167
+ "summary": "**Changed** Overflow \"More\" menu re-founded on the DS `Sheet` primitive — inherits focus trap, scroll lock, return-focus, `aria-modal`, and trigger↔panel ARIA wiring (was a hand-rolled `role=\"dialog\"` with none of these). Composes `Badge` for notification counts and the Sheet's built-in close (≥ touch target)."
12168
+ },
11977
12169
  {
11978
12170
  "version": "0.19.0",
11979
12171
  "summary": "**Changed** Background elevated from `bg-surface-1` to `bg-surface-2` for visual hierarchy above app background"
@@ -13279,6 +13471,14 @@
13279
13471
  "name": "--color-backdrop",
13280
13472
  "value": "Canvas"
13281
13473
  },
13474
+ {
13475
+ "name": "--color-segment-track",
13476
+ "value": "color-mix(in oklch, oklch(1 0 0) 7%, transparent)"
13477
+ },
13478
+ {
13479
+ "name": "--color-segment-thumb",
13480
+ "value": "var(--neutral-3)"
13481
+ },
13282
13482
  {
13283
13483
  "name": "--color-overlay",
13284
13484
  "value": "Canvas"
@@ -13933,6 +14133,10 @@
13933
14133
  "name": "--shadow-raised-hover",
13934
14134
  "value": "var(--shadow-sm-internal)"
13935
14135
  },
14136
+ {
14137
+ "name": "--shadow-segment",
14138
+ "value": "0 1px 2px -0.5px oklch(var(--shadow-color) / calc(0.10 * var(--shadow-strength))), 0 3px 8px -2px oklch(var(--shadow-color) / calc(0.09 * var(--shadow-strength)))"
14139
+ },
13936
14140
  {
13937
14141
  "name": "--shadow-floating",
13938
14142
  "value": "var(--shadow-md-internal)"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@devalok/shilp-sutra",
3
- "version": "0.51.0",
3
+ "version": "0.53.0",
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>",
@@ -183,7 +183,7 @@ function buildFullBanner(version, prevVersion) {
183
183
  lines.push(row(` ${colour('https://shilp-sutra.devalok.in/themer', DIM)}`))
184
184
  lines.push(colour(EMPTY, PINK_DIM))
185
185
  lines.push(row(` ${colour('▸', PINK)} Wire your AI agent (Claude Code / Cursor / Codex):`))
186
- lines.push(row(` ${colour('connect the live docs MCP https://shilp-sutra.devalok.in/mcp', DIM)}`))
186
+ lines.push(row(` ${colour('live docs MCP added to .mcp.json — approve it to enable', DIM)}`))
187
187
  lines.push(row(` ${colour('(version-exact setup + peer preflight; beats guessing)', DIM)}`))
188
188
  lines.push(row(` ${colour('or copy the skill: cp -r node_modules/@devalok/shilp-sutra/skill \\', DIM)}`))
189
189
  lines.push(row(` ${colour('~/.claude/skills/shilp-sutra', DIM)}`))
@@ -215,6 +215,59 @@ function buildCompactBanner(version, prevVersion) {
215
215
  ].join('\n')
216
216
  }
217
217
 
218
+ // ── MCP auto-discovery ───────────────────────────────────────────────────────
219
+ // Write a project-scoped `.mcp.json` pointing at the hosted docs MCP so an AI
220
+ // coding agent DISCOVERS it right after install. This runs even when stdout is
221
+ // piped (unlike the banner) — a config file is not console noise, and the agent
222
+ // that just ran `install` is exactly who should find it. It is never silent-
223
+ // forced: Claude Code (and peers) still PROMPT the user to approve a project
224
+ // MCP server before enabling it. Safety: additive merge (never clobbers other
225
+ // servers or an existing shilp-sutra entry), skips CI and dev installs, honours
226
+ // opt-out, and a write-once sentinel so a user who deletes it is not re-nagged.
227
+ const SEP = process.platform === 'win32' ? '\\' : '/'
228
+ const MCP_URL = 'https://shilp-sutra.devalok.in/mcp'
229
+
230
+ function tryWriteMcpConfig() {
231
+ try {
232
+ if (process.env.SHILP_SUTRA_NO_WELCOME === '1' || process.env.SHILP_SUTRA_NO_WELCOME === 'true') return
233
+ if (process.env.SHILP_SUTRA_NO_MCP === '1' || process.env.SHILP_SUTRA_NO_MCP === 'true') return
234
+ if (process.env.CI) return // writing agent config into a CI checkout is pointless/unwanted
235
+
236
+ const initCwd = process.env.INIT_CWD
237
+ const cwd = process.cwd()
238
+ const isInsideNodeModules = cwd.includes(`${SEP}node_modules${SEP}`) || cwd.includes('/node_modules/')
239
+ if (!isInsideNodeModules) return // dev install inside the DS repo itself
240
+ if (!initCwd || initCwd === cwd) return // no consumer root → unusual context
241
+
242
+ // Write-once-ever sentinel (survives re-installs; respects user deletion of .mcp.json)
243
+ const parts = PKG_DIR.split(/[/\\]/)
244
+ const nmIdx = parts.lastIndexOf('node_modules')
245
+ const sentinel = nmIdx === -1 ? null : join(parts.slice(0, nmIdx + 1).join(SEP), '.shilp-sutra-mcp-written')
246
+ if (sentinel && existsSync(sentinel)) return
247
+
248
+ const target = join(initCwd, '.mcp.json')
249
+ let config = { mcpServers: {} }
250
+ if (existsSync(target)) {
251
+ try {
252
+ config = JSON.parse(readFileSync(target, 'utf-8'))
253
+ } catch {
254
+ return // existing but unparseable — never clobber a hand-authored config
255
+ }
256
+ if (!config || typeof config !== 'object') return
257
+ if (!config.mcpServers || typeof config.mcpServers !== 'object') config.mcpServers = {}
258
+ if (config.mcpServers['shilp-sutra']) {
259
+ if (sentinel) writeFileSync(sentinel, MCP_URL + '\n')
260
+ return // already declared — leave the consumer's version untouched
261
+ }
262
+ }
263
+ config.mcpServers['shilp-sutra'] = { type: 'http', url: MCP_URL }
264
+ writeFileSync(target, JSON.stringify(config, null, 2) + '\n')
265
+ if (sentinel) writeFileSync(sentinel, MCP_URL + '\n')
266
+ } catch {
267
+ // Never break the consumer install — a failed config write is a no-op.
268
+ }
269
+ }
270
+
218
271
  // ── Main ────────────────────────────────────────────────────────────────────
219
272
  function main() {
220
273
  // --preview / --compact bypass all guards. Used by maintainers + by the
@@ -223,6 +276,11 @@ function main() {
223
276
  const preview = process.argv.includes('--preview')
224
277
  const forceCompact = process.argv.includes('--compact')
225
278
 
279
+ // MCP auto-discovery runs regardless of TTY — an agent-run (piped) install is
280
+ // precisely when the agent should discover the docs MCP. Must come before the
281
+ // TTY skip below, which only governs the human-facing banner.
282
+ if (!preview) tryWriteMcpConfig()
283
+
226
284
  if (!preview) {
227
285
  const skipReason = shouldSkip()
228
286
  if (skipReason) return // silent
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.51.0"
6
+ version: "0.53.0"
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
@@ -2,7 +2,7 @@
2
2
 
3
3
  # @devalok/shilp-sutra
4
4
 
5
- > Radix UI + Tailwind 4 (CSS-first) + CVA design system for Devalok apps, v0.51.0.
5
+ > Radix UI + Tailwind 4 (CSS-first) + CVA design system for Devalok apps, v0.53.0.
6
6
  > Built on the same primitives as shadcn/ui but with DIFFERENT prop APIs — never guess from shadcn knowledge; verify every prop.
7
7
  > This file is a ROUTER: it tells you what exists and where to get details. Do not look for prop tables here — fetch them per component (MCP tool or per-component doc file below).
8
8
 
@@ -153,8 +153,8 @@ Both `<Suspense>` and `<LoadingSkeleton>` are server-safe.
153
153
  import { useColorMode } from "@devalok/shilp-sutra/hooks/use-color-mode";
154
154
 
155
155
  export default function ServerPage() {
156
- const { mode } = useColorMode(); // breaks
157
- return <div>{mode}</div>;
156
+ const { colorMode } = useColorMode(); // breaks
157
+ return <div>{colorMode}</div>;
158
158
  }
159
159
  ```
160
160
 
@@ -129,10 +129,10 @@ For runtime toggling inside React components, use `useColorMode`:
129
129
  import { useColorMode } from "@devalok/shilp-sutra/hooks/use-color-mode";
130
130
 
131
131
  export function ThemeToggle() {
132
- const { mode, toggle } = useColorMode();
132
+ const { colorMode, toggleColorMode } = useColorMode();
133
133
  return (
134
- <button onClick={toggle} aria-label="Toggle theme">
135
- {mode === "dark" ? "☀" : "☾"}
134
+ <button onClick={toggleColorMode} aria-label="Toggle theme">
135
+ {colorMode === "dark" ? "☀" : "☾"}
136
136
  </button>
137
137
  );
138
138
  }
@@ -123,10 +123,10 @@ Wire the runtime hook from anywhere in the app (e.g., a header button):
123
123
  import { useColorMode } from "@devalok/shilp-sutra/hooks/use-color-mode";
124
124
 
125
125
  export function ThemeToggle() {
126
- const { mode, toggle } = useColorMode();
126
+ const { colorMode, toggleColorMode } = useColorMode();
127
127
  return (
128
- <button onClick={toggle} aria-label="Toggle theme">
129
- {mode === "dark" ? "☀" : "☾"}
128
+ <button onClick={toggleColorMode} aria-label="Toggle theme">
129
+ {colorMode === "dark" ? "☀" : "☾"}
130
130
  </button>
131
131
  );
132
132
  }