@agents-inc/cli 0.32.1 → 0.35.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 (225) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/README.md +7 -7
  3. package/config/skills-matrix.yaml +10 -10
  4. package/config/stacks.yaml +9 -9
  5. package/dist/{chunk-IZZ4IIEG.js → chunk-5LPPIT6H.js} +4 -4
  6. package/dist/{chunk-Z4TWOP3H.js → chunk-5YNZJ5TP.js} +2 -2
  7. package/dist/chunk-AQQVSNUX.js +33 -0
  8. package/dist/chunk-AQQVSNUX.js.map +1 -0
  9. package/dist/{chunk-ODUOU55D.js → chunk-BLLXNFWP.js} +2 -2
  10. package/dist/{chunk-YND42IXK.js → chunk-BPD4VUAU.js} +12 -10
  11. package/dist/chunk-BPD4VUAU.js.map +1 -0
  12. package/dist/{chunk-4RAY5AOI.js → chunk-CEWNZQMH.js} +3 -3
  13. package/dist/{chunk-WEUVWHMA.js → chunk-CXWPUVA7.js} +16 -11
  14. package/dist/chunk-CXWPUVA7.js.map +1 -0
  15. package/dist/{chunk-R74PZWQS.js → chunk-GGHH3KR2.js} +5 -5
  16. package/dist/chunk-GGHH3KR2.js.map +1 -0
  17. package/dist/chunk-HTTPKSL6.js +31 -0
  18. package/dist/chunk-HTTPKSL6.js.map +1 -0
  19. package/dist/{chunk-A27LOC4Z.js → chunk-IG7CUREJ.js} +3 -3
  20. package/dist/{chunk-FJQRVFMB.js → chunk-JXMRTHDT.js} +2 -2
  21. package/dist/{chunk-JMVWYAHT.js → chunk-KUV24B5M.js} +4 -4
  22. package/dist/chunk-KUV24B5M.js.map +1 -0
  23. package/dist/{chunk-B2UBHA66.js → chunk-KWF6D7ZP.js} +38 -36
  24. package/dist/chunk-KWF6D7ZP.js.map +1 -0
  25. package/dist/{chunk-IYG2LAIM.js → chunk-LFHZBF6N.js} +5 -17
  26. package/dist/chunk-LFHZBF6N.js.map +1 -0
  27. package/dist/{chunk-SO22IQPY.js → chunk-MZB3GGOH.js} +2 -2
  28. package/dist/chunk-MZB3GGOH.js.map +1 -0
  29. package/dist/{chunk-W2ZSCZ2U.js → chunk-NJ775OJ4.js} +4 -4
  30. package/dist/chunk-NVQEHRJY.js +120 -0
  31. package/dist/chunk-NVQEHRJY.js.map +1 -0
  32. package/dist/{chunk-LGUI3PMO.js → chunk-OGJ7DFCL.js} +9 -9
  33. package/dist/chunk-OGJ7DFCL.js.map +1 -0
  34. package/dist/{chunk-3ZOIOVKT.js → chunk-OGXSTJP2.js} +4 -4
  35. package/dist/chunk-OGXSTJP2.js.map +1 -0
  36. package/dist/{chunk-BZN2Z5P7.js → chunk-OI4WBRC7.js} +12 -3
  37. package/dist/chunk-OI4WBRC7.js.map +1 -0
  38. package/dist/{chunk-OGJIZ6QH.js → chunk-OKILA27U.js} +122 -232
  39. package/dist/chunk-OKILA27U.js.map +1 -0
  40. package/dist/{chunk-PBEHPQLK.js → chunk-PKUIO2Z7.js} +57 -21
  41. package/dist/chunk-PKUIO2Z7.js.map +1 -0
  42. package/dist/{chunk-5PIKNCZX.js → chunk-U36YCEBK.js} +79 -39
  43. package/dist/chunk-U36YCEBK.js.map +1 -0
  44. package/dist/{chunk-OMV7TLWD.js → chunk-UFUQUFV6.js} +23 -107
  45. package/dist/chunk-UFUQUFV6.js.map +1 -0
  46. package/dist/{chunk-R3XFQKPG.js → chunk-VEZ2GZEK.js} +2 -2
  47. package/dist/{chunk-MM7NK5N2.js → chunk-WMVGRAFB.js} +2896 -2945
  48. package/dist/chunk-WMVGRAFB.js.map +1 -0
  49. package/dist/{chunk-UX2H2K2G.js → chunk-XNQJBQ5X.js} +2 -2
  50. package/dist/{chunk-EMJ2ZKS7.js → chunk-XYCN2GCV.js} +3 -3
  51. package/dist/{chunk-LAPCUV4D.js → chunk-YCS7GF6Y.js} +2 -4
  52. package/dist/chunk-YCS7GF6Y.js.map +1 -0
  53. package/dist/{chunk-UICL22RT.js → chunk-YIKBNGE3.js} +4 -4
  54. package/dist/{chunk-BZQBJP34.js → chunk-YN35L5NE.js} +2 -2
  55. package/dist/chunk-YN35L5NE.js.map +1 -0
  56. package/dist/{chunk-QPTOIZAT.js → chunk-YPJKOM42.js} +2 -2
  57. package/dist/{chunk-FZGYSLJL.js → chunk-ZE355C6C.js} +2 -2
  58. package/dist/commands/build/marketplace.js +3 -3
  59. package/dist/commands/build/plugins.js +5 -5
  60. package/dist/commands/build/stack.js +5 -5
  61. package/dist/commands/compile.js +20 -9
  62. package/dist/commands/compile.js.map +1 -1
  63. package/dist/commands/config/get.js +4 -4
  64. package/dist/commands/config/index.js +5 -5
  65. package/dist/commands/config/path.js +4 -4
  66. package/dist/commands/config/set-project.js +4 -4
  67. package/dist/commands/config/show.js +5 -5
  68. package/dist/commands/config/unset-project.js +4 -4
  69. package/dist/commands/diff.js +5 -5
  70. package/dist/commands/diff.js.map +1 -1
  71. package/dist/commands/doctor.js +7 -7
  72. package/dist/commands/doctor.js.map +1 -1
  73. package/dist/commands/edit.js +35 -32
  74. package/dist/commands/edit.js.map +1 -1
  75. package/dist/commands/eject.js +5 -5
  76. package/dist/commands/eject.js.map +1 -1
  77. package/dist/commands/import/skill.js +5 -5
  78. package/dist/commands/info.js +6 -6
  79. package/dist/commands/info.js.map +1 -1
  80. package/dist/commands/init.js +37 -41
  81. package/dist/commands/init.js.map +1 -1
  82. package/dist/commands/list.js +5 -5
  83. package/dist/commands/list.js.map +1 -1
  84. package/dist/commands/new/agent.js +5 -5
  85. package/dist/commands/new/skill.js +5 -5
  86. package/dist/commands/new/skill.js.map +1 -1
  87. package/dist/commands/outdated.js +5 -5
  88. package/dist/commands/outdated.js.map +1 -1
  89. package/dist/commands/search.js +7 -7
  90. package/dist/commands/uninstall.js +105 -28
  91. package/dist/commands/uninstall.js.map +1 -1
  92. package/dist/commands/update.js +7 -7
  93. package/dist/commands/update.js.map +1 -1
  94. package/dist/commands/validate.js +5 -5
  95. package/dist/commands/version/bump.js +4 -4
  96. package/dist/commands/version/index.js +4 -4
  97. package/dist/commands/version/set.js +4 -4
  98. package/dist/commands/version/show.js +4 -4
  99. package/dist/components/skill-search/skill-search.js +3 -3
  100. package/dist/components/wizard/category-grid.js +2 -2
  101. package/dist/components/wizard/category-grid.test.js +34 -66
  102. package/dist/components/wizard/category-grid.test.js.map +1 -1
  103. package/dist/components/wizard/domain-selection.js +5 -5
  104. package/dist/components/wizard/help-modal.js +2 -2
  105. package/dist/components/wizard/menu-item.js +2 -2
  106. package/dist/components/wizard/search-modal.js +3 -3
  107. package/dist/components/wizard/search-modal.test.js +3 -3
  108. package/dist/components/wizard/section-progress.js +2 -2
  109. package/dist/components/wizard/section-progress.test.js +2 -2
  110. package/dist/components/wizard/source-grid.js +4 -4
  111. package/dist/components/wizard/source-grid.test.js +4 -4
  112. package/dist/components/wizard/stack-selection.js +8 -8
  113. package/dist/components/wizard/step-build.js +8 -7
  114. package/dist/components/wizard/step-build.test.js +18 -17
  115. package/dist/components/wizard/step-build.test.js.map +1 -1
  116. package/dist/components/wizard/step-confirm.js +3 -3
  117. package/dist/components/wizard/step-confirm.test.js +6 -6
  118. package/dist/components/wizard/step-refine.js +2 -2
  119. package/dist/components/wizard/step-refine.test.js +2 -2
  120. package/dist/components/wizard/step-settings.js +6 -6
  121. package/dist/components/wizard/step-settings.test.js +13 -13
  122. package/dist/components/wizard/step-settings.test.js.map +1 -1
  123. package/dist/components/wizard/step-sources.js +10 -9
  124. package/dist/components/wizard/step-sources.test.js +11 -10
  125. package/dist/components/wizard/step-sources.test.js.map +1 -1
  126. package/dist/components/wizard/step-stack.js +10 -10
  127. package/dist/components/wizard/step-stack.test.js +19 -23
  128. package/dist/components/wizard/step-stack.test.js.map +1 -1
  129. package/dist/components/wizard/view-title.js +2 -2
  130. package/dist/components/wizard/wizard-layout.js +7 -7
  131. package/dist/components/wizard/wizard-tabs.js +2 -2
  132. package/dist/components/wizard/wizard-tabs.test.js +7 -8
  133. package/dist/components/wizard/wizard-tabs.test.js.map +1 -1
  134. package/dist/components/wizard/wizard.js +24 -23
  135. package/dist/config/skills-matrix.yaml +10 -10
  136. package/dist/config/stacks.yaml +9 -9
  137. package/dist/hooks/init.js +5 -3
  138. package/dist/hooks/init.js.map +1 -1
  139. package/dist/{source-manager-SBPPLOQQ.js → source-manager-PTK4P6BF.js} +4 -4
  140. package/dist/src/agents/developer/api-developer/agent.yaml +1 -1
  141. package/dist/src/agents/developer/cli-developer/agent.yaml +1 -1
  142. package/dist/src/agents/developer/web-architecture/agent.yaml +1 -1
  143. package/dist/src/agents/developer/web-developer/agent.yaml +1 -1
  144. package/dist/src/agents/meta/agent-summoner/agent.yaml +1 -1
  145. package/dist/src/agents/meta/agent-summoner/critical-reminders.md +1 -1
  146. package/dist/src/agents/meta/agent-summoner/critical-requirements.md +1 -1
  147. package/dist/src/agents/meta/agent-summoner/examples.md +2 -2
  148. package/dist/src/agents/meta/agent-summoner/workflow.md +3 -3
  149. package/dist/src/agents/meta/documentor/agent.yaml +1 -1
  150. package/dist/src/agents/meta/skill-summoner/agent.yaml +1 -1
  151. package/dist/src/agents/meta/skill-summoner/output-format.md +1 -1
  152. package/dist/src/agents/migration/cli-migrator/agent.yaml +1 -1
  153. package/dist/src/agents/pattern/pattern-scout/agent.yaml +1 -1
  154. package/dist/src/agents/pattern/web-pattern-critique/agent.yaml +1 -1
  155. package/dist/src/agents/planning/web-pm/agent.yaml +1 -1
  156. package/dist/src/agents/researcher/api-researcher/agent.yaml +1 -1
  157. package/dist/src/agents/researcher/web-researcher/agent.yaml +1 -1
  158. package/dist/src/agents/reviewer/api-reviewer/agent.yaml +1 -1
  159. package/dist/src/agents/reviewer/cli-reviewer/agent.yaml +1 -1
  160. package/dist/src/agents/reviewer/web-reviewer/agent.yaml +1 -1
  161. package/dist/src/agents/tester/cli-tester/agent.yaml +1 -1
  162. package/dist/src/agents/tester/web-tester/agent.yaml +1 -1
  163. package/dist/stores/wizard-store.js +4 -4
  164. package/dist/stores/wizard-store.test.js +5 -5
  165. package/package.json +8 -8
  166. package/src/agents/developer/api-developer/agent.yaml +1 -1
  167. package/src/agents/developer/cli-developer/agent.yaml +1 -1
  168. package/src/agents/developer/web-architecture/agent.yaml +1 -1
  169. package/src/agents/developer/web-developer/agent.yaml +1 -1
  170. package/src/agents/meta/agent-summoner/agent.yaml +1 -1
  171. package/src/agents/meta/agent-summoner/critical-reminders.md +1 -1
  172. package/src/agents/meta/agent-summoner/critical-requirements.md +1 -1
  173. package/src/agents/meta/agent-summoner/examples.md +2 -2
  174. package/src/agents/meta/agent-summoner/workflow.md +3 -3
  175. package/src/agents/meta/documentor/agent.yaml +1 -1
  176. package/src/agents/meta/skill-summoner/agent.yaml +1 -1
  177. package/src/agents/meta/skill-summoner/output-format.md +1 -1
  178. package/src/agents/migration/cli-migrator/agent.yaml +1 -1
  179. package/src/agents/pattern/pattern-scout/agent.yaml +1 -1
  180. package/src/agents/pattern/web-pattern-critique/agent.yaml +1 -1
  181. package/src/agents/planning/web-pm/agent.yaml +1 -1
  182. package/src/agents/researcher/api-researcher/agent.yaml +1 -1
  183. package/src/agents/researcher/web-researcher/agent.yaml +1 -1
  184. package/src/agents/reviewer/api-reviewer/agent.yaml +1 -1
  185. package/src/agents/reviewer/cli-reviewer/agent.yaml +1 -1
  186. package/src/agents/reviewer/web-reviewer/agent.yaml +1 -1
  187. package/src/agents/tester/cli-tester/agent.yaml +1 -1
  188. package/src/agents/tester/web-tester/agent.yaml +1 -1
  189. package/src/schemas/project-config.schema.json +3 -0
  190. package/src/schemas/project-source-config.schema.json +12 -0
  191. package/dist/chunk-3ZOIOVKT.js.map +0 -1
  192. package/dist/chunk-5PIKNCZX.js.map +0 -1
  193. package/dist/chunk-B2UBHA66.js.map +0 -1
  194. package/dist/chunk-BZN2Z5P7.js.map +0 -1
  195. package/dist/chunk-BZQBJP34.js.map +0 -1
  196. package/dist/chunk-H566H3MQ.js +0 -87
  197. package/dist/chunk-H566H3MQ.js.map +0 -1
  198. package/dist/chunk-IYG2LAIM.js.map +0 -1
  199. package/dist/chunk-JMVWYAHT.js.map +0 -1
  200. package/dist/chunk-LAPCUV4D.js.map +0 -1
  201. package/dist/chunk-LGUI3PMO.js.map +0 -1
  202. package/dist/chunk-MM7NK5N2.js.map +0 -1
  203. package/dist/chunk-O4D67NN7.js +0 -24
  204. package/dist/chunk-O4D67NN7.js.map +0 -1
  205. package/dist/chunk-OGJIZ6QH.js.map +0 -1
  206. package/dist/chunk-OMV7TLWD.js.map +0 -1
  207. package/dist/chunk-PBEHPQLK.js.map +0 -1
  208. package/dist/chunk-R74PZWQS.js.map +0 -1
  209. package/dist/chunk-SO22IQPY.js.map +0 -1
  210. package/dist/chunk-WEUVWHMA.js.map +0 -1
  211. package/dist/chunk-YND42IXK.js.map +0 -1
  212. /package/dist/{chunk-IZZ4IIEG.js.map → chunk-5LPPIT6H.js.map} +0 -0
  213. /package/dist/{chunk-Z4TWOP3H.js.map → chunk-5YNZJ5TP.js.map} +0 -0
  214. /package/dist/{chunk-ODUOU55D.js.map → chunk-BLLXNFWP.js.map} +0 -0
  215. /package/dist/{chunk-4RAY5AOI.js.map → chunk-CEWNZQMH.js.map} +0 -0
  216. /package/dist/{chunk-A27LOC4Z.js.map → chunk-IG7CUREJ.js.map} +0 -0
  217. /package/dist/{chunk-FJQRVFMB.js.map → chunk-JXMRTHDT.js.map} +0 -0
  218. /package/dist/{chunk-W2ZSCZ2U.js.map → chunk-NJ775OJ4.js.map} +0 -0
  219. /package/dist/{chunk-R3XFQKPG.js.map → chunk-VEZ2GZEK.js.map} +0 -0
  220. /package/dist/{chunk-UX2H2K2G.js.map → chunk-XNQJBQ5X.js.map} +0 -0
  221. /package/dist/{chunk-EMJ2ZKS7.js.map → chunk-XYCN2GCV.js.map} +0 -0
  222. /package/dist/{chunk-UICL22RT.js.map → chunk-YIKBNGE3.js.map} +0 -0
  223. /package/dist/{chunk-QPTOIZAT.js.map → chunk-YPJKOM42.js.map} +0 -0
  224. /package/dist/{chunk-FZGYSLJL.js.map → chunk-ZE355C6C.js.map} +0 -0
  225. /package/dist/{source-manager-SBPPLOQQ.js.map → source-manager-PTK4P6BF.js.map} +0 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/cli/components/wizard/category-grid.tsx","../src/cli/components/hooks/use-category-grid-input.ts"],"sourcesContent":["import React, { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\n\nimport { Box, type DOMElement, Text, measureElement } from \"ink\";\n\nimport type { SkillId, Subcategory } from \"../../types/index.js\";\nimport { CLI_COLORS, SCROLL_VIEWPORT } from \"../../consts.js\";\nimport {\n findValidStartColumn,\n isSectionLocked,\n useCategoryGridInput,\n} from \"../hooks/use-category-grid-input.js\";\nimport { useFocusedListItem } from \"../hooks/use-focused-list-item.js\";\n\nexport type OptionState = \"normal\" | \"recommended\" | \"discouraged\" | \"disabled\";\n\nexport type CategoryOption = {\n id: SkillId;\n label: string;\n state: OptionState;\n stateReason?: string;\n selected: boolean;\n local?: boolean;\n installed?: boolean;\n};\n\nexport type CategoryRow = {\n id: Subcategory;\n displayName: string;\n required: boolean;\n exclusive: boolean;\n options: CategoryOption[];\n};\n\nexport type CategoryGridProps = {\n categories: CategoryRow[];\n /** Available height in terminal lines for the scrollable viewport. 0 = no constraint. */\n availableHeight?: number;\n showDescriptions: boolean;\n expertMode: boolean;\n onToggle: (categoryId: Subcategory, technologyId: SkillId) => void;\n onToggleDescriptions: () => void;\n /** Optional initial focus row (default: 0). Use with React `key` to reset. */\n defaultFocusedRow?: number;\n /** Optional initial focus col (default: 0). Use with React `key` to reset. */\n defaultFocusedCol?: number;\n /** Optional callback fired whenever the focused position changes */\n onFocusChange?: (row: number, col: number) => void;\n};\n\nconst SYMBOL_REQUIRED = \"*\";\n\n/**\n * Priority order for skill states in the initial sort.\n * Lower numbers appear first. Selected skills are sorted above all states.\n */\nconst STATE_PRIORITY: Record<OptionState, number> = {\n recommended: 0,\n normal: 1,\n discouraged: 2,\n disabled: 3,\n};\n\n/**\n * Sort options by: selected first, then by state priority.\n * Within each group, original matrix order is preserved (stable sort).\n */\nconst stableSortByState = (options: CategoryOption[]): CategoryOption[] => {\n return [...options].sort((a, b) => {\n // Selected skills first\n if (a.selected !== b.selected) return a.selected ? -1 : 1;\n // Then by state priority\n return STATE_PRIORITY[a.state] - STATE_PRIORITY[b.state];\n });\n};\n\nconst findNextValidOption = (\n options: CategoryOption[],\n currentIndex: number,\n direction: 1 | -1,\n wrap = true,\n): number => {\n const length = options.length;\n if (length === 0) return currentIndex;\n\n let index = currentIndex + direction;\n\n if (wrap) {\n if (index < 0) index = length - 1;\n if (index >= length) index = 0;\n } else {\n if (index < 0) index = 0;\n if (index >= length) index = length - 1;\n }\n\n return index;\n};\n\ntype SkillTagProps = {\n option: CategoryOption;\n isFocused: boolean;\n isLocked: boolean;\n};\n\nconst getStateSuffix = (state: OptionState, isLocked: boolean): string | null => {\n if (isLocked || state === \"disabled\") return \"(disabled)\";\n if (state === \"recommended\") return \"(recommended)\";\n if (state === \"discouraged\") return \"(discouraged)\";\n return null;\n};\n\nconst SkillTag: React.FC<SkillTagProps> = ({ option, isFocused, isLocked }) => {\n const getTextColor = (): string => {\n if (isLocked || option.state === \"disabled\") return CLI_COLORS.NEUTRAL;\n if (option.selected) return CLI_COLORS.PRIMARY;\n if (option.state === \"recommended\") return CLI_COLORS.UNFOCUSED;\n if (option.state === \"discouraged\") return CLI_COLORS.WARNING;\n // Normal unselected: muted color to clearly contrast with selected (cyan) skills\n return CLI_COLORS.NEUTRAL;\n };\n\n const getStateBorderColor = (): string => {\n if (isLocked || option.state === \"disabled\") return CLI_COLORS.NEUTRAL;\n if (option.selected) return CLI_COLORS.PRIMARY;\n if (option.state === \"recommended\") return CLI_COLORS.UNFOCUSED;\n if (option.state === \"discouraged\") return CLI_COLORS.WARNING;\n return CLI_COLORS.UNFOCUSED;\n };\n\n const isBold = isFocused || option.selected;\n const textColor = getTextColor();\n const stateSuffix = getStateSuffix(option.state, isLocked);\n\n return (\n <Box\n marginRight={1}\n borderColor={isFocused ? getStateBorderColor() : CLI_COLORS.NEUTRAL}\n borderStyle=\"single\"\n borderDimColor={!isFocused}\n >\n <Text color={textColor} bold={isBold} dimColor={false}>\n {\" \"}\n {option.local && (\n <>\n <Text backgroundColor={CLI_COLORS.NEUTRAL}> L </Text>{\" \"}\n </>\n )}\n {option.label}\n {stateSuffix && <Text dimColor> {stateSuffix}</Text>}{\" \"}\n </Text>\n </Box>\n );\n};\n\ntype CategorySectionProps = {\n category: CategoryRow;\n options: CategoryOption[];\n isLocked: boolean;\n isFocused: boolean;\n focusedOptionIndex: number;\n showDescriptions: boolean;\n};\n\nconst CategorySection: React.FC<CategorySectionProps> = ({\n category,\n options,\n isLocked,\n isFocused,\n focusedOptionIndex,\n showDescriptions,\n}) => {\n return (\n <Box flexDirection=\"column\" marginTop={1}>\n <Box flexDirection=\"row\">\n <Text dimColor={isLocked}>{category.displayName}</Text>\n {category.required && (\n <Text color={isLocked ? CLI_COLORS.NEUTRAL : CLI_COLORS.ERROR} dimColor={isLocked}>\n {\" \"}\n {SYMBOL_REQUIRED}\n </Text>\n )}\n </Box>\n\n <Box flexDirection=\"row\" flexWrap=\"wrap\" marginTop={0}>\n {options.map((option, index) => (\n <Box key={option.id} flexDirection=\"column\">\n <SkillTag\n option={option}\n isFocused={isFocused && index === focusedOptionIndex && !isLocked}\n isLocked={isLocked}\n />\n {showDescriptions && option.stateReason && !isLocked && (\n <Box marginLeft={1} marginBottom={0}>\n <Text dimColor wrap=\"truncate-end\">\n {option.stateReason}\n </Text>\n </Box>\n )}\n </Box>\n ))}\n </Box>\n </Box>\n );\n};\n\ntype ProcessedCategory = CategoryRow & { sortedOptions: CategoryOption[] };\n\nexport const CategoryGrid: React.FC<CategoryGridProps> = ({\n categories,\n availableHeight = 0,\n showDescriptions,\n expertMode,\n onToggle,\n onToggleDescriptions,\n defaultFocusedRow = 0,\n defaultFocusedCol = 0,\n onFocusChange,\n}) => {\n // Cache the initial sort order per category so toggling selections does not reorder skills.\n // The ref resets when the component remounts (e.g., domain change via key={activeDomain}).\n const initialOrderRef = useRef<Map<string, SkillId[]>>(new Map());\n\n const processedCategories = useMemo(\n () =>\n categories.map((category) => {\n const cached = initialOrderRef.current.get(category.id);\n if (cached) {\n // Re-order options to match the cached initial sort\n const orderMap = new Map(cached.map((id, idx) => [id, idx]));\n const sorted = [...category.options].sort((a, b) => {\n const aIdx = orderMap.get(a.id) ?? Infinity;\n const bIdx = orderMap.get(b.id) ?? Infinity;\n return aIdx - bIdx;\n });\n return { ...category, sortedOptions: sorted };\n }\n // First time seeing this category — sort by state priority\n const sorted = stableSortByState(category.options);\n initialOrderRef.current.set(\n category.id,\n sorted.map((o) => o.id),\n );\n return { ...category, sortedOptions: sorted };\n }),\n [categories],\n );\n\n const getColCount = useCallback(\n (row: number): number => processedCategories[row]?.sortedOptions.length ?? 0,\n [processedCategories],\n );\n\n const isRowLocked = useCallback(\n (row: number): boolean => {\n const cat = processedCategories[row];\n return cat ? isSectionLocked(cat.id, categories) : false;\n },\n [processedCategories, categories],\n );\n\n const findValidCol = useCallback(\n (row: number, currentCol: number, direction: 1 | -1): number => {\n const options = processedCategories[row]?.sortedOptions || [];\n const catId = processedCategories[row]?.id;\n if (catId && isSectionLocked(catId, categories)) return currentCol;\n return findNextValidOption(options, currentCol, direction, true);\n },\n [processedCategories, categories],\n );\n\n const { focusedRow, focusedCol, setFocused, moveFocus } = useFocusedListItem(\n processedCategories.length,\n getColCount,\n {\n wrap: true,\n isRowLocked,\n findValidCol,\n onChange: onFocusChange,\n initialRow: defaultFocusedRow,\n initialCol: defaultFocusedCol,\n },\n );\n\n useCategoryGridInput({\n processedCategories,\n categories,\n focusedRow,\n focusedCol,\n setFocused,\n moveFocus,\n onToggle,\n onToggleDescriptions,\n });\n\n // --- Pixel-accurate scroll tracking ---\n const sectionRefs = useRef<(DOMElement | null)[]>([]);\n const [sectionHeights, setSectionHeights] = useState<number[]>([]);\n const [scrollTopPx, setScrollTopPx] = useState(0);\n\n const setSectionRef = useCallback((index: number, el: DOMElement | null) => {\n sectionRefs.current[index] = el;\n }, []);\n\n // Measure all section heights after each render\n useEffect(() => {\n const heights = sectionRefs.current.map((el) => {\n if (el) {\n const { height } = measureElement(el);\n return height;\n }\n return 0;\n });\n setSectionHeights((prev) => {\n // Only update if heights actually changed (avoid infinite re-render)\n if (prev.length === heights.length && prev.every((h, i) => h === heights[i])) {\n return prev;\n }\n return heights;\n });\n });\n\n const scrollEnabled = availableHeight > 0 && availableHeight >= SCROLL_VIEWPORT.MIN_VIEWPORT_ROWS;\n\n // Scroll focused row into view\n useEffect(() => {\n if (!scrollEnabled || sectionHeights.length === 0) return;\n\n let topOfFocused = 0;\n for (let i = 0; i < focusedRow; i++) {\n topOfFocused += sectionHeights[i] ?? 0;\n }\n const focusedHeight = sectionHeights[focusedRow] ?? 0;\n const bottomOfFocused = topOfFocused + focusedHeight;\n\n setScrollTopPx((prev) => {\n if (topOfFocused < prev) {\n return topOfFocused;\n }\n if (bottomOfFocused > prev + availableHeight) {\n return bottomOfFocused - availableHeight;\n }\n return prev;\n });\n }, [focusedRow, sectionHeights, scrollEnabled, availableHeight]);\n\n if (categories.length === 0) {\n return (\n <Box flexDirection=\"column\">\n <Text dimColor>No categories to display.</Text>\n </Box>\n );\n }\n\n const sectionElements = processedCategories.map((category, index) => {\n const isLocked = isSectionLocked(category.id, categories);\n\n return (\n <Box key={category.id} ref={(el) => setSectionRef(index, el)} flexShrink={0}>\n <CategorySection\n category={category}\n options={category.sortedOptions}\n isLocked={isLocked}\n isFocused={index === focusedRow}\n focusedOptionIndex={focusedCol}\n showDescriptions={showDescriptions}\n />\n </Box>\n );\n });\n\n // When no height constraint, render flat (tests, or before first measurement)\n if (!scrollEnabled) {\n return (\n <Box flexDirection=\"column\" flexGrow={1} overflow=\"hidden\">\n {sectionElements}\n </Box>\n );\n }\n\n return (\n <Box flexDirection=\"column\" height={availableHeight} overflow=\"hidden\">\n <Box flexDirection=\"column\" marginTop={scrollTopPx > 0 ? -scrollTopPx : 0} flexShrink={0}>\n {sectionElements}\n </Box>\n </Box>\n );\n};\n","import { useCallback, useEffect } from \"react\";\nimport { useInput } from \"ink\";\n\nimport type { Subcategory, SkillId } from \"../../types/index.js\";\nimport type { CategoryOption, CategoryRow } from \"../wizard/category-grid.js\";\n\nconst FRAMEWORK_CATEGORY_ID = \"framework\";\n\n// Locked = non-framework section when no framework is selected\nexport const isSectionLocked = (categoryId: Subcategory, categories: CategoryRow[]): boolean => {\n if (categoryId === FRAMEWORK_CATEGORY_ID) {\n return false;\n }\n\n const frameworkCategory = categories.find((cat) => cat.id === FRAMEWORK_CATEGORY_ID);\n if (!frameworkCategory) return false;\n\n return !frameworkCategory.options.some((opt) => opt.selected);\n};\n\nexport const findValidStartColumn = (_options: CategoryOption[]): number => {\n return 0;\n};\n\n/** Find next unlocked section index (wrapping, direction: forward) */\nexport const findNextUnlockedIndex = (\n processed: { id: Subcategory; sortedOptions: CategoryOption[] }[],\n currentIndex: number,\n allCategories: CategoryRow[],\n): number => {\n const length = processed.length;\n if (length === 0) return currentIndex;\n\n let index = currentIndex;\n let attempts = 0;\n\n while (attempts < length) {\n index += 1;\n if (index >= length) index = 0;\n\n const category = processed[index];\n if (category && !isSectionLocked(category.id, allCategories)) {\n return index;\n }\n\n attempts++;\n }\n\n return currentIndex;\n};\n\ntype ProcessedCategory = CategoryRow & { sortedOptions: CategoryOption[] };\n\ntype UseCategoryGridInputOptions = {\n processedCategories: ProcessedCategory[];\n categories: CategoryRow[];\n focusedRow: number;\n focusedCol: number;\n setFocused: (row: number, col: number) => void;\n moveFocus: (direction: \"up\" | \"down\" | \"left\" | \"right\") => void;\n onToggle: (categoryId: Subcategory, technologyId: SkillId) => void;\n onToggleDescriptions: () => void;\n};\n\nexport function useCategoryGridInput({\n processedCategories,\n categories,\n focusedRow,\n focusedCol,\n setFocused,\n moveFocus,\n onToggle,\n onToggleDescriptions,\n}: UseCategoryGridInputOptions): void {\n const currentRow = processedCategories[focusedRow];\n const currentOptions = currentRow?.sortedOptions || [];\n const currentLocked = currentRow ? isSectionLocked(currentRow.id, categories) : false;\n\n // Adjust column when current row's options change externally (e.g. option becomes disabled)\n useEffect(() => {\n if (!currentRow) return;\n\n const maxCol = currentOptions.length - 1;\n if (focusedCol > maxCol) {\n const newCol = Math.max(0, maxCol);\n setFocused(focusedRow, newCol);\n }\n }, [focusedRow, currentOptions, focusedCol, setFocused, currentRow]);\n\n // Bounce off locked sections when a section becomes locked (e.g. framework deselected)\n useEffect(() => {\n if (currentRow && currentLocked) {\n const nextUnlocked = findNextUnlockedIndex(processedCategories, focusedRow, categories);\n if (nextUnlocked !== focusedRow) {\n const newRowOptions = processedCategories[nextUnlocked]?.sortedOptions || [];\n const newCol = findValidStartColumn(newRowOptions);\n setFocused(nextUnlocked, newCol);\n }\n }\n }, [currentRow, currentLocked, focusedRow, processedCategories, categories, setFocused]);\n\n useInput(\n useCallback(\n (\n input: string,\n key: {\n leftArrow: boolean;\n rightArrow: boolean;\n upArrow: boolean;\n downArrow: boolean;\n tab: boolean;\n shift: boolean;\n },\n ) => {\n if (key.tab && key.shift) {\n onToggleDescriptions();\n return;\n }\n\n if (key.tab && !key.shift) {\n const nextSection = findNextUnlockedIndex(processedCategories, focusedRow, categories);\n if (nextSection !== focusedRow) {\n const newRowOptions = processedCategories[nextSection]?.sortedOptions || [];\n const newCol = findValidStartColumn(newRowOptions);\n setFocused(nextSection, newCol);\n }\n return;\n }\n\n if (input === \"d\" || input === \"D\") {\n onToggleDescriptions();\n return;\n }\n\n if (input === \" \") {\n if (currentLocked) return;\n const currentOption = currentOptions[focusedCol];\n if (currentOption && currentOption.state !== \"disabled\") {\n onToggle(currentRow.id, currentOption.id);\n }\n return;\n }\n\n const isLeft = key.leftArrow || input === \"h\";\n const isRight = key.rightArrow || input === \"l\";\n const isUp = key.upArrow || input === \"k\";\n const isDown = key.downArrow || input === \"j\";\n\n if (isLeft) {\n if (currentLocked) return;\n moveFocus(\"left\");\n } else if (isRight) {\n if (currentLocked) return;\n moveFocus(\"right\");\n } else if (isUp) {\n moveFocus(\"up\");\n } else if (isDown) {\n moveFocus(\"down\");\n }\n },\n [\n focusedRow,\n focusedCol,\n currentOptions,\n currentRow,\n currentLocked,\n processedCategories,\n categories,\n onToggle,\n onToggleDescriptions,\n setFocused,\n moveFocus,\n ],\n ),\n );\n}\n"],"mappings":"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","names":["useCallback","useEffect","sorted","useCallback","useEffect"]}
