@polymorphism-tech/morph-spec 7.9.4 → 8.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +2 -2
- package/README.md +31 -2
- package/bin/morph-spec.js +50 -2
- package/bin/validate.js +379 -379
- package/claude-plugin.json +1 -1
- package/docs/CHEATSHEET.md +38 -1
- package/docs/QUICKSTART.md +60 -1
- package/framework/CLAUDE.md +14 -3
- package/framework/MORPH.md +41 -5
- package/framework/agents.json +22 -3
- package/framework/commands/commit.md +166 -166
- package/framework/commands/morph-hotfix.md +39 -0
- package/framework/commands/morph-infra.md +215 -215
- package/framework/commands/morph-preflight.md +205 -205
- package/framework/commands/morph-troubleshoot.md +124 -124
- package/framework/hooks/README.md +25 -11
- package/framework/hooks/claude-code/core/gate-guard.js +4 -2
- package/framework/hooks/claude-code/core/loop-detect.js +2 -1
- package/framework/hooks/claude-code/core/post-edit-typecheck.js +14 -5
- package/framework/hooks/claude-code/core/tasks-json-guard.js +4 -2
- package/framework/hooks/claude-code/core/validate-completion.js +1 -1
- package/framework/hooks/claude-code/core/worktree-context.js +160 -0
- package/framework/hooks/claude-code/statusline.py +679 -679
- package/framework/hooks/claude-code/statusline.sh +11 -11
- package/framework/hooks/git/commit-msg/conventional-commits.sh +33 -33
- package/framework/hooks/git/pre-commit/agents.sh +25 -25
- package/framework/hooks/git/pre-commit/orchestrator.sh +64 -64
- package/framework/hooks/git/pre-commit/specs.sh +50 -50
- package/framework/hooks/git/pre-push/run-tests.sh +44 -44
- package/framework/hooks/shared/activity-logger.js +160 -160
- package/framework/hooks/shared/build-check.js +2 -2
- package/framework/hooks/shared/hook-response.js +17 -5
- package/framework/hooks/shared/payload-utils.js +42 -0
- package/framework/hooks/shared/state-reader.js +22 -2
- package/framework/hooks/shared/stdin-reader.js +26 -26
- package/framework/rubrics/backend.json +41 -41
- package/framework/rules/ai-agents-standards.md +45 -45
- package/framework/rules/dotnet-templates.md +86 -86
- package/framework/rules/frontend-standards.md +13 -13
- package/framework/rules/infrastructure-standards.md +27 -27
- package/framework/rules/neon-standards.md +25 -25
- package/framework/rules/nextjs-standards.md +20 -20
- package/framework/rules/nextjs-templates.md +40 -40
- package/framework/schemas/feature.schema.json +95 -1
- package/framework/skills/core/create-umbraco-lp/SKILL.md +1 -1
- package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +1 -1
- package/framework/skills/core/morph-apply/SKILL.md +49 -27
- package/framework/skills/core/morph-brainstorming/SKILL.md +9 -1
- package/framework/skills/core/morph-eval/SKILL.md +6 -1
- package/framework/skills/core/morph-hotfix/SKILL.md +134 -0
- package/framework/skills/core/morph-implement/SKILL.md +17 -22
- package/framework/skills/core/morph-plan/SKILL.md +5 -1
- package/framework/skills/core/morph-proposal/SKILL.md +24 -1
- package/framework/skills/core/morph-review/SKILL.md +13 -16
- package/framework/skills/core/morph-stack-scan/SKILL.md +1 -1
- package/framework/skills/core/morph-standards/SKILL.md +1 -1
- package/framework/standards/ai-agents/agent-session.md +160 -160
- package/framework/standards/ai-agents/batching.md +193 -193
- package/framework/standards/ai-agents/context-providers.md +188 -188
- package/framework/standards/ai-agents/mcp-server.md +250 -250
- package/framework/standards/ai-agents/mcp-tools.md +197 -197
- package/framework/standards/ai-agents/middleware-patterns.md +207 -207
- package/framework/standards/ai-agents/modalities-image-gen.md +173 -173
- package/framework/standards/ai-agents/modalities-speech-to-text.md +171 -171
- package/framework/standards/ai-agents/modalities-text-to-speech.md +181 -181
- package/framework/standards/ai-agents/modalities-vision.md +201 -201
- package/framework/standards/ai-agents/multi-agent-patterns.md +155 -155
- package/framework/standards/ai-agents/production.md +275 -275
- package/framework/standards/ai-agents/prompts-in-db.md +214 -214
- package/framework/standards/ai-agents/providers/model-registry.md +158 -158
- package/framework/standards/ai-agents/service-tier-flex.md +135 -135
- package/framework/standards/ai-agents/structured-output.md +249 -249
- package/framework/standards/ai-agents/sweet-spot.md +109 -109
- package/framework/templates/.idea/morph-templates.xml +92 -0
- package/framework/templates/.vscode/morph-templates.code-snippets +186 -0
- package/framework/templates/IDE-SNIPPETS.md +262 -262
- package/framework/templates/REGISTRY.json +0 -62
- package/framework/templates/code/dotnet/abstractions/api-endpoint.cs +7 -7
- package/framework/templates/code/dotnet/ai-agents/AgentBootstrap.cs.template +39 -39
- package/framework/templates/code/dotnet/ai-agents/EmbeddingService.cs.template +68 -68
- package/framework/templates/code/dotnet/ai-agents/ModelRegistry.cs.template +117 -117
- package/framework/templates/code/dotnet/ai-agents/PromptRepository.cs.template +95 -95
- package/framework/templates/code/dotnet/ai-agents/ResilientToolMiddleware.cs.template +61 -61
- package/framework/templates/code/dotnet/ai-agents/VectorSearchTool.cs.template +107 -107
- package/framework/templates/code/dotnet/ai-agents/model-registry.json.template +58 -58
- package/framework/templates/code/dotnet/contracts/contracts-vsa.cs +281 -281
- package/framework/templates/code/dotnet/database/migration.cs +83 -83
- package/framework/templates/code/dotnet/feature/endpoint.cs.hbs +21 -21
- package/framework/templates/code/dotnet/feature/request.cs.hbs +4 -4
- package/framework/templates/code/dotnet/feature/response.cs.hbs +4 -4
- package/framework/templates/code/dotnet/feature/validator.cs.hbs +15 -15
- package/framework/templates/code/sql/ai-agents/agent-prompts-migration.sql.template +58 -58
- package/framework/templates/code/sql/ai-agents/pgvector-embeddings-migration.sql.template +91 -91
- package/framework/templates/code/typescript/contracts.ts +167 -167
- package/framework/templates/docs/clarifications.md +253 -253
- package/framework/templates/docs/schema-analysis.md +119 -119
- package/framework/templates/docs/ui-components.md +52 -52
- package/framework/templates/docs/ui-design-system.md +76 -76
- package/framework/templates/docs/ui-flows.md +167 -167
- package/framework/templates/docs/ui-mockups.md +98 -98
- package/framework/templates/feature/decisions.md +122 -122
- package/framework/templates/frontend/nextjs/Dockerfile.nextjs.hbs +43 -43
- package/framework/templates/frontend/nextjs/client-component.tsx.hbs +26 -26
- package/framework/templates/frontend/nextjs/env.mjs.hbs +32 -32
- package/framework/templates/frontend/nextjs/feature-form.tsx.hbs +56 -56
- package/framework/templates/frontend/nextjs/page.tsx.hbs +22 -22
- package/framework/templates/frontend/nextjs/tsconfig.json.hbs +26 -26
- package/framework/templates/frontend/nextjs/use-feature.ts.hbs +54 -54
- package/framework/templates/infrastructure/docker/Dockerfile.template +58 -58
- package/framework/templates/infrastructure/docker/docker-compose.template.yml +50 -50
- package/framework/templates/infrastructure/docker/dockerfile-api.dockerfile +38 -38
- package/framework/templates/infrastructure/docker/dockerfile-web.dockerfile +48 -48
- package/framework/templates/infrastructure/github/README.md +56 -56
- package/framework/templates/infrastructure/github/actions/docker-build-push/action.yml.hbs +45 -45
- package/framework/templates/infrastructure/github/actions/health-check/action.yml.hbs +27 -27
- package/framework/templates/infrastructure/github/workflows/docker-build-push.yml.hbs +59 -59
- package/framework/templates/infrastructure/github/workflows/dotnet-build.yml.hbs +39 -39
- package/framework/templates/integrations/asaas-client.cs +387 -387
- package/framework/templates/integrations/asaas-webhook.cs +351 -351
- package/framework/templates/meta-prompts/fusion/fusion-agent.md +76 -76
- package/framework/templates/meta-prompts/fusion/fusion-aggregator.md +100 -100
- package/framework/templates/meta-prompts/validators/pre-commit-validator.md +95 -95
- package/framework/templates/project/validate.js +120 -120
- package/framework/templates/state.template.json +17 -17
- package/framework/templates/ui/design-system.css +226 -226
- package/package.json +1 -1
- package/src/commands/dashboard/dashboard.js +53 -53
- package/src/commands/index.js +13 -13
- package/src/commands/mcp/mcp-setup.js +519 -519
- package/src/commands/project/checkpoint.js +209 -209
- package/src/commands/project/doctor.js +4 -2
- package/src/commands/project/init.js +46 -2
- package/src/commands/project/update.js +5 -3
- package/src/commands/project/worktree.js +1005 -46
- package/src/commands/state/advance.js +17 -2
- package/src/commands/state/approve.js +18 -1
- package/src/commands/state/create.js +158 -14
- package/src/commands/state/retype.js +126 -0
- package/src/commands/state/score.js +4 -0
- package/src/commands/tasks/dag.js +32 -0
- package/src/commands/validation/index.js +1 -0
- package/src/commands/validation/validate-feature.js +33 -5
- package/src/commands/validation/verify.js +190 -0
- package/src/core/state/phase-state-machine.js +197 -197
- package/src/core/state/state-manager.js +35 -10
- package/src/core/templates/template-renderer.js +477 -477
- package/src/core/work-types.js +79 -0
- package/src/dashboard/app/components/DetailPanel.jsx +11 -11
- package/src/dashboard/app/components/FlowGraph.jsx +17 -17
- package/src/dashboard/app/components/SearchFilter.jsx +22 -22
- package/src/dashboard/app/hooks/useDetailPanel.js +15 -15
- package/src/dashboard/app/main.jsx +13 -13
- package/src/dashboard/app/pages/AgentMap.jsx +370 -370
- package/src/dashboard/app/pages/HookInspector.jsx +418 -418
- package/src/dashboard/app/pages/LiveState.jsx +11 -1
- package/src/dashboard/app/pages/PhasePipeline.jsx +408 -408
- package/src/dashboard/app/pages/RulesMap.jsx +273 -273
- package/src/dashboard/app/pages/SkillsLibrary.jsx +366 -366
- package/src/dashboard/app/pages/Standards.jsx +332 -332
- package/src/dashboard/app/styles/index.css +47 -47
- package/src/dashboard/index.html +12 -12
- package/src/dashboard/package.json +24 -24
- package/src/dashboard/scanner/hooks-scanner.js +41 -41
- package/src/dashboard/scanner/phases-scanner.js +15 -15
- package/src/dashboard/scanner/rubrics-scanner.js +12 -12
- package/src/dashboard/scanner/rules-scanner.js +37 -37
- package/src/dashboard/scanner/skills-scanner.js +43 -43
- package/src/dashboard/scanner/squads-scanner.js +13 -13
- package/src/dashboard/scanner/standards-scanner.js +26 -26
- package/src/dashboard/scanner/state-scanner.js +42 -0
- package/src/dashboard/vite-plugin-morph-data.js +17 -17
- package/src/dashboard/vite.config.js +24 -24
- package/src/lib/agents/metrics-aggregator.js +56 -56
- package/src/lib/agents/metrics-calculator.js +125 -125
- package/src/lib/agents/output-collector.js +73 -73
- package/src/lib/detectors/claude-config-detector.js +152 -152
- package/src/lib/detectors/design-system-detector.js +231 -231
- package/src/lib/detectors/index.js +217 -217
- package/src/lib/generators/architecture-scanner.js +639 -639
- package/src/lib/generators/design-system-generator.js +508 -508
- package/src/lib/generators/index.js +13 -13
- package/src/lib/generators/settings-generator.js +209 -209
- package/src/lib/hooks/hook-executor.js +167 -167
- package/src/lib/installers/mcp-installer.js +719 -719
- package/src/lib/installers/memory-installer.js +1 -1
- package/src/lib/risk-detector.js +16 -0
- package/src/lib/tasks/dispatch-planner.js +122 -4
- package/src/lib/tasks/mermaid-renderer.js +132 -0
- package/src/lib/tasks/task-classifier.js +30 -30
- package/src/lib/tasks/task-parser.js +209 -209
- package/src/lib/tasks/test-runner.js +27 -6
- package/src/lib/validators/architecture/architecture-validator.js +259 -259
- package/src/lib/validators/checkpoint-persistence.js +39 -39
- package/src/lib/validators/contracts/contract-compliance-validator.js +274 -274
- package/src/lib/validators/css/css-validator.js +340 -340
- package/src/lib/validators/packages/package-validator.js +347 -347
- package/src/lib/validators/shared/emit-validator-dispatch.js +14 -14
- package/src/lib/validators/shared/issue-counter.js +14 -14
- package/src/lib/validators/validation-runner.js +349 -349
- package/src/lib/validators/verify-runner.js +322 -0
- package/src/lib/work-type-detector.js +143 -0
- package/src/scripts/setup-infra.js +32 -5
- package/src/utils/agent-id-mapper.js +11 -11
- package/src/utils/agents-installer.js +10 -6
- package/src/utils/claude-settings-manager.js +300 -300
- package/src/utils/file-copier.js +43 -0
- package/src/utils/hooks-installer.js +43 -34
- package/src/utils/skills-installer.js +174 -174
- package/framework/hooks/claude-code/core/subagent-report-guard.js +0 -95
- package/framework/templates/meta-prompts/parallel-workers/parallel-coordinator.md +0 -113
- package/framework/templates/meta-prompts/parallel-workers/parallel-worker.md +0 -80
- 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
|
+
);
|