@polymorphism-tech/morph-spec 7.9.4 → 8.0.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 (211) hide show
  1. package/CLAUDE.md +2 -2
  2. package/README.md +31 -2
  3. package/bin/morph-spec.js +50 -2
  4. package/bin/validate.js +379 -379
  5. package/claude-plugin.json +1 -1
  6. package/docs/CHEATSHEET.md +38 -1
  7. package/docs/QUICKSTART.md +60 -1
  8. package/framework/CLAUDE.md +13 -2
  9. package/framework/MORPH.md +38 -2
  10. package/framework/agents.json +22 -3
  11. package/framework/commands/commit.md +166 -166
  12. package/framework/commands/morph-hotfix.md +39 -0
  13. package/framework/commands/morph-infra.md +215 -215
  14. package/framework/commands/morph-preflight.md +205 -205
  15. package/framework/commands/morph-troubleshoot.md +124 -124
  16. package/framework/hooks/README.md +25 -10
  17. package/framework/hooks/claude-code/core/gate-guard.js +4 -2
  18. package/framework/hooks/claude-code/core/loop-detect.js +2 -1
  19. package/framework/hooks/claude-code/core/post-edit-typecheck.js +14 -5
  20. package/framework/hooks/claude-code/core/tasks-json-guard.js +4 -2
  21. package/framework/hooks/claude-code/core/validate-completion.js +1 -1
  22. package/framework/hooks/claude-code/core/worktree-context.js +160 -0
  23. package/framework/hooks/claude-code/statusline.py +679 -679
  24. package/framework/hooks/claude-code/statusline.sh +11 -11
  25. package/framework/hooks/git/commit-msg/conventional-commits.sh +33 -33
  26. package/framework/hooks/git/pre-commit/agents.sh +25 -25
  27. package/framework/hooks/git/pre-commit/orchestrator.sh +64 -64
  28. package/framework/hooks/git/pre-commit/specs.sh +50 -50
  29. package/framework/hooks/git/pre-push/run-tests.sh +44 -44
  30. package/framework/hooks/shared/activity-logger.js +160 -160
  31. package/framework/hooks/shared/build-check.js +2 -2
  32. package/framework/hooks/shared/hook-response.js +17 -5
  33. package/framework/hooks/shared/payload-utils.js +42 -0
  34. package/framework/hooks/shared/state-reader.js +22 -2
  35. package/framework/hooks/shared/stdin-reader.js +26 -26
  36. package/framework/rubrics/backend.json +41 -41
  37. package/framework/rules/ai-agents-standards.md +45 -45
  38. package/framework/rules/dotnet-templates.md +86 -86
  39. package/framework/rules/frontend-standards.md +13 -13
  40. package/framework/rules/infrastructure-standards.md +27 -27
  41. package/framework/rules/neon-standards.md +25 -25
  42. package/framework/rules/nextjs-standards.md +20 -20
  43. package/framework/rules/nextjs-templates.md +40 -40
  44. package/framework/schemas/feature.schema.json +95 -1
  45. package/framework/skills/core/create-umbraco-lp/SKILL.md +1 -1
  46. package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +1 -1
  47. package/framework/skills/core/morph-apply/SKILL.md +39 -8
  48. package/framework/skills/core/morph-brainstorming/SKILL.md +9 -1
  49. package/framework/skills/core/morph-eval/SKILL.md +6 -1
  50. package/framework/skills/core/morph-hotfix/SKILL.md +136 -0
  51. package/framework/skills/core/morph-implement/SKILL.md +8 -4
  52. package/framework/skills/core/morph-plan/SKILL.md +5 -1
  53. package/framework/skills/core/morph-proposal/SKILL.md +24 -1
  54. package/framework/skills/core/morph-review/SKILL.md +3 -2
  55. package/framework/skills/core/morph-stack-scan/SKILL.md +1 -1
  56. package/framework/skills/core/morph-standards/SKILL.md +1 -1
  57. package/framework/standards/ai-agents/agent-session.md +160 -160
  58. package/framework/standards/ai-agents/batching.md +193 -193
  59. package/framework/standards/ai-agents/context-providers.md +188 -188
  60. package/framework/standards/ai-agents/mcp-server.md +250 -250
  61. package/framework/standards/ai-agents/mcp-tools.md +197 -197
  62. package/framework/standards/ai-agents/middleware-patterns.md +207 -207
  63. package/framework/standards/ai-agents/modalities-image-gen.md +173 -173
  64. package/framework/standards/ai-agents/modalities-speech-to-text.md +171 -171
  65. package/framework/standards/ai-agents/modalities-text-to-speech.md +181 -181
  66. package/framework/standards/ai-agents/modalities-vision.md +201 -201
  67. package/framework/standards/ai-agents/multi-agent-patterns.md +155 -155
  68. package/framework/standards/ai-agents/production.md +275 -275
  69. package/framework/standards/ai-agents/prompts-in-db.md +214 -214
  70. package/framework/standards/ai-agents/providers/model-registry.md +158 -158
  71. package/framework/standards/ai-agents/service-tier-flex.md +135 -135
  72. package/framework/standards/ai-agents/structured-output.md +249 -249
  73. package/framework/standards/ai-agents/sweet-spot.md +109 -109
  74. package/framework/templates/.idea/morph-templates.xml +92 -0
  75. package/framework/templates/.vscode/morph-templates.code-snippets +186 -0
  76. package/framework/templates/IDE-SNIPPETS.md +262 -262
  77. package/framework/templates/REGISTRY.json +0 -62
  78. package/framework/templates/code/dotnet/abstractions/api-endpoint.cs +7 -7
  79. package/framework/templates/code/dotnet/ai-agents/AgentBootstrap.cs.template +39 -39
  80. package/framework/templates/code/dotnet/ai-agents/EmbeddingService.cs.template +68 -68
  81. package/framework/templates/code/dotnet/ai-agents/ModelRegistry.cs.template +117 -117
  82. package/framework/templates/code/dotnet/ai-agents/PromptRepository.cs.template +95 -95
  83. package/framework/templates/code/dotnet/ai-agents/ResilientToolMiddleware.cs.template +61 -61
  84. package/framework/templates/code/dotnet/ai-agents/VectorSearchTool.cs.template +107 -107
  85. package/framework/templates/code/dotnet/ai-agents/model-registry.json.template +58 -58
  86. package/framework/templates/code/dotnet/contracts/contracts-vsa.cs +281 -281
  87. package/framework/templates/code/dotnet/database/migration.cs +83 -83
  88. package/framework/templates/code/dotnet/feature/endpoint.cs.hbs +21 -21
  89. package/framework/templates/code/dotnet/feature/request.cs.hbs +4 -4
  90. package/framework/templates/code/dotnet/feature/response.cs.hbs +4 -4
  91. package/framework/templates/code/dotnet/feature/validator.cs.hbs +15 -15
  92. package/framework/templates/code/sql/ai-agents/agent-prompts-migration.sql.template +58 -58
  93. package/framework/templates/code/sql/ai-agents/pgvector-embeddings-migration.sql.template +91 -91
  94. package/framework/templates/code/typescript/contracts.ts +167 -167
  95. package/framework/templates/docs/clarifications.md +253 -253
  96. package/framework/templates/docs/schema-analysis.md +119 -119
  97. package/framework/templates/docs/ui-components.md +52 -52
  98. package/framework/templates/docs/ui-design-system.md +76 -76
  99. package/framework/templates/docs/ui-flows.md +167 -167
  100. package/framework/templates/docs/ui-mockups.md +98 -98
  101. package/framework/templates/feature/decisions.md +122 -122
  102. package/framework/templates/frontend/nextjs/Dockerfile.nextjs.hbs +43 -43
  103. package/framework/templates/frontend/nextjs/client-component.tsx.hbs +26 -26
  104. package/framework/templates/frontend/nextjs/env.mjs.hbs +32 -32
  105. package/framework/templates/frontend/nextjs/feature-form.tsx.hbs +56 -56
  106. package/framework/templates/frontend/nextjs/page.tsx.hbs +22 -22
  107. package/framework/templates/frontend/nextjs/tsconfig.json.hbs +26 -26
  108. package/framework/templates/frontend/nextjs/use-feature.ts.hbs +54 -54
  109. package/framework/templates/infrastructure/docker/Dockerfile.template +58 -58
  110. package/framework/templates/infrastructure/docker/docker-compose.template.yml +50 -50
  111. package/framework/templates/infrastructure/docker/dockerfile-api.dockerfile +38 -38
  112. package/framework/templates/infrastructure/docker/dockerfile-web.dockerfile +48 -48
  113. package/framework/templates/infrastructure/github/README.md +56 -56
  114. package/framework/templates/infrastructure/github/actions/docker-build-push/action.yml.hbs +45 -45
  115. package/framework/templates/infrastructure/github/actions/health-check/action.yml.hbs +27 -27
  116. package/framework/templates/infrastructure/github/workflows/docker-build-push.yml.hbs +59 -59
  117. package/framework/templates/infrastructure/github/workflows/dotnet-build.yml.hbs +39 -39
  118. package/framework/templates/integrations/asaas-client.cs +387 -387
  119. package/framework/templates/integrations/asaas-webhook.cs +351 -351
  120. package/framework/templates/meta-prompts/fusion/fusion-agent.md +76 -76
  121. package/framework/templates/meta-prompts/fusion/fusion-aggregator.md +100 -100
  122. package/framework/templates/meta-prompts/validators/pre-commit-validator.md +95 -95
  123. package/framework/templates/project/validate.js +120 -120
  124. package/framework/templates/state.template.json +17 -17
  125. package/framework/templates/ui/design-system.css +226 -226
  126. package/package.json +1 -1
  127. package/src/commands/dashboard/dashboard.js +53 -53
  128. package/src/commands/index.js +13 -13
  129. package/src/commands/mcp/mcp-setup.js +519 -519
  130. package/src/commands/project/checkpoint.js +209 -209
  131. package/src/commands/project/doctor.js +4 -2
  132. package/src/commands/project/init.js +46 -2
  133. package/src/commands/project/update.js +5 -3
  134. package/src/commands/project/worktree.js +1005 -46
  135. package/src/commands/state/advance.js +17 -2
  136. package/src/commands/state/approve.js +18 -1
  137. package/src/commands/state/create.js +158 -14
  138. package/src/commands/state/retype.js +126 -0
  139. package/src/commands/state/score.js +4 -0
  140. package/src/commands/tasks/dag.js +32 -0
  141. package/src/commands/validation/index.js +1 -0
  142. package/src/commands/validation/validate-feature.js +33 -5
  143. package/src/commands/validation/verify.js +190 -0
  144. package/src/core/state/phase-state-machine.js +197 -197
  145. package/src/core/state/state-manager.js +35 -10
  146. package/src/core/templates/template-renderer.js +477 -477
  147. package/src/core/work-types.js +79 -0
  148. package/src/dashboard/app/components/DetailPanel.jsx +11 -11
  149. package/src/dashboard/app/components/FlowGraph.jsx +17 -17
  150. package/src/dashboard/app/components/SearchFilter.jsx +22 -22
  151. package/src/dashboard/app/hooks/useDetailPanel.js +15 -15
  152. package/src/dashboard/app/main.jsx +13 -13
  153. package/src/dashboard/app/pages/AgentMap.jsx +370 -370
  154. package/src/dashboard/app/pages/HookInspector.jsx +418 -418
  155. package/src/dashboard/app/pages/LiveState.jsx +11 -1
  156. package/src/dashboard/app/pages/PhasePipeline.jsx +408 -408
  157. package/src/dashboard/app/pages/RulesMap.jsx +273 -273
  158. package/src/dashboard/app/pages/SkillsLibrary.jsx +366 -366
  159. package/src/dashboard/app/pages/Standards.jsx +332 -332
  160. package/src/dashboard/app/styles/index.css +47 -47
  161. package/src/dashboard/index.html +12 -12
  162. package/src/dashboard/package.json +24 -24
  163. package/src/dashboard/scanner/hooks-scanner.js +41 -41
  164. package/src/dashboard/scanner/phases-scanner.js +15 -15
  165. package/src/dashboard/scanner/rubrics-scanner.js +12 -12
  166. package/src/dashboard/scanner/rules-scanner.js +37 -37
  167. package/src/dashboard/scanner/skills-scanner.js +43 -43
  168. package/src/dashboard/scanner/squads-scanner.js +13 -13
  169. package/src/dashboard/scanner/standards-scanner.js +26 -26
  170. package/src/dashboard/scanner/state-scanner.js +42 -0
  171. package/src/dashboard/vite-plugin-morph-data.js +17 -17
  172. package/src/dashboard/vite.config.js +24 -24
  173. package/src/lib/agents/metrics-aggregator.js +56 -56
  174. package/src/lib/agents/metrics-calculator.js +125 -125
  175. package/src/lib/agents/output-collector.js +73 -73
  176. package/src/lib/detectors/claude-config-detector.js +152 -152
  177. package/src/lib/detectors/design-system-detector.js +231 -231
  178. package/src/lib/detectors/index.js +217 -217
  179. package/src/lib/generators/architecture-scanner.js +639 -639
  180. package/src/lib/generators/design-system-generator.js +508 -508
  181. package/src/lib/generators/index.js +13 -13
  182. package/src/lib/generators/settings-generator.js +209 -209
  183. package/src/lib/hooks/hook-executor.js +167 -167
  184. package/src/lib/installers/mcp-installer.js +719 -719
  185. package/src/lib/installers/memory-installer.js +1 -1
  186. package/src/lib/risk-detector.js +16 -0
  187. package/src/lib/tasks/dispatch-planner.js +122 -4
  188. package/src/lib/tasks/mermaid-renderer.js +132 -0
  189. package/src/lib/tasks/task-classifier.js +30 -30
  190. package/src/lib/tasks/task-parser.js +209 -209
  191. package/src/lib/tasks/test-runner.js +27 -6
  192. package/src/lib/validators/architecture/architecture-validator.js +259 -259
  193. package/src/lib/validators/checkpoint-persistence.js +39 -39
  194. package/src/lib/validators/contracts/contract-compliance-validator.js +274 -274
  195. package/src/lib/validators/css/css-validator.js +340 -340
  196. package/src/lib/validators/packages/package-validator.js +347 -347
  197. package/src/lib/validators/shared/emit-validator-dispatch.js +14 -14
  198. package/src/lib/validators/shared/issue-counter.js +14 -14
  199. package/src/lib/validators/validation-runner.js +349 -349
  200. package/src/lib/validators/verify-runner.js +322 -0
  201. package/src/lib/work-type-detector.js +143 -0
  202. package/src/scripts/setup-infra.js +32 -5
  203. package/src/utils/agent-id-mapper.js +11 -11
  204. package/src/utils/agents-installer.js +10 -6
  205. package/src/utils/claude-settings-manager.js +300 -300
  206. package/src/utils/file-copier.js +43 -0
  207. package/src/utils/hooks-installer.js +41 -11
  208. package/src/utils/skills-installer.js +174 -174
  209. package/framework/templates/meta-prompts/parallel-workers/parallel-coordinator.md +0 -113
  210. package/framework/templates/meta-prompts/parallel-workers/parallel-worker.md +0 -80
  211. package/src/lib/tasks/wave-resolver.js +0 -353
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Work Types — Single Source of Truth (v8)
3
+ *
4
+ * Routes work by TYPE (feature/bug/chore/hotfix), each with a dimensioned
5
+ * pipeline. `derivePhase` is already skip-tolerant via approved gates
6
+ * (`gateImpliedPhase`), so "skipping phases" means stamping gates by policy
7
+ * at birth (`approvedBy: "policy:{type}"` — see POLICY_APPROVER_PREFIX).
8
+ *
9
+ * Edit ONLY this file to add, rename, or retune a work type. Consumed by
10
+ * `create`/`retype`/`risk-detector` in later tasks — this module is pure
11
+ * and has no filesystem/CLI side effects.
12
+ *
13
+ * Fields per type:
14
+ * label - Human-readable name
15
+ * scaffold - Phase folders created at feature birth (subset of
16
+ * 0-proposal/1-design/2-plan/3-implement/4-review)
17
+ * skippedGates - Gates stamped `policy:{type}`-approved at birth
18
+ * instead of waiting for human approval
19
+ * forcedPauseGates - Gates that ALWAYS pause for a human, regardless of
20
+ * trust mode or skippedGates (e.g. hotfix.review)
21
+ * entrySkill - The skill that kicks off work of this type
22
+ */
23
+
24
+ export const DEFAULT_WORK_TYPE = 'feature';
25
+ export const POLICY_APPROVER_PREFIX = 'policy:';
26
+
27
+ export const WORK_TYPES = {
28
+ feature: {
29
+ label: 'Feature',
30
+ scaffold: ['0-proposal'],
31
+ skippedGates: [],
32
+ forcedPauseGates: [],
33
+ entrySkill: 'morph-proposal',
34
+ },
35
+ bug: {
36
+ label: 'Bug fix',
37
+ scaffold: ['0-proposal'],
38
+ skippedGates: [],
39
+ forcedPauseGates: [],
40
+ entrySkill: 'morph-proposal',
41
+ },
42
+ chore: {
43
+ label: 'Chore',
44
+ scaffold: ['0-proposal', '2-plan', '3-implement'],
45
+ skippedGates: ['proposal', 'plan'],
46
+ forcedPauseGates: [],
47
+ entrySkill: 'morph-proposal',
48
+ },
49
+ hotfix: {
50
+ label: 'Hotfix',
51
+ scaffold: ['0-proposal', '2-plan', '3-implement'],
52
+ skippedGates: ['proposal', 'plan'],
53
+ forcedPauseGates: ['review'],
54
+ entrySkill: 'morph-hotfix',
55
+ },
56
+ };
57
+
58
+ /**
59
+ * @param {string} t - candidate work type key
60
+ * @returns {boolean} true iff `t` is a known key of WORK_TYPES
61
+ */
62
+ export function isValidWorkType(t) {
63
+ return Object.prototype.hasOwnProperty.call(WORK_TYPES, t);
64
+ }
65
+
66
+ /**
67
+ * Resolve a feature's work-type config, defaulting to `feature` when the
68
+ * feature has no `workType` or an unrecognized one.
69
+ *
70
+ * @param {object} feature - feature.json contents (uses `workType`)
71
+ * @returns {object} the matching WORK_TYPES entry
72
+ */
73
+ export function getWorkType(feature) {
74
+ // Guard against inherited property names (e.g. workType: "toString") — a bare
75
+ // WORK_TYPES[key] lookup would resolve those to Object.prototype members.
76
+ return isValidWorkType(feature?.workType)
77
+ ? WORK_TYPES[feature.workType]
78
+ : WORK_TYPES[DEFAULT_WORK_TYPE];
79
+ }
@@ -1,11 +1,11 @@
1
- export default function DetailPanel({ open, title, onClose, children }) {
2
- return (
3
- <div className={`fixed top-0 right-0 h-full w-96 bg-gray-900 border-l border-gray-700 shadow-2xl transform transition-transform duration-200 ease-in-out z-50 ${open ? 'translate-x-0' : 'translate-x-full'}`}>
4
- <div className="flex items-center justify-between p-4 border-b border-gray-700">
5
- <h2 className="text-white font-semibold text-lg truncate">{title}</h2>
6
- <button onClick={onClose} className="text-gray-400 hover:text-white text-xl leading-none">×</button>
7
- </div>
8
- <div className="p-4 overflow-y-auto h-[calc(100%-57px)]">{children}</div>
9
- </div>
10
- );
11
- }
1
+ export default function DetailPanel({ open, title, onClose, children }) {
2
+ return (
3
+ <div className={`fixed top-0 right-0 h-full w-96 bg-gray-900 border-l border-gray-700 shadow-2xl transform transition-transform duration-200 ease-in-out z-50 ${open ? 'translate-x-0' : 'translate-x-full'}`}>
4
+ <div className="flex items-center justify-between p-4 border-b border-gray-700">
5
+ <h2 className="text-white font-semibold text-lg truncate">{title}</h2>
6
+ <button onClick={onClose} className="text-gray-400 hover:text-white text-xl leading-none">×</button>
7
+ </div>
8
+ <div className="p-4 overflow-y-auto h-[calc(100%-57px)]">{children}</div>
9
+ </div>
10
+ );
11
+ }
@@ -1,17 +1,17 @@
1
- import { ReactFlow, Controls, MiniMap, Background } from '@xyflow/react';
2
-
3
- export default function FlowGraph({ nodes, edges, nodeTypes, onNodeClick, fitView = true, className = '' }) {
4
- return (
5
- <div className={`w-full h-full ${className}`} style={{ minHeight: '500px' }}>
6
- <ReactFlow
7
- nodes={nodes} edges={edges} nodeTypes={nodeTypes} onNodeClick={onNodeClick}
8
- fitView={fitView} proOptions={{ hideAttribution: true }}
9
- defaultEdgeOptions={{ type: 'smoothstep', animated: false, style: { stroke: '#475569', strokeWidth: 1.5 } }}
10
- >
11
- <Background color="#1e293b" gap={20} />
12
- <Controls position="bottom-right" />
13
- <MiniMap nodeColor={(node) => node.data?.color || '#475569'} maskColor="rgba(0, 0, 0, 0.7)" position="bottom-left" />
14
- </ReactFlow>
15
- </div>
16
- );
17
- }
1
+ import { ReactFlow, Controls, MiniMap, Background } from '@xyflow/react';
2
+
3
+ export default function FlowGraph({ nodes, edges, nodeTypes, onNodeClick, fitView = true, className = '' }) {
4
+ return (
5
+ <div className={`w-full h-full ${className}`} style={{ minHeight: '500px' }}>
6
+ <ReactFlow
7
+ nodes={nodes} edges={edges} nodeTypes={nodeTypes} onNodeClick={onNodeClick}
8
+ fitView={fitView} proOptions={{ hideAttribution: true }}
9
+ defaultEdgeOptions={{ type: 'smoothstep', animated: false, style: { stroke: '#475569', strokeWidth: 1.5 } }}
10
+ >
11
+ <Background color="#1e293b" gap={20} />
12
+ <Controls position="bottom-right" />
13
+ <MiniMap nodeColor={(node) => node.data?.color || '#475569'} maskColor="rgba(0, 0, 0, 0.7)" position="bottom-left" />
14
+ </ReactFlow>
15
+ </div>
16
+ );
17
+ }
@@ -1,22 +1,22 @@
1
- import { useState } from 'react';
2
-
3
- export default function SearchFilter({ placeholder = 'Search...', filters = [], onSearchChange, onFilterChange }) {
4
- const [search, setSearch] = useState('');
5
- function handleSearchChange(e) {
6
- setSearch(e.target.value);
7
- onSearchChange?.(e.target.value);
8
- }
9
- return (
10
- <div className="flex items-center gap-3 p-3 bg-gray-900 border-b border-gray-800">
11
- <input type="text" value={search} onChange={handleSearchChange} placeholder={placeholder}
12
- className="flex-1 bg-gray-800 border border-gray-700 rounded-md px-3 py-1.5 text-sm text-gray-100 placeholder-gray-500 focus:outline-none focus:border-blue-500" />
13
- {filters.map(({ name, label, options, value }) => (
14
- <select key={name} value={value} onChange={(e) => onFilterChange?.(name, e.target.value)}
15
- className="bg-gray-800 border border-gray-700 rounded-md px-2 py-1.5 text-sm text-gray-300 focus:outline-none focus:border-blue-500">
16
- <option value="">{label}: All</option>
17
- {options.map((opt) => (<option key={opt} value={opt}>{opt}</option>))}
18
- </select>
19
- ))}
20
- </div>
21
- );
22
- }
1
+ import { useState } from 'react';
2
+
3
+ export default function SearchFilter({ placeholder = 'Search...', filters = [], onSearchChange, onFilterChange }) {
4
+ const [search, setSearch] = useState('');
5
+ function handleSearchChange(e) {
6
+ setSearch(e.target.value);
7
+ onSearchChange?.(e.target.value);
8
+ }
9
+ return (
10
+ <div className="flex items-center gap-3 p-3 bg-gray-900 border-b border-gray-800">
11
+ <input type="text" value={search} onChange={handleSearchChange} placeholder={placeholder}
12
+ className="flex-1 bg-gray-800 border border-gray-700 rounded-md px-3 py-1.5 text-sm text-gray-100 placeholder-gray-500 focus:outline-none focus:border-blue-500" />
13
+ {filters.map(({ name, label, options, value }) => (
14
+ <select key={name} value={value} onChange={(e) => onFilterChange?.(name, e.target.value)}
15
+ className="bg-gray-800 border border-gray-700 rounded-md px-2 py-1.5 text-sm text-gray-300 focus:outline-none focus:border-blue-500">
16
+ <option value="">{label}: All</option>
17
+ {options.map((opt) => (<option key={opt} value={opt}>{opt}</option>))}
18
+ </select>
19
+ ))}
20
+ </div>
21
+ );
22
+ }
@@ -1,15 +1,15 @@
1
- import { useState, useCallback } from 'react';
2
-
3
- export function useDetailPanel() {
4
- const [panel, setPanel] = useState({ open: false, title: '', content: null });
5
-
6
- const openPanel = useCallback((title, content) => {
7
- setPanel({ open: true, title, content });
8
- }, []);
9
-
10
- const closePanel = useCallback(() => {
11
- setPanel((prev) => ({ ...prev, open: false }));
12
- }, []);
13
-
14
- return { panel, openPanel, closePanel };
15
- }
1
+ import { useState, useCallback } from 'react';
2
+
3
+ export function useDetailPanel() {
4
+ const [panel, setPanel] = useState({ open: false, title: '', content: null });
5
+
6
+ const openPanel = useCallback((title, content) => {
7
+ setPanel({ open: true, title, content });
8
+ }, []);
9
+
10
+ const closePanel = useCallback(() => {
11
+ setPanel((prev) => ({ ...prev, open: false }));
12
+ }, []);
13
+
14
+ return { panel, openPanel, closePanel };
15
+ }
@@ -1,13 +1,13 @@
1
- import { StrictMode } from 'react';
2
- import { createRoot } from 'react-dom/client';
3
- import { BrowserRouter } from 'react-router-dom';
4
- import App from './App.jsx';
5
- import './styles/index.css';
6
-
7
- createRoot(document.getElementById('root')).render(
8
- <StrictMode>
9
- <BrowserRouter>
10
- <App />
11
- </BrowserRouter>
12
- </StrictMode>
13
- );
1
+ import { StrictMode } from 'react';
2
+ import { createRoot } from 'react-dom/client';
3
+ import { BrowserRouter } from 'react-router-dom';
4
+ import App from './App.jsx';
5
+ import './styles/index.css';
6
+
7
+ createRoot(document.getElementById('root')).render(
8
+ <StrictMode>
9
+ <BrowserRouter>
10
+ <App />
11
+ </BrowserRouter>
12
+ </StrictMode>
13
+ );