@@ -2,16 +2,17 @@
2
2
  import {
3
3
  WIZARD_STEPS,
4
4
  WizardTabs
5
- } from "./chunk-IYG2LAIM.js";
5
+ } from "./chunk-LFHZBF6N.js";
6
6
  import {
7
7
  HelpModal
8
- } from "./chunk-FZGYSLJL.js";
8
+ } from "./chunk-ZE355C6C.js";
9
9
  import {
10
10
  useWizardStore
11
- } from "./chunk-3ZOIOVKT.js";
11
+ } from "./chunk-OGXSTJP2.js";
12
12
  import {
13
- CLI_COLORS
14
- } from "./chunk-LAPCUV4D.js";
13
+ CLI_COLORS,
14
+ DEFAULT_PLUGIN_NAME
15
+ } from "./chunk-YCS7GF6Y.js";
15
16
  import {
16
17
  init_esm_shims
17
18
  } from "./chunk-DHET7RCE.js";
@@ -20,6 +21,35 @@ import {
20
21
  init_esm_shims();
21
22
  import { Fragment } from "react";
22
23
  import { Box, Text } from "ink";
24
+
25
+ // src/cli/components/hooks/use-terminal-dimensions.ts
26
+ init_esm_shims();
27
+ import { useState, useEffect } from "react";
28
+ import { useStdout } from "ink";
29
+ var DEFAULT_COLUMNS = 80;
30
+ var DEFAULT_ROWS = 24;
31
+ function useTerminalDimensions() {
32
+ const { stdout } = useStdout();
33
+ const [dimensions, setDimensions] = useState(() => ({
34
+ columns: stdout.columns || DEFAULT_COLUMNS,
35
+ rows: stdout.rows || DEFAULT_ROWS
36
+ }));
37
+ useEffect(() => {
38
+ const handleResize = () => {
39
+ setDimensions({
40
+ columns: stdout.columns || DEFAULT_COLUMNS,
41
+ rows: stdout.rows || DEFAULT_ROWS
42
+ });
43
+ };
44
+ stdout.on("resize", handleResize);
45
+ return () => {
46
+ stdout.off("resize", handleResize);
47
+ };
48
+ }, [stdout]);
49
+ return dimensions;
50
+ }
51
+
52
+ // src/cli/components/wizard/wizard-layout.tsx
23
53
  import { Fragment as Fragment2, jsx, jsxs } from "react/jsx-runtime";
24
54
  import { createElement } from "react";
25
55
  var DefinitionItem = ({
@@ -33,11 +63,18 @@ var DefinitionItem = ({
33
63
  }
34
64
  return /* @__PURE__ */ jsxs(Text, { children: [
35
65
  values.map((value) => /* @__PURE__ */ jsxs(Fragment, { children: [
36
- /* @__PURE__ */ jsxs(Text, { backgroundColor: "black", color: CLI_COLORS.UNFOCUSED, children: [
37
- " ",
38
- value,
39
- " "
40
- ] }),
66
+ /* @__PURE__ */ jsxs(
67
+ Text,
68
+ {
69
+ backgroundColor: "black",
70
+ color: isActive ? CLI_COLORS.PRIMARY : CLI_COLORS.UNFOCUSED,
71
+ children: [
72
+ " ",
73
+ value,
74
+ " "
75
+ ]
76
+ }
77
+ ),
41
78
  " "
42
79
  ] }, value)),
43
80
  /* @__PURE__ */ jsx(Text, { color: isActive ? CLI_COLORS.PRIMARY : void 0, children: label })
@@ -80,14 +117,18 @@ var WizardFooter = () => {
80
117
  var WizardLayout = ({
81
118
  version,
82
119
  marketplaceLabel,
83
- brandingName,
84
- terminalHeight,
120
+ logo,
85
121
  children
86
122
  }) => {
87
123
  const store = useWizardStore();
88
124
  const { completedSteps, skippedSteps } = store.getStepProgress();
89
- const constrainedHeight = store.step === "build" ? terminalHeight : void 0;
90
- return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", paddingX: 1, height: constrainedHeight, children: [
125
+ const { rows: terminalHeight } = useTerminalDimensions();
126
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", paddingX: 1, height: terminalHeight, children: [
127
+ logo && /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(Text, { children: logo }) }),
128
+ /* @__PURE__ */ jsxs(Box, { children: [
129
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Marketplace: " }),
130
+ /* @__PURE__ */ jsx(Text, { bold: true, children: marketplaceLabel || `${DEFAULT_PLUGIN_NAME} (public)` })
131
+ ] }),
91
132
  /* @__PURE__ */ jsx(
92
133
  WizardTabs,
93
134
  {
@@ -95,14 +136,9 @@ var WizardLayout = ({
95
136
  currentStep: store.step,
96
137
  completedSteps,
97
138
  skippedSteps,
98
- version,
99
- brandingName
139
+ version
100
140
  }
101
141
  ),
102
- marketplaceLabel && /* @__PURE__ */ jsxs(Box, { paddingLeft: 1, marginTop: 1, children: [
103
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Marketplace: " }),
104
- /* @__PURE__ */ jsx(Text, { bold: true, children: marketplaceLabel })
105
- ] }),
106
142
  store.showHelp ? /* @__PURE__ */ jsx(HelpModal, { currentStep: store.step }) : /* @__PURE__ */ jsxs(Fragment2, { children: [
107
143
  /* @__PURE__ */ jsx(Box, { flexGrow: 1, marginTop: 1, children }),
108
144
  /* @__PURE__ */ jsxs(Box, { paddingX: 1, columnGap: 2, marginTop: 2, children: [
@@ -143,4 +179,4 @@ var WizardLayout = ({
143
179
  export {
144
180
  WizardLayout
145
181
  };
146
- //# sourceMappingURL=chunk-PBEHPQLK.js.map
182
+ //# sourceMappingURL=chunk-PKUIO2Z7.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/cli/components/wizard/wizard-layout.tsx","../src/cli/components/hooks/use-terminal-dimensions.ts"],"sourcesContent":["import React, { Fragment } from \"react\";\nimport { Box, Text } from \"ink\";\nimport { useWizardStore } from \"../../stores/wizard-store.js\";\nimport { CLI_COLORS, DEFAULT_PLUGIN_NAME } from \"../../consts.js\";\nimport { useTerminalDimensions } from \"../hooks/use-terminal-dimensions.js\";\nimport { WizardTabs, WIZARD_STEPS } from \"./wizard-tabs.js\";\nimport { HelpModal } from \"./help-modal.js\";\n\ntype KeyHintProps = {\n isVisible?: boolean;\n isActive?: boolean;\n label: string;\n values: string[];\n};\n\nconst DefinitionItem: React.FC<KeyHintProps> = ({\n isVisible = true,\n isActive = false,\n label,\n values,\n}) => {\n if (!isVisible) {\n return null;\n }\n\n return (\n <Text>\n {values.map((value) => (\n <Fragment key={value}>\n <Text\n backgroundColor=\"black\"\n color={isActive ? CLI_COLORS.PRIMARY : CLI_COLORS.UNFOCUSED}\n >\n {\" \"}\n {value}{\" \"}\n </Text>{\" \"}\n </Fragment>\n ))}\n <Text color={isActive ? CLI_COLORS.PRIMARY : undefined}>{label}</Text>\n </Text>\n );\n};\n\nconst HOT_KEYS: { label: string; values: string[] }[] = [\n { label: \"navigate\", values: [\"\\u2190/\\u2192\", \"\\u2191/\\u2193\"] },\n { label: \"select\", values: [\"SPACE\"] },\n { label: \"continue\", values: [\"ENTER\"] },\n { label: \"back\", values: [\"ESC\"] },\n];\n\nconst WizardFooter = () => {\n const store = useWizardStore();\n\n return (\n <Box\n columnGap={2}\n borderTop\n borderRight={false}\n borderBottom\n borderLeft={false}\n borderColor=\"blackBright\"\n borderStyle=\"single\"\n paddingLeft={1}\n paddingRight={1}\n >\n <DefinitionItem\n label=\"Accept defaults\"\n values={[\"A\"]}\n isVisible={store.step === \"build\" && !!store.selectedStackId}\n />\n {HOT_KEYS.map((hotkey) => (\n <DefinitionItem {...hotkey} key={hotkey.label} />\n ))}\n </Box>\n );\n};\n\ntype WizardLayoutProps = {\n version?: string;\n marketplaceLabel?: string;\n logo?: string;\n children: React.ReactNode;\n};\n\nexport const WizardLayout: React.FC<WizardLayoutProps> = ({\n version,\n marketplaceLabel,\n logo,\n children,\n}) => {\n const store = useWizardStore();\n const { completedSteps, skippedSteps } = store.getStepProgress();\n const { rows: terminalHeight } = useTerminalDimensions();\n\n return (\n <Box flexDirection=\"column\" paddingX={1} height={terminalHeight}>\n {logo && (\n <Box marginTop={1}>\n <Text>{logo}</Text>\n </Box>\n )}\n <Box>\n <Text dimColor>Marketplace: </Text>\n <Text bold>{marketplaceLabel || `${DEFAULT_PLUGIN_NAME} (public)`}</Text>\n </Box>\n <WizardTabs\n steps={WIZARD_STEPS}\n currentStep={store.step}\n completedSteps={completedSteps}\n skippedSteps={skippedSteps}\n version={version}\n />\n {store.showHelp ? (\n <HelpModal currentStep={store.step} />\n ) : (\n <>\n <Box flexGrow={1} marginTop={1}>\n {children}\n </Box>\n <Box paddingX={1} columnGap={2} marginTop={2}>\n <DefinitionItem label=\"Expert mode\" values={[\"E\"]} isActive={store.expertMode} />\n <DefinitionItem\n label=\"Descriptions\"\n values={[\"D\"]}\n isVisible={store.step === \"build\"}\n isActive={store.showDescriptions}\n />\n <DefinitionItem\n label=\"Plugin mode\"\n values={[\"P\"]}\n isActive={store.installMode === \"plugin\"}\n />\n <DefinitionItem\n label=\"Settings\"\n values={[\"G\"]}\n isVisible={store.step === \"sources\"}\n isActive={store.showSettings}\n />\n <DefinitionItem label=\"Help\" values={[\"?\"]} />\n </Box>\n <WizardFooter />\n </>\n )}\n </Box>\n );\n};\n","import { useState, useEffect } from \"react\";\nimport { useStdout } from \"ink\";\n\nconst DEFAULT_COLUMNS = 80;\nconst DEFAULT_ROWS = 24;\n\nexport type TerminalDimensions = {\n /** Terminal width in columns */\n columns: number;\n /** Terminal height in rows */\n rows: number;\n};\n\n/**\n * Tracks terminal dimensions reactively. Re-renders on resize.\n *\n * Falls back to DEFAULT_COLUMNS x DEFAULT_ROWS when stdout is not a TTY\n * (e.g., piped output, CI environments, tests).\n */\nexport function useTerminalDimensions(): TerminalDimensions {\n const { stdout } = useStdout();\n\n const [dimensions, setDimensions] = useState<TerminalDimensions>(() => ({\n columns: stdout.columns || DEFAULT_COLUMNS,\n rows: stdout.rows || DEFAULT_ROWS,\n }));\n\n useEffect(() => {\n const handleResize = () => {\n setDimensions({\n columns: stdout.columns || DEFAULT_COLUMNS,\n rows: stdout.rows || DEFAULT_ROWS,\n });\n };\n\n stdout.on(\"resize\", handleResize);\n return () => {\n stdout.off(\"resize\", handleResize);\n };\n }, [stdout]);\n\n return dimensions;\n}\n"],"mappings":"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","names":["Fragment"]}
@@ -2,24 +2,25 @@
2
2
  import {
3
3
  useModalState
4
4
  } from "./chunk-7SOPVGDV.js";
5
- import {
6
- SearchModal
7
- } from "./chunk-A27LOC4Z.js";
8
5
  import {
9
6
  useFocusedListItem
10
7
  } from "./chunk-DC5AK3LW.js";
8
+ import {
9
+ SearchModal
10
+ } from "./chunk-IG7CUREJ.js";
11
11
  import {
12
12
  CLI_COLORS,
13
+ SCROLL_VIEWPORT,
13
14
  UI_SYMBOLS
14
- } from "./chunk-LAPCUV4D.js";
15
+ } from "./chunk-YCS7GF6Y.js";
15
16
  import {
16
17
  init_esm_shims
17
18
  } from "./chunk-DHET7RCE.js";
18
19
 
19
20
  // src/cli/components/wizard/source-grid.tsx
20
21
  init_esm_shims();
21
- import { useCallback as useCallback2 } from "react";
22
- import { Box, Text, useInput } from "ink";
22
+ import { useCallback as useCallback2, useEffect, useRef, useState as useState2 } from "react";
23
+ import { Box, Text, measureElement, useInput } from "ink";
23
24
 
24
25
  // src/cli/components/hooks/use-source-grid-search-modal.ts
25
26
  init_esm_shims();
@@ -127,6 +128,7 @@ var getNavigableCount = (row, showSearchPill) => {
127
128
  };
128
129
  var SourceGrid = ({
129
130
  rows,
131
+ availableHeight = 0,
130
132
  onSelect,
131
133
  onSearch,
132
134
  onBind,
@@ -157,10 +159,49 @@ var SourceGrid = ({
157
159
  initialRow: defaultFocusedRow,
158
160
  initialCol: defaultFocusedCol
159
161
  });
162
+ const sectionRefs = useRef([]);
163
+ const [sectionHeights, setSectionHeights] = useState2([]);
164
+ const [scrollTopPx, setScrollTopPx] = useState2(0);
165
+ const setSectionRef = useCallback2((index, el) => {
166
+ sectionRefs.current[index] = el;
167
+ }, []);
168
+ useEffect(() => {
169
+ const heights = sectionRefs.current.map((el) => {
170
+ if (el) {
171
+ const { height } = measureElement(el);
172
+ return height;
173
+ }
174
+ return 0;
175
+ });
176
+ setSectionHeights((prev) => {
177
+ if (prev.length === heights.length && prev.every((h, i) => h === heights[i])) {
178
+ return prev;
179
+ }
180
+ return heights;
181
+ });
182
+ });
183
+ const scrollEnabled = availableHeight > 0 && availableHeight >= SCROLL_VIEWPORT.MIN_VIEWPORT_ROWS;
184
+ useEffect(() => {
185
+ if (!scrollEnabled || sectionHeights.length === 0) return;
186
+ let topOfFocused = 0;
187
+ for (let i = 0; i < focusedRow; i++) {
188
+ topOfFocused += sectionHeights[i] ?? 0;
189
+ }
190
+ const focusedHeight = sectionHeights[focusedRow] ?? 0;
191
+ const bottomOfFocused = topOfFocused + focusedHeight;
192
+ setScrollTopPx((prev) => {
193
+ if (topOfFocused < prev) {
194
+ return topOfFocused;
195
+ }
196
+ if (bottomOfFocused > prev + availableHeight) {
197
+ return bottomOfFocused - availableHeight;
198
+ }
199
+ return prev;
200
+ });
201
+ }, [focusedRow, sectionHeights, scrollEnabled, availableHeight]);
160
202
  useInput(
161
203
  useCallback2(
162
204
  (input, key) => {
163
- if (searchModal.isOpen) return;
164
205
  if (input === " ") {
165
206
  const currentRow = rows[focusedRow];
166
207
  if (!currentRow) return;
@@ -190,45 +231,44 @@ var SourceGrid = ({
190
231
  moveFocus("down");
191
232
  }
192
233
  },
193
- [
194
- rows,
195
- focusedRow,
196
- focusedCol,
197
- onSelect,
198
- showSearchPill,
199
- searchModal.isOpen,
200
- handleSearchTrigger,
201
- moveFocus
202
- ]
203
- )
234
+ [rows, focusedRow, focusedCol, onSelect, showSearchPill, handleSearchTrigger, moveFocus]
235
+ ),
236
+ { isActive: !searchModal.isOpen }
204
237
  );
205
238
  if (rows.length === 0) {
206
239
  return /* @__PURE__ */ jsx(Box, { flexDirection: "column", children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: "No skills to display." }) });
207
240
  }
208
- return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
209
- rows.map((row, rowIndex) => /* @__PURE__ */ jsx(
210
- SourceSection,
211
- {
212
- row,
213
- isFocused: rowIndex === focusedRow,
214
- focusedOptionIndex: focusedCol,
215
- showSearchPill
216
- },
217
- row.skillId
218
- )),
219
- searchModal.isOpen && /* @__PURE__ */ jsx(
220
- SearchModal,
221
- {
222
- results: searchResults,
223
- alias: searchAlias,
224
- onBind: handleBind,
225
- onClose: handleCloseSearch
226
- }
227
- )
241
+ const sectionElements = rows.map((row, rowIndex) => /* @__PURE__ */ jsx(Box, { ref: (el) => setSectionRef(rowIndex, el), flexShrink: 0, children: /* @__PURE__ */ jsx(
242
+ SourceSection,
243
+ {
244
+ row,
245
+ isFocused: rowIndex === focusedRow,
246
+ focusedOptionIndex: focusedCol,
247
+ showSearchPill
248
+ }
249
+ ) }, row.skillId));
250
+ const searchModalElement = searchModal.isOpen && /* @__PURE__ */ jsx(
251
+ SearchModal,
252
+ {
253
+ results: searchResults,
254
+ alias: searchAlias,
255
+ onBind: handleBind,
256
+ onClose: handleCloseSearch
257
+ }
258
+ );
259
+ if (!scrollEnabled) {
260
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", flexGrow: 1, overflow: "hidden", children: [
261
+ sectionElements,
262
+ searchModalElement
263
+ ] });
264
+ }
265
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", height: availableHeight, overflow: "hidden", children: [
266
+ /* @__PURE__ */ jsx(Box, { flexDirection: "column", marginTop: scrollTopPx > 0 ? -scrollTopPx : 0, flexShrink: 0, children: sectionElements }),
267
+ searchModalElement
228
268
  ] });
229
269
  };
230
270
 
231
271
  export {
232
272
  SourceGrid
233
273
  };
234
- //# sourceMappingURL=chunk-5PIKNCZX.js.map
274
+ //# sourceMappingURL=chunk-U36YCEBK.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/cli/components/wizard/source-grid.tsx","../src/cli/components/hooks/use-source-grid-search-modal.ts"],"sourcesContent":["import React, { useCallback, useEffect, useRef, useState } from \"react\";\nimport { Box, type DOMElement, Text, measureElement, useInput } from \"ink\";\nimport type { BoundSkillCandidate, SkillAlias, SkillId } from \"../../types/index.js\";\nimport { CLI_COLORS, SCROLL_VIEWPORT, UI_SYMBOLS } from \"../../consts.js\";\nimport { useFocusedListItem } from \"../hooks/use-focused-list-item.js\";\nimport { useSourceGridSearchModal } from \"../hooks/use-source-grid-search-modal.js\";\nimport { SearchModal } from \"./search-modal.js\";\n\nconst SEARCH_PILL_LABEL = \"\\u2315 Search\";\n\nexport type SourceOption = {\n id: string;\n label: string;\n selected: boolean;\n installed: boolean;\n};\n\nexport type SourceRow = {\n skillId: SkillId;\n displayName: string;\n alias: SkillAlias;\n options: SourceOption[];\n};\n\nexport type SourceGridProps = {\n rows: SourceRow[];\n /** Available height in terminal lines for the scrollable viewport. 0 = no constraint. */\n availableHeight?: number;\n onSelect: (skillId: SkillId, sourceId: string) => void;\n onSearch?: (alias: SkillAlias) => Promise<BoundSkillCandidate[]>;\n onBind?: (candidate: BoundSkillCandidate) => void;\n onSearchStateChange?: (active: boolean) => void;\n /** Optional initial focus row (default: 0). Use with React `key` to reset. */\n defaultFocusedRow?: number;\n /** Optional initial focus col (default: 0). Use with React `key` to reset. */\n defaultFocusedCol?: number;\n /** Optional callback fired whenever the focused position changes */\n onFocusChange?: (row: number, col: number) => void;\n};\n\ntype SearchPillProps = {\n isFocused: boolean;\n};\n\nconst SearchPill: React.FC<SearchPillProps> = ({ isFocused }) => {\n const borderColor = isFocused ? CLI_COLORS.UNFOCUSED : CLI_COLORS.NEUTRAL;\n\n return (\n <Box marginRight={1} borderColor={borderColor} borderStyle=\"single\" borderDimColor={!isFocused}>\n <Text dimColor={!isFocused} bold={isFocused}>\n {\" \"}\n {SEARCH_PILL_LABEL}{\" \"}\n </Text>\n </Box>\n );\n};\n\ntype SourceSectionProps = {\n row: SourceRow;\n isFocused: boolean;\n focusedOptionIndex: number;\n showSearchPill: boolean;\n};\n\nconst SourceTag: React.FC<{ option: SourceOption; isFocused: boolean }> = ({\n option,\n isFocused,\n}) => {\n const borderColor = option.selected\n ? CLI_COLORS.PRIMARY\n : isFocused\n ? CLI_COLORS.UNFOCUSED\n : CLI_COLORS.NEUTRAL;\n const textColor = option.selected ? CLI_COLORS.PRIMARY : undefined;\n const isBold = isFocused || option.selected;\n const symbol = option.selected ? UI_SYMBOLS.SELECTED : UI_SYMBOLS.UNSELECTED;\n\n return (\n <Box marginRight={1} borderColor={borderColor} borderStyle=\"single\">\n <Text color={textColor} bold={isBold}>\n {\" \"}\n <Text dimColor={!option.selected}>{symbol}</Text> {option.label}\n {option.selected && <Text dimColor> (active)</Text>}{\" \"}\n </Text>\n </Box>\n );\n};\n\nconst SourceSection: React.FC<SourceSectionProps> = ({\n row,\n isFocused,\n focusedOptionIndex,\n showSearchPill,\n}) => {\n const searchPillIndex = row.options.length;\n\n return (\n <Box flexDirection=\"column\" marginTop={1}>\n <Box flexDirection=\"row\">\n <Text>{row.displayName}</Text>\n </Box>\n\n <Box flexDirection=\"row\" flexWrap=\"wrap\" marginTop={0}>\n {row.options.map((option, index) => (\n <SourceTag\n key={option.id}\n option={option}\n isFocused={isFocused && index === focusedOptionIndex}\n />\n ))}\n {showSearchPill && (\n <SearchPill isFocused={isFocused && focusedOptionIndex === searchPillIndex} />\n )}\n </Box>\n </Box>\n );\n};\n\n/** Total navigable columns for a row (options + search pill if applicable) */\nconst getNavigableCount = (row: SourceRow, showSearchPill: boolean): number => {\n return row.options.length + (showSearchPill ? 1 : 0);\n};\n\nexport const SourceGrid: React.FC<SourceGridProps> = ({\n rows,\n availableHeight = 0,\n onSelect,\n onSearch,\n onBind,\n onSearchStateChange,\n defaultFocusedRow = 0,\n defaultFocusedCol = 0,\n onFocusChange,\n}) => {\n const {\n searchModal,\n searchResults,\n searchAlias,\n handleSearchTrigger,\n handleBind,\n handleCloseSearch,\n } = useSourceGridSearchModal({ rows, onSearch, onBind, onSearchStateChange });\n\n const showSearchPill = !!onSearch;\n\n const getColCount = useCallback(\n (row: number): number => {\n const rowData = rows[row];\n return rowData ? getNavigableCount(rowData, showSearchPill) : 0;\n },\n [rows, showSearchPill],\n );\n\n const { focusedRow, focusedCol, moveFocus } = useFocusedListItem(rows.length, getColCount, {\n wrap: true,\n onChange: onFocusChange,\n initialRow: defaultFocusedRow,\n initialCol: defaultFocusedCol,\n });\n\n // --- Pixel-accurate scroll tracking ---\n const sectionRefs = useRef<(DOMElement | null)[]>([]);\n const [sectionHeights, setSectionHeights] = useState<number[]>([]);\n const [scrollTopPx, setScrollTopPx] = useState(0);\n\n const setSectionRef = useCallback((index: number, el: DOMElement | null) => {\n sectionRefs.current[index] = el;\n }, []);\n\n // Measure all section heights after each render\n useEffect(() => {\n const heights = sectionRefs.current.map((el) => {\n if (el) {\n const { height } = measureElement(el);\n return height;\n }\n return 0;\n });\n setSectionHeights((prev) => {\n // Only update if heights actually changed (avoid infinite re-render)\n if (prev.length === heights.length && prev.every((h, i) => h === heights[i])) {\n return prev;\n }\n return heights;\n });\n });\n\n const scrollEnabled = availableHeight > 0 && availableHeight >= SCROLL_VIEWPORT.MIN_VIEWPORT_ROWS;\n\n // Scroll focused row into view\n useEffect(() => {\n if (!scrollEnabled || sectionHeights.length === 0) return;\n\n let topOfFocused = 0;\n for (let i = 0; i < focusedRow; i++) {\n topOfFocused += sectionHeights[i] ?? 0;\n }\n const focusedHeight = sectionHeights[focusedRow] ?? 0;\n const bottomOfFocused = topOfFocused + focusedHeight;\n\n setScrollTopPx((prev) => {\n if (topOfFocused < prev) {\n return topOfFocused;\n }\n if (bottomOfFocused > prev + availableHeight) {\n return bottomOfFocused - availableHeight;\n }\n return prev;\n });\n }, [focusedRow, sectionHeights, scrollEnabled, availableHeight]);\n\n useInput(\n useCallback(\n (\n input: string,\n key: {\n leftArrow: boolean;\n rightArrow: boolean;\n upArrow: boolean;\n downArrow: boolean;\n return: boolean;\n },\n ) => {\n if (input === \" \") {\n const currentRow = rows[focusedRow];\n if (!currentRow) return;\n // Space on search pill triggers search\n if (showSearchPill && focusedCol === currentRow.options.length) {\n void handleSearchTrigger(focusedRow);\n return;\n }\n // Space on a source option toggles selection\n if (focusedCol < currentRow.options.length) {\n const currentOption = currentRow.options[focusedCol];\n if (currentOption) {\n onSelect(currentRow.skillId, currentOption.id);\n }\n }\n return;\n }\n\n const isLeft = key.leftArrow;\n const isRight = key.rightArrow;\n const isUp = key.upArrow;\n const isDown = key.downArrow;\n\n if (isLeft) {\n moveFocus(\"left\");\n } else if (isRight) {\n moveFocus(\"right\");\n } else if (isUp) {\n moveFocus(\"up\");\n } else if (isDown) {\n moveFocus(\"down\");\n }\n },\n [rows, focusedRow, focusedCol, onSelect, showSearchPill, handleSearchTrigger, moveFocus],\n ),\n { isActive: !searchModal.isOpen },\n );\n\n if (rows.length === 0) {\n return (\n <Box flexDirection=\"column\">\n <Text dimColor>No skills to display.</Text>\n </Box>\n );\n }\n\n const sectionElements = rows.map((row, rowIndex) => (\n <Box key={row.skillId} ref={(el) => setSectionRef(rowIndex, el)} flexShrink={0}>\n <SourceSection\n row={row}\n isFocused={rowIndex === focusedRow}\n focusedOptionIndex={focusedCol}\n showSearchPill={showSearchPill}\n />\n </Box>\n ));\n\n const searchModalElement = searchModal.isOpen && (\n <SearchModal\n results={searchResults}\n alias={searchAlias}\n onBind={handleBind}\n onClose={handleCloseSearch}\n />\n );\n\n // When no height constraint, render flat (tests, or before first measurement)\n if (!scrollEnabled) {\n return (\n <Box flexDirection=\"column\" flexGrow={1} overflow=\"hidden\">\n {sectionElements}\n {searchModalElement}\n </Box>\n );\n }\n\n return (\n <Box flexDirection=\"column\" height={availableHeight} overflow=\"hidden\">\n <Box flexDirection=\"column\" marginTop={scrollTopPx > 0 ? -scrollTopPx : 0} flexShrink={0}>\n {sectionElements}\n </Box>\n {searchModalElement}\n </Box>\n );\n};\n","import { useCallback, useState } from \"react\";\nimport type { BoundSkillCandidate, SkillAlias } from \"../../types/index.js\";\nimport { useModalState } from \"./use-modal-state.js\";\nimport type { SourceRow } from \"../wizard/source-grid.js\";\n\ntype UseSourceGridSearchModalOptions = {\n rows: SourceRow[];\n onSearch?: (alias: SkillAlias) => Promise<BoundSkillCandidate[]>;\n onBind?: (candidate: BoundSkillCandidate) => void;\n onSearchStateChange?: (active: boolean) => void;\n};\n\ntype UseSourceGridSearchModalResult = {\n searchModal: { isOpen: boolean };\n searchResults: BoundSkillCandidate[];\n searchAlias: string;\n handleSearchTrigger: (rowIndex: number) => Promise<void>;\n handleBind: (candidate: BoundSkillCandidate) => void;\n handleCloseSearch: () => void;\n};\n\nexport function useSourceGridSearchModal({\n rows,\n onSearch,\n onBind,\n onSearchStateChange,\n}: UseSourceGridSearchModalOptions): UseSourceGridSearchModalResult {\n const searchModal = useModalState<number>();\n const [searchResults, setSearchResults] = useState<BoundSkillCandidate[]>([]);\n const [searchAlias, setSearchAlias] = useState(\"\");\n\n const resetSearch = useCallback(() => {\n searchModal.close();\n setSearchResults([]);\n setSearchAlias(\"\");\n onSearchStateChange?.(false);\n }, [onSearchStateChange, searchModal]);\n\n const handleSearchTrigger = useCallback(\n async (rowIndex: number) => {\n const row = rows[rowIndex];\n if (!row || !onSearch) return;\n\n const alias = row.alias;\n setSearchAlias(alias);\n searchModal.open(rowIndex);\n onSearchStateChange?.(true);\n\n const results = await onSearch(alias);\n setSearchResults(results);\n },\n [rows, onSearch, onSearchStateChange, searchModal],\n );\n\n const handleBind = useCallback(\n (candidate: BoundSkillCandidate) => {\n onBind?.(candidate);\n resetSearch();\n },\n [onBind, resetSearch],\n );\n\n const handleCloseSearch = useCallback(() => {\n resetSearch();\n }, [resetSearch]);\n\n return {\n searchModal: { isOpen: searchModal.isOpen },\n searchResults,\n searchAlias,\n handleSearchTrigger,\n handleBind,\n handleCloseSearch,\n };\n}\n"],"mappings":"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","names":["useCallback","useState","useCallback","useState"]}
@@ -1,29 +1,30 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
- ViewTitle
4
- } from "./chunk-O4D67NN7.js";
3
+ useMeasuredHeight
4
+ } from "./chunk-AQQVSNUX.js";
5
5
  import {
6
6
  getDomainDisplayName
7
- } from "./chunk-SO22IQPY.js";
7
+ } from "./chunk-MZB3GGOH.js";
8
+ import {
9
+ ViewTitle
10
+ } from "./chunk-HTTPKSL6.js";
8
11
  import {
9
12
  CategoryGrid
10
- } from "./chunk-OGJIZ6QH.js";
13
+ } from "./chunk-OKILA27U.js";
11
14
  import {
12
15
  getAvailableSkills,
13
16
  resolveAlias
14
- } from "./chunk-MM7NK5N2.js";
17
+ } from "./chunk-WMVGRAFB.js";
15
18
  import {
16
- CLI_COLORS,
17
- SCROLL_VIEWPORT,
18
- UI_SYMBOLS
19
- } from "./chunk-LAPCUV4D.js";
19
+ CLI_COLORS
20
+ } from "./chunk-YCS7GF6Y.js";
20
21
  import {
21
22
  init_esm_shims
22
23
  } from "./chunk-DHET7RCE.js";
23
24
 
24
25
  // src/cli/components/wizard/step-build.tsx
25
26
  init_esm_shims();
26
- import { useState as useState3 } from "react";
27
+ import { useState } from "react";
27
28
  import { Box, Text, useInput } from "ink";
28
29
 
29
30
  // src/cli/lib/wizard/index.ts
@@ -161,57 +162,6 @@ function useFrameworkFiltering({
161
162
  );
162
163
  }
163
164
 
164
- // src/cli/components/hooks/use-terminal-dimensions.ts
165
- init_esm_shims();
166
- import { useState, useEffect } from "react";
167
- import { useStdout } from "ink";
168
- var DEFAULT_COLUMNS = 80;
169
- var DEFAULT_ROWS = 24;
170
- function useTerminalDimensions() {
171
- const { stdout } = useStdout();
172
- const [dimensions, setDimensions] = useState(() => ({
173
- columns: stdout.columns || DEFAULT_COLUMNS,
174
- rows: stdout.rows || DEFAULT_ROWS
175
- }));
176
- useEffect(() => {
177
- const handleResize = () => {
178
- setDimensions({
179
- columns: stdout.columns || DEFAULT_COLUMNS,
180
- rows: stdout.rows || DEFAULT_ROWS
181
- });
182
- };
183
- stdout.on("resize", handleResize);
184
- return () => {
185
- stdout.off("resize", handleResize);
186
- };
187
- }, [stdout]);
188
- return dimensions;
189
- }
190
-
191
- // src/cli/components/hooks/use-measured-height.ts
192
- init_esm_shims();
193
- import { useRef, useState as useState2, useEffect as useEffect2 } from "react";
194
- import { measureElement, useStdout as useStdout2 } from "ink";
195
- function useMeasuredHeight() {
196
- const ref = useRef(null);
197
- const [measuredHeight, setMeasuredHeight] = useState2(0);
198
- const { stdout } = useStdout2();
199
- useEffect2(() => {
200
- const measure = () => {
201
- if (ref.current) {
202
- const { height } = measureElement(ref.current);
203
- setMeasuredHeight((prev) => prev !== height ? height : prev);
204
- }
205
- };
206
- measure();
207
- stdout.on("resize", measure);
208
- return () => {
209
- stdout.off("resize", measure);
210
- };
211
- }, [stdout]);
212
- return { ref, measuredHeight };
213
- }
214
-
215
165
  // src/cli/components/wizard/step-build.tsx
216
166
  import { jsx, jsxs } from "react/jsx-runtime";
217
167
  var Footer = ({ validationError }) => {
@@ -220,26 +170,6 @@ var Footer = ({ validationError }) => {
220
170
  /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Press ESC to go back, or select a skill and press ENTER to continue." })
221
171
  ] }) });
222
172
  };
223
- var LegendRow = () => {
224
- return /* @__PURE__ */ jsxs(Box, { paddingLeft: 1, columnGap: 2, children: [
225
- /* @__PURE__ */ jsxs(Text, { color: CLI_COLORS.PRIMARY, children: [
226
- UI_SYMBOLS.SELECTED,
227
- " active"
228
- ] }),
229
- /* @__PURE__ */ jsxs(Text, { color: CLI_COLORS.UNFOCUSED, children: [
230
- UI_SYMBOLS.UNSELECTED,
231
- " recommended"
232
- ] }),
233
- /* @__PURE__ */ jsxs(Text, { color: CLI_COLORS.WARNING, children: [
234
- UI_SYMBOLS.DISCOURAGED,
235
- " discouraged"
236
- ] }),
237
- /* @__PURE__ */ jsxs(Text, { color: CLI_COLORS.NEUTRAL, children: [
238
- UI_SYMBOLS.DISABLED,
239
- " disabled"
240
- ] })
241
- ] });
242
- };
243
173
  var StepBuild = ({
244
174
  matrix,
245
175
  domain: activeDomain,
@@ -255,8 +185,7 @@ var StepBuild = ({
255
185
  onContinue,
256
186
  onBack
257
187
  }) => {
258
- const [validationError, setValidationError] = useState3(void 0);
259
- const { columns } = useTerminalDimensions();
188
+ const [validationError, setValidationError] = useState(void 0);
260
189
  const { ref: gridRef, measuredHeight: gridHeight } = useMeasuredHeight();
261
190
  const categories = useFrameworkFiltering({
262
191
  domain: activeDomain,
@@ -267,7 +196,6 @@ var StepBuild = ({
267
196
  parentDomainSelections,
268
197
  installedSkillIds
269
198
  });
270
- const availableHeight = gridHeight > 0 ? Math.max(SCROLL_VIEWPORT.MIN_VIEWPORT_ROWS, gridHeight) : Infinity;
271
199
  useInput((_input, key) => {
272
200
  if (key.return) {
273
201
  const validation = validateBuildStep(categories, selections);
@@ -282,8 +210,8 @@ var StepBuild = ({
282
210
  onBack();
283
211
  }
284
212
  });
285
- return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", width: "100%", flexGrow: 1, children: [
286
- /* @__PURE__ */ jsxs(
213
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", width: "100%", flexGrow: 1, flexBasis: 0, children: [
214
+ /* @__PURE__ */ jsx(
287
215
  Box,
288
216
  {
289
217
  columnGap: 2,
@@ -297,34 +225,22 @@ var StepBuild = ({
297
225
  borderLeft: false,
298
226
  borderColor: CLI_COLORS.NEUTRAL,
299
227
  borderStyle: "single",
300
- children: [
301
- /* @__PURE__ */ jsx(Box, { columnGap: 2, flexDirection: "row", children: selectedDomains.map((domain) => {
302
- const isActive = domain === activeDomain;
303
- return /* @__PURE__ */ jsxs(Text, { color: isActive ? CLI_COLORS.PRIMARY : void 0, children: [
304
- isActive ? UI_SYMBOLS.CURRENT : UI_SYMBOLS.UNSELECTED,
305
- " ",
306
- getDomainDisplayName(domain)
307
- ] }, domain);
308
- }) }),
309
- /* @__PURE__ */ jsx(LegendRow, {})
310
- ]
228
+ children: /* @__PURE__ */ jsx(Box, { columnGap: 2, flexDirection: "row", children: selectedDomains.map((domain) => {
229
+ const isActive = domain === activeDomain;
230
+ return /* @__PURE__ */ jsx(Text, { color: isActive ? CLI_COLORS.PRIMARY : void 0, bold: isActive, children: getDomainDisplayName(domain) }, domain);
231
+ }) })
311
232
  }
312
233
  ),
313
- /* @__PURE__ */ jsxs(ViewTitle, { children: [
314
- "Customize your ",
315
- getDomainDisplayName(activeDomain),
316
- " stack"
317
- ] }),
318
- /* @__PURE__ */ jsx(Box, { ref: gridRef, flexGrow: 1, children: /* @__PURE__ */ jsx(
234
+ /* @__PURE__ */ jsx(ViewTitle, { children: `[2] Customize your ${getDomainDisplayName(activeDomain)} stack` }),
235
+ /* @__PURE__ */ jsx(Box, { ref: gridRef, flexGrow: 1, flexBasis: 0, children: /* @__PURE__ */ jsx(
319
236
  CategoryGrid,
320
237
  {
321
238
  categories,
239
+ availableHeight: gridHeight,
322
240
  expertMode,
323
241
  showDescriptions,
324
242
  onToggle,
325
- onToggleDescriptions,
326
- availableHeight,
327
- terminalWidth: columns
243
+ onToggleDescriptions
328
244
  },
329
245
  activeDomain
330
246
  ) }),
@@ -337,4 +253,4 @@ export {
337
253
  getSkillDisplayLabel,
338
254
  StepBuild
339
255
  };
340
- //# sourceMappingURL=chunk-OMV7TLWD.js.map
256
+ //# sourceMappingURL=chunk-UFUQUFV6.js.map