@xdelivered/emberflow 0.2.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/LICENSE +21 -0
- package/README.md +153 -0
- package/bin/commands.test.ts +185 -0
- package/bin/commands.ts +306 -0
- package/bin/emberflow.mjs +38 -0
- package/bin/init.test.ts +165 -0
- package/bin/init.ts +297 -0
- package/bin/runtime.mjs +105 -0
- package/bin/runtime.test.ts +167 -0
- package/bin/skillTargets.test.ts +108 -0
- package/bin/skillTargets.ts +36 -0
- package/bin/skillTemplates.test.ts +37 -0
- package/bin/tsconfig.json +23 -0
- package/dist/bin/commands.d.ts +24 -0
- package/dist/bin/commands.js +252 -0
- package/dist/bin/init.d.ts +17 -0
- package/dist/bin/init.js +241 -0
- package/dist/bin/runtime.d.mts +39 -0
- package/dist/bin/runtime.mjs +96 -0
- package/dist/bin/skillTargets.d.ts +13 -0
- package/dist/bin/skillTargets.js +22 -0
- package/dist/server/agents/claudeAdapter.d.ts +8 -0
- package/dist/server/agents/claudeAdapter.js +190 -0
- package/dist/server/agents/claudeParse.d.ts +2 -0
- package/dist/server/agents/claudeParse.js +162 -0
- package/dist/server/agents/codexAdapter.d.ts +13 -0
- package/dist/server/agents/codexAdapter.js +174 -0
- package/dist/server/agents/codexParse.d.ts +2 -0
- package/dist/server/agents/codexParse.js +74 -0
- package/dist/server/agents/detect.d.ts +23 -0
- package/dist/server/agents/detect.js +35 -0
- package/dist/server/agents/gitScope.d.ts +24 -0
- package/dist/server/agents/gitScope.js +95 -0
- package/dist/server/agents/modelRejectionHint.d.ts +8 -0
- package/dist/server/agents/modelRejectionHint.js +12 -0
- package/dist/server/agents/prompt.d.ts +79 -0
- package/dist/server/agents/prompt.js +364 -0
- package/dist/server/agents/runManager.d.ts +90 -0
- package/dist/server/agents/runManager.js +259 -0
- package/dist/server/agents/types.d.ts +15 -0
- package/dist/server/agents/types.js +1 -0
- package/dist/server/apiStore.d.ts +60 -0
- package/dist/server/apiStore.js +249 -0
- package/dist/server/auth/enforce.d.ts +1 -0
- package/dist/server/auth/enforce.js +6 -0
- package/dist/server/auth/types.d.ts +1 -0
- package/dist/server/auth/types.js +1 -0
- package/dist/server/auth/verifiers.d.ts +1 -0
- package/dist/server/auth/verifiers.js +4 -0
- package/dist/server/authAttach.d.ts +19 -0
- package/dist/server/authAttach.js +79 -0
- package/dist/server/cli.d.ts +7 -0
- package/dist/server/cli.js +804 -0
- package/dist/server/client.d.ts +130 -0
- package/dist/server/client.js +247 -0
- package/dist/server/environments.d.ts +103 -0
- package/dist/server/environments.js +486 -0
- package/dist/server/flowStore.d.ts +37 -0
- package/dist/server/flowStore.js +117 -0
- package/dist/server/httpOperations.d.ts +32 -0
- package/dist/server/httpOperations.js +62 -0
- package/dist/server/index.d.ts +1 -0
- package/dist/server/index.js +1022 -0
- package/dist/server/infrastructure.d.ts +51 -0
- package/dist/server/infrastructure.js +116 -0
- package/dist/server/login.d.ts +21 -0
- package/dist/server/login.js +44 -0
- package/dist/server/mcp.d.ts +1 -0
- package/dist/server/mcp.js +182 -0
- package/dist/server/migrateFlows.d.ts +8 -0
- package/dist/server/migrateFlows.js +25 -0
- package/dist/server/mockHandler.d.ts +15 -0
- package/dist/server/mockHandler.js +35 -0
- package/dist/server/nodesPayload.d.ts +14 -0
- package/dist/server/nodesPayload.js +14 -0
- package/dist/server/normalizeFlow.d.ts +21 -0
- package/dist/server/normalizeFlow.js +48 -0
- package/dist/server/openBrowser.d.ts +8 -0
- package/dist/server/openBrowser.js +16 -0
- package/dist/server/operationResult.d.ts +12 -0
- package/dist/server/operationResult.js +26 -0
- package/dist/server/pathGuard.d.ts +1 -0
- package/dist/server/pathGuard.js +16 -0
- package/dist/server/pathSafety.d.ts +20 -0
- package/dist/server/pathSafety.js +35 -0
- package/dist/server/projectConfig.d.ts +47 -0
- package/dist/server/projectConfig.js +61 -0
- package/dist/server/projectMode.d.ts +26 -0
- package/dist/server/projectMode.js +44 -0
- package/dist/server/redact.d.ts +6 -0
- package/dist/server/redact.js +37 -0
- package/dist/server/runRegistry.d.ts +84 -0
- package/dist/server/runRegistry.js +200 -0
- package/dist/server/runtime/RuntimeAdapter.d.ts +22 -0
- package/dist/server/runtime/RuntimeAdapter.js +1 -0
- package/dist/server/runtime/expressAdapter.d.ts +19 -0
- package/dist/server/runtime/expressAdapter.js +56 -0
- package/dist/server/scenarioTest.d.ts +14 -0
- package/dist/server/scenarioTest.js +66 -0
- package/dist/server/subflowRunner.d.ts +32 -0
- package/dist/server/subflowRunner.js +72 -0
- package/dist/server/testRunner.d.ts +70 -0
- package/dist/server/testRunner.js +151 -0
- package/dist/src/config.d.ts +3 -0
- package/dist/src/config.js +2 -0
- package/dist/src/engine/authVerify.d.ts +33 -0
- package/dist/src/engine/authVerify.js +63 -0
- package/dist/src/engine/diagnostics.d.ts +62 -0
- package/dist/src/engine/diagnostics.js +148 -0
- package/dist/src/engine/executor.d.ts +151 -0
- package/dist/src/engine/executor.js +684 -0
- package/dist/src/engine/httpError.d.ts +7 -0
- package/dist/src/engine/httpError.js +14 -0
- package/dist/src/engine/index.d.ts +11 -0
- package/dist/src/engine/index.js +11 -0
- package/dist/src/engine/output.d.ts +11 -0
- package/dist/src/engine/output.js +20 -0
- package/dist/src/engine/publish.d.ts +30 -0
- package/dist/src/engine/publish.js +95 -0
- package/dist/src/engine/registry.d.ts +34 -0
- package/dist/src/engine/registry.js +67 -0
- package/dist/src/engine/scenarios.d.ts +12 -0
- package/dist/src/engine/scenarios.js +19 -0
- package/dist/src/engine/schemaCheck.d.ts +4 -0
- package/dist/src/engine/schemaCheck.js +34 -0
- package/dist/src/engine/trace.d.ts +12 -0
- package/dist/src/engine/trace.js +12 -0
- package/dist/src/engine/types.d.ts +308 -0
- package/dist/src/engine/types.js +1 -0
- package/dist/src/engine/validation.d.ts +26 -0
- package/dist/src/engine/validation.js +318 -0
- package/dist/src/flows/anomaly-flows.d.ts +23 -0
- package/dist/src/flows/anomaly-flows.js +151 -0
- package/dist/src/flows/city-sweep-flow.d.ts +10 -0
- package/dist/src/flows/city-sweep-flow.js +127 -0
- package/dist/src/flows/login-flow.d.ts +13 -0
- package/dist/src/flows/login-flow.js +145 -0
- package/dist/src/flows/pradar-flows.d.ts +5 -0
- package/dist/src/flows/pradar-flows.js +375 -0
- package/dist/src/flows/weather-flow.d.ts +12 -0
- package/dist/src/flows/weather-flow.js +150 -0
- package/dist/src/lib/pathParams.d.ts +3 -0
- package/dist/src/lib/pathParams.js +6 -0
- package/dist/src/nodes/anomaly.d.ts +2 -0
- package/dist/src/nodes/anomaly.js +556 -0
- package/dist/src/nodes/flow-control.d.ts +7 -0
- package/dist/src/nodes/flow-control.js +270 -0
- package/dist/src/nodes/index.d.ts +3 -0
- package/dist/src/nodes/index.js +20 -0
- package/dist/src/nodes/login.d.ts +7 -0
- package/dist/src/nodes/login.js +158 -0
- package/dist/src/nodes/loops.d.ts +11 -0
- package/dist/src/nodes/loops.js +60 -0
- package/dist/src/nodes/pradar/index.d.ts +7 -0
- package/dist/src/nodes/pradar/index.js +9 -0
- package/dist/src/nodes/pradar/logic.d.ts +189 -0
- package/dist/src/nodes/pradar/logic.js +370 -0
- package/dist/src/nodes/pradar/nodes.d.ts +42 -0
- package/dist/src/nodes/pradar/nodes.js +801 -0
- package/dist/src/nodes/requireAuth.d.ts +20 -0
- package/dist/src/nodes/requireAuth.js +48 -0
- package/dist/src/nodes/response.d.ts +8 -0
- package/dist/src/nodes/response.js +33 -0
- package/dist/src/nodes/weather.d.ts +33 -0
- package/dist/src/nodes/weather.js +260 -0
- package/package.json +92 -0
- package/server/agentRoute.test.ts +157 -0
- package/server/agents/__fixtures__/fake-claude-writer.mjs +23 -0
- package/server/agents/__fixtures__/fake-claude.mjs +55 -0
- package/server/agents/__fixtures__/fake-codex-writer.mjs +27 -0
- package/server/agents/__fixtures__/fake-codex.mjs +52 -0
- package/server/agents/__fixtures__/fake-version-empty.mjs +3 -0
- package/server/agents/__fixtures__/fake-version-fail.mjs +4 -0
- package/server/agents/__fixtures__/fake-version-garbage.mjs +4 -0
- package/server/agents/__fixtures__/fake-version-good.mjs +4 -0
- package/server/agents/__fixtures__/fake-version-hang.mjs +3 -0
- package/server/agents/claudeAdapter.test.ts +127 -0
- package/server/agents/claudeAdapter.ts +212 -0
- package/server/agents/claudeParse.ts +193 -0
- package/server/agents/codexAdapter.test.ts +125 -0
- package/server/agents/codexAdapter.ts +198 -0
- package/server/agents/codexParse.test.ts +58 -0
- package/server/agents/codexParse.ts +126 -0
- package/server/agents/detect.test.ts +65 -0
- package/server/agents/detect.ts +43 -0
- package/server/agents/gitScope.test.ts +129 -0
- package/server/agents/gitScope.ts +110 -0
- package/server/agents/modelRejectionHint.test.ts +30 -0
- package/server/agents/modelRejectionHint.ts +14 -0
- package/server/agents/prompt.test.ts +613 -0
- package/server/agents/prompt.ts +527 -0
- package/server/agents/runManager.test.ts +251 -0
- package/server/agents/runManager.ts +286 -0
- package/server/agents/types.ts +17 -0
- package/server/apiMount.test.ts +591 -0
- package/server/apiStore.test.ts +324 -0
- package/server/apiStore.ts +241 -0
- package/server/auth/enforce.test.ts +61 -0
- package/server/auth/enforce.ts +6 -0
- package/server/auth/types.ts +5 -0
- package/server/auth/verifiers.test.ts +37 -0
- package/server/auth/verifiers.ts +9 -0
- package/server/authAttach.test.ts +175 -0
- package/server/authAttach.ts +99 -0
- package/server/cli.ts +847 -0
- package/server/cliAuth.test.ts +229 -0
- package/server/cliCreate.test.ts +131 -0
- package/server/cliDoctor.test.ts +326 -0
- package/server/cliTest.test.ts +735 -0
- package/server/client.ts +314 -0
- package/server/demoProject.test.ts +29 -0
- package/server/diagnosticsRoute.test.ts +169 -0
- package/server/environments.test.ts +620 -0
- package/server/environments.ts +603 -0
- package/server/environmentsRoute.test.ts +385 -0
- package/server/errorWorkflow.test.ts +205 -0
- package/server/flowStore.test.ts +84 -0
- package/server/flowStore.ts +125 -0
- package/server/httpOperations.test.ts +377 -0
- package/server/httpOperations.ts +96 -0
- package/server/index.ts +1111 -0
- package/server/infrastructure.test.ts +154 -0
- package/server/infrastructure.ts +161 -0
- package/server/infrastructureRoute.test.ts +105 -0
- package/server/login.test.ts +153 -0
- package/server/login.ts +67 -0
- package/server/mcp.ts +278 -0
- package/server/migrateFlows.test.ts +55 -0
- package/server/migrateFlows.ts +23 -0
- package/server/mockHandler.test.ts +138 -0
- package/server/mockHandler.ts +52 -0
- package/server/mockRun.test.ts +367 -0
- package/server/mockServe.test.ts +467 -0
- package/server/nodesPayload.test.ts +29 -0
- package/server/nodesPayload.ts +26 -0
- package/server/normalizeFlow.test.ts +80 -0
- package/server/normalizeFlow.ts +59 -0
- package/server/openBrowser.test.ts +20 -0
- package/server/openBrowser.ts +22 -0
- package/server/operationResult.test.ts +67 -0
- package/server/operationResult.ts +27 -0
- package/server/pathGuard.test.ts +19 -0
- package/server/pathGuard.ts +12 -0
- package/server/pathSafety.test.ts +38 -0
- package/server/pathSafety.ts +33 -0
- package/server/projectConfig.test.ts +67 -0
- package/server/projectConfig.ts +101 -0
- package/server/projectMode.test.ts +106 -0
- package/server/projectMode.ts +59 -0
- package/server/redact.test.ts +88 -0
- package/server/redact.ts +35 -0
- package/server/redactEgress.test.ts +281 -0
- package/server/runRegistry.ts +270 -0
- package/server/runsAuth.test.ts +119 -0
- package/server/runtime/RuntimeAdapter.ts +24 -0
- package/server/runtime/expressAdapter.test.ts +38 -0
- package/server/runtime/expressAdapter.ts +70 -0
- package/server/scenarioTest.test.ts +148 -0
- package/server/scenarioTest.ts +88 -0
- package/server/setupStatus.test.ts +127 -0
- package/server/subflowRunner.ts +104 -0
- package/server/testRunner.ts +216 -0
- package/server/tsconfig.json +20 -0
- package/server/workflowTestRoute.test.ts +297 -0
- package/src/App.tsx +233 -0
- package/src/components/AgentConsole.tsx +461 -0
- package/src/components/BranchRulesEditor.tsx +156 -0
- package/src/components/CreateModal.tsx +275 -0
- package/src/components/Dock.tsx +487 -0
- package/src/components/EnvironmentDialog.tsx +569 -0
- package/src/components/EnvironmentPicker.tsx +357 -0
- package/src/components/EnvironmentsDialog.tsx +144 -0
- package/src/components/ExecutionPager.tsx +67 -0
- package/src/components/InfraPanel.test.tsx +103 -0
- package/src/components/InfraPanel.tsx +171 -0
- package/src/components/Inspector.tsx +1182 -0
- package/src/components/Json.tsx +50 -0
- package/src/components/JsonModal.tsx +52 -0
- package/src/components/LogMessage.tsx +61 -0
- package/src/components/NodeRunModal.tsx +213 -0
- package/src/components/RunLogPanel.tsx +397 -0
- package/src/components/RunbookView.tsx +1085 -0
- package/src/components/Scenarios.tsx +476 -0
- package/src/components/SettingsDialog.tsx +229 -0
- package/src/components/Sidebar.tsx +436 -0
- package/src/components/StatusBar.tsx +302 -0
- package/src/components/Toolbar.tsx +663 -0
- package/src/components/WelcomeDialog.test.tsx +107 -0
- package/src/components/WelcomeDialog.tsx +412 -0
- package/src/components/agentMarkdown.test.tsx +91 -0
- package/src/components/agentMarkdown.tsx +158 -0
- package/src/components/ui/badge.tsx +31 -0
- package/src/components/ui/button.tsx +46 -0
- package/src/components/ui/combobox.tsx +114 -0
- package/src/components/ui/command.tsx +82 -0
- package/src/components/ui/dialog.tsx +73 -0
- package/src/components/ui/input.tsx +20 -0
- package/src/components/ui/popover.tsx +32 -0
- package/src/components/ui/resizable.tsx +44 -0
- package/src/components/ui/tabs.tsx +36 -0
- package/src/config.ts +4 -0
- package/src/engine/authVerify.ts +81 -0
- package/src/engine/diagnostics.test.ts +437 -0
- package/src/engine/diagnostics.ts +211 -0
- package/src/engine/executor.test.ts +1339 -0
- package/src/engine/executor.ts +840 -0
- package/src/engine/httpError.test.ts +12 -0
- package/src/engine/httpError.ts +16 -0
- package/src/engine/index.ts +11 -0
- package/src/engine/output.ts +21 -0
- package/src/engine/publish.test.ts +176 -0
- package/src/engine/publish.ts +127 -0
- package/src/engine/registry.test.ts +64 -0
- package/src/engine/registry.ts +80 -0
- package/src/engine/scenarios.test.ts +55 -0
- package/src/engine/scenarios.ts +29 -0
- package/src/engine/schemaCheck.test.ts +21 -0
- package/src/engine/schemaCheck.ts +35 -0
- package/src/engine/trace.test.ts +19 -0
- package/src/engine/trace.ts +23 -0
- package/src/engine/types.test.ts +22 -0
- package/src/engine/types.ts +316 -0
- package/src/engine/validation.test.ts +314 -0
- package/src/engine/validation.ts +354 -0
- package/src/flows/anomaly-flows.test.ts +81 -0
- package/src/flows/anomaly-flows.ts +202 -0
- package/src/flows/city-sweep-flow.ts +131 -0
- package/src/flows/flows.test.ts +30 -0
- package/src/flows/login-flow.ts +150 -0
- package/src/flows/pradar-flows.test.ts +204 -0
- package/src/flows/pradar-flows.ts +419 -0
- package/src/flows/weather-flow.ts +154 -0
- package/src/globals.css +147 -0
- package/src/lib/pathParams.test.ts +24 -0
- package/src/lib/pathParams.ts +6 -0
- package/src/lib/registerLens.test.ts +79 -0
- package/src/lib/registerLens.ts +59 -0
- package/src/lib/runbookModel.test.ts +61 -0
- package/src/lib/runbookModel.ts +290 -0
- package/src/lib/runbookProjection.test.ts +417 -0
- package/src/lib/runbookProjection.ts +254 -0
- package/src/lib/useTailAnchor.ts +53 -0
- package/src/lib/utils.ts +7 -0
- package/src/main.tsx +13 -0
- package/src/nodes/anomaly.test.ts +98 -0
- package/src/nodes/anomaly.ts +637 -0
- package/src/nodes/effects-scan.test.ts +44 -0
- package/src/nodes/flow-control.test.ts +280 -0
- package/src/nodes/flow-control.ts +305 -0
- package/src/nodes/index.ts +22 -0
- package/src/nodes/login.test.ts +81 -0
- package/src/nodes/login.ts +186 -0
- package/src/nodes/loops.ts +69 -0
- package/src/nodes/pradar/index.ts +11 -0
- package/src/nodes/pradar/logic.test.ts +288 -0
- package/src/nodes/pradar/logic.ts +582 -0
- package/src/nodes/pradar/nodes.test.ts +146 -0
- package/src/nodes/pradar/nodes.ts +955 -0
- package/src/nodes/requireAuth.test.ts +75 -0
- package/src/nodes/requireAuth.ts +55 -0
- package/src/nodes/response.test.ts +33 -0
- package/src/nodes/response.ts +39 -0
- package/src/nodes/traceDetail.test.ts +39 -0
- package/src/nodes/weather.test.ts +65 -0
- package/src/nodes/weather.ts +306 -0
- package/src/store/agentClient.test.ts +103 -0
- package/src/store/agentClient.ts +131 -0
- package/src/store/apiTree.test.ts +17 -0
- package/src/store/apiTree.ts +73 -0
- package/src/store/builderStore.test.ts +1234 -0
- package/src/store/builderStore.ts +1879 -0
- package/src/store/infraClient.ts +63 -0
- package/src/store/nodeMeta.test.ts +24 -0
- package/src/store/nodeMeta.ts +21 -0
- package/src/store/persistence.test.ts +15 -0
- package/src/store/persistence.ts +54 -0
- package/src/store/serverRunner.ts +381 -0
- package/src/store/setupClient.ts +57 -0
- package/studio-dist/assets/ibm-plex-mono-cyrillic-400-normal-BSMlKf0J.woff2 +0 -0
- package/studio-dist/assets/ibm-plex-mono-cyrillic-400-normal-CEL4l2ZJ.woff +0 -0
- package/studio-dist/assets/ibm-plex-mono-cyrillic-500-normal-Ael50iVv.woff +0 -0
- package/studio-dist/assets/ibm-plex-mono-cyrillic-500-normal-Bq9vWWag.woff2 +0 -0
- package/studio-dist/assets/ibm-plex-mono-cyrillic-ext-400-normal-DMdlQ8Kv.woff +0 -0
- package/studio-dist/assets/ibm-plex-mono-cyrillic-ext-400-normal-xuaO2J-f.woff2 +0 -0
- package/studio-dist/assets/ibm-plex-mono-cyrillic-ext-500-normal-BIfNGwUT.woff +0 -0
- package/studio-dist/assets/ibm-plex-mono-cyrillic-ext-500-normal-BqneJy0T.woff2 +0 -0
- package/studio-dist/assets/ibm-plex-mono-latin-400-normal-CvHOgSBP.woff +0 -0
- package/studio-dist/assets/ibm-plex-mono-latin-400-normal-DMJ8VG8y.woff2 +0 -0
- package/studio-dist/assets/ibm-plex-mono-latin-500-normal-CB9ihrfo.woff +0 -0
- package/studio-dist/assets/ibm-plex-mono-latin-500-normal-DSY6xOcd.woff2 +0 -0
- package/studio-dist/assets/ibm-plex-mono-latin-ext-400-normal-BmRBH3aV.woff2 +0 -0
- package/studio-dist/assets/ibm-plex-mono-latin-ext-400-normal-D3D2R8hC.woff +0 -0
- package/studio-dist/assets/ibm-plex-mono-latin-ext-500-normal-CAhNIIs5.woff2 +0 -0
- package/studio-dist/assets/ibm-plex-mono-latin-ext-500-normal-CZ70TYgx.woff +0 -0
- package/studio-dist/assets/ibm-plex-mono-vietnamese-400-normal-BulugwFq.woff2 +0 -0
- package/studio-dist/assets/ibm-plex-mono-vietnamese-400-normal-DDuiU_S-.woff +0 -0
- package/studio-dist/assets/ibm-plex-mono-vietnamese-500-normal-C8zxqsMH.woff +0 -0
- package/studio-dist/assets/ibm-plex-mono-vietnamese-500-normal-DZ4AoWbu.woff2 +0 -0
- package/studio-dist/assets/index-DNJwW-hM.css +1 -0
- package/studio-dist/assets/index-O26dKRjW.js +65 -0
- package/studio-dist/assets/inter-cyrillic-ext-wght-normal-BOeWTOD4.woff2 +0 -0
- package/studio-dist/assets/inter-cyrillic-wght-normal-DqGufNeO.woff2 +0 -0
- package/studio-dist/assets/inter-greek-ext-wght-normal-DlzME5K_.woff2 +0 -0
- package/studio-dist/assets/inter-greek-wght-normal-CkhJZR-_.woff2 +0 -0
- package/studio-dist/assets/inter-latin-ext-wght-normal-DO1Apj_S.woff2 +0 -0
- package/studio-dist/assets/inter-latin-wght-normal-Dx4kXJAl.woff2 +0 -0
- package/studio-dist/assets/inter-vietnamese-wght-normal-CBcvBZtf.woff2 +0 -0
- package/studio-dist/favicon.svg +1 -0
- package/studio-dist/icons.svg +24 -0
- package/studio-dist/index.html +14 -0
- package/templates/skills/emberflow-basics/SKILL.md +316 -0
- package/templates/skills/emberflow-model-process/SKILL.md +209 -0
- package/templates/skills/emberflow-new-workflow/SKILL.md +211 -0
- package/templates/skills/emberflow-review-workflow/SKILL.md +148 -0
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { renderToStaticMarkup } from 'react-dom/server';
|
|
3
|
+
import { WelcomeChecklist } from './WelcomeDialog';
|
|
4
|
+
import type { SetupStatus } from '../store/setupClient';
|
|
5
|
+
|
|
6
|
+
/** A pristine, just-initialized project: no agent, no environments, no skills,
|
|
7
|
+
* only the hello example op. */
|
|
8
|
+
const FRESH: SetupStatus = {
|
|
9
|
+
agents: [],
|
|
10
|
+
environments: { configured: false, count: 0, protectedCount: 0, anyAuthConfigured: false },
|
|
11
|
+
skills: { claude: false, codex: false },
|
|
12
|
+
language: 'typescript',
|
|
13
|
+
ops: { count: 1, onlyHello: true },
|
|
14
|
+
servingMode: 'mock',
|
|
15
|
+
infrastructure: { present: false },
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
/** A project that's been set up: agent on PATH, environments + auth, skills
|
|
19
|
+
* installed, infrastructure scouted, several ops. */
|
|
20
|
+
const CONFIGURED: SetupStatus = {
|
|
21
|
+
agents: [
|
|
22
|
+
{ kind: 'claude', version: '2.1.0' },
|
|
23
|
+
{ kind: 'codex', version: '0.5.3' },
|
|
24
|
+
],
|
|
25
|
+
environments: { configured: true, count: 2, protectedCount: 1, anyAuthConfigured: true },
|
|
26
|
+
skills: { claude: true, codex: false },
|
|
27
|
+
language: 'typescript',
|
|
28
|
+
ops: { count: 4, onlyHello: false },
|
|
29
|
+
servingMode: 'real',
|
|
30
|
+
infrastructure: { present: true, itemCount: 3 },
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const html = (status: SetupStatus): string =>
|
|
34
|
+
renderToStaticMarkup(<WelcomeChecklist status={status} />);
|
|
35
|
+
|
|
36
|
+
describe('WelcomeChecklist', () => {
|
|
37
|
+
it('renders every checklist row', () => {
|
|
38
|
+
const out = html(FRESH);
|
|
39
|
+
for (const title of [
|
|
40
|
+
'Coding agent detected',
|
|
41
|
+
'Environments configured',
|
|
42
|
+
'Secrets & auth',
|
|
43
|
+
'Agent skills installed',
|
|
44
|
+
'Infrastructure scouted',
|
|
45
|
+
'First operation',
|
|
46
|
+
]) {
|
|
47
|
+
expect(out).toContain(title);
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('fresh project: shows the install hint and no completed rows', () => {
|
|
52
|
+
const out = html(FRESH);
|
|
53
|
+
// Skills-missing hint is the copyable init command, no action button.
|
|
54
|
+
expect(out).toContain('npx emberflow init --local --no-launch');
|
|
55
|
+
expect(out).toContain('None detected on PATH');
|
|
56
|
+
// Nothing is done yet — no "done" glyph.
|
|
57
|
+
expect(out).not.toContain('aria-label="done"');
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('fresh project: the scout button is disabled without an agent', () => {
|
|
61
|
+
const out = html(FRESH);
|
|
62
|
+
// The Scout button carries the "detect an agent first" tooltip and is disabled.
|
|
63
|
+
expect(out).toContain('Detect a coding agent first');
|
|
64
|
+
expect(out).toMatch(/Scout/);
|
|
65
|
+
expect(out).toContain('disabled');
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('configured project: shows completed rows and agent versions', () => {
|
|
69
|
+
const out = html(CONFIGURED);
|
|
70
|
+
expect(out).toContain('aria-label="done"');
|
|
71
|
+
expect(out).toContain('claude 2.1.0');
|
|
72
|
+
expect(out).toContain('codex 0.5.3');
|
|
73
|
+
expect(out).toContain('2 environments, 1 protected');
|
|
74
|
+
// Skills present → no install hint.
|
|
75
|
+
expect(out).not.toContain('npx emberflow init');
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('configured project: scout is enabled (agent present)', () => {
|
|
79
|
+
const out = html(CONFIGURED);
|
|
80
|
+
expect(out).not.toContain('Detect a coding agent first');
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('two agents + onChooseAgent: renders the picker with the chosen agent pressed', () => {
|
|
84
|
+
const out = renderToStaticMarkup(
|
|
85
|
+
<WelcomeChecklist status={CONFIGURED} chosenAgent="codex" onChooseAgent={() => {}} />,
|
|
86
|
+
);
|
|
87
|
+
expect(out).toContain('Choose coding agent');
|
|
88
|
+
expect(out).toContain('Both detected');
|
|
89
|
+
// Codex is the active choice; Claude is not.
|
|
90
|
+
expect(out).toMatch(/aria-pressed="true"[^>]*>Codex/);
|
|
91
|
+
expect(out).toMatch(/aria-pressed="false"[^>]*>Claude/);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('single agent: no picker even when onChooseAgent is provided', () => {
|
|
95
|
+
const oneAgent: SetupStatus = { ...CONFIGURED, agents: [{ kind: 'claude', version: '2.1.0' }] };
|
|
96
|
+
const out = renderToStaticMarkup(
|
|
97
|
+
<WelcomeChecklist status={oneAgent} onChooseAgent={() => {}} />,
|
|
98
|
+
);
|
|
99
|
+
expect(out).not.toContain('Choose coding agent');
|
|
100
|
+
expect(out).toContain('claude 2.1.0');
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('shows checklist progress', () => {
|
|
104
|
+
expect(html(FRESH)).toContain('0/6 done');
|
|
105
|
+
expect(html(CONFIGURED)).toContain('6/6 done');
|
|
106
|
+
});
|
|
107
|
+
});
|
|
@@ -0,0 +1,412 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
CheckCircle2Icon,
|
|
4
|
+
CheckIcon,
|
|
5
|
+
CircleIcon,
|
|
6
|
+
CopyIcon,
|
|
7
|
+
MinusCircleIcon,
|
|
8
|
+
SparklesIcon,
|
|
9
|
+
} from 'lucide-react';
|
|
10
|
+
import { cn } from '@/lib/utils';
|
|
11
|
+
import { Button } from '@/components/ui/button';
|
|
12
|
+
import { Dialog, DialogContent, DialogDescription, DialogTitle } from '@/components/ui/dialog';
|
|
13
|
+
import { EnvironmentsDialog } from './EnvironmentsDialog';
|
|
14
|
+
import { useBuilderStore } from '../store/builderStore';
|
|
15
|
+
import { setupProgress, type SetupStatus } from '../store/setupClient';
|
|
16
|
+
import type { AgentKind } from '../store/agentClient';
|
|
17
|
+
|
|
18
|
+
const AGENT_LABELS: Record<AgentKind, string> = {
|
|
19
|
+
codex: 'Codex',
|
|
20
|
+
claude: 'Claude',
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const WELCOME_DISMISSED_KEY = 'emberflow.welcome.dismissed';
|
|
24
|
+
/** Re-run this exact command to (re)install the agent skills only. */
|
|
25
|
+
const SKILLS_INSTALL_COMMAND = 'npx emberflow init --local --no-launch';
|
|
26
|
+
|
|
27
|
+
type RowStatus = 'complete' | 'incomplete' | 'unavailable';
|
|
28
|
+
|
|
29
|
+
function StatusGlyph({ status }: { status: RowStatus }) {
|
|
30
|
+
if (status === 'complete') {
|
|
31
|
+
return <CheckCircle2Icon className="mt-0.5 size-4 shrink-0 text-success" aria-label="done" />;
|
|
32
|
+
}
|
|
33
|
+
if (status === 'unavailable') {
|
|
34
|
+
return (
|
|
35
|
+
<MinusCircleIcon
|
|
36
|
+
className="mt-0.5 size-4 shrink-0 text-muted-foreground/30"
|
|
37
|
+
aria-label="not yet available"
|
|
38
|
+
/>
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
return (
|
|
42
|
+
<CircleIcon
|
|
43
|
+
className="mt-0.5 size-4 shrink-0 text-muted-foreground/50"
|
|
44
|
+
aria-label="to do"
|
|
45
|
+
/>
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function SectionTitle({ children }: { children: React.ReactNode }) {
|
|
50
|
+
return (
|
|
51
|
+
<div className="text-[10px] font-medium uppercase tracking-widest text-muted-foreground">
|
|
52
|
+
{children}
|
|
53
|
+
</div>
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function ChecklistRow({
|
|
58
|
+
status,
|
|
59
|
+
title,
|
|
60
|
+
detail,
|
|
61
|
+
action,
|
|
62
|
+
children,
|
|
63
|
+
}: {
|
|
64
|
+
status: RowStatus;
|
|
65
|
+
title: string;
|
|
66
|
+
detail: string;
|
|
67
|
+
action?: React.ReactNode;
|
|
68
|
+
children?: React.ReactNode;
|
|
69
|
+
}) {
|
|
70
|
+
return (
|
|
71
|
+
<div className="flex items-start gap-2.5 rounded-md border border-border/70 px-2.5 py-2">
|
|
72
|
+
<StatusGlyph status={status} />
|
|
73
|
+
<div className="flex min-w-0 flex-1 flex-col gap-1">
|
|
74
|
+
<span className="text-[12.5px] font-medium leading-tight">{title}</span>
|
|
75
|
+
<span className="text-[11px] leading-snug text-muted-foreground">{detail}</span>
|
|
76
|
+
{children}
|
|
77
|
+
</div>
|
|
78
|
+
{action && <div className="shrink-0 self-center">{action}</div>}
|
|
79
|
+
</div>
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function CopyCommand({ command }: { command: string }) {
|
|
84
|
+
const [copied, setCopied] = useState(false);
|
|
85
|
+
return (
|
|
86
|
+
<button
|
|
87
|
+
type="button"
|
|
88
|
+
onClick={() => {
|
|
89
|
+
void navigator.clipboard?.writeText(command);
|
|
90
|
+
setCopied(true);
|
|
91
|
+
setTimeout(() => setCopied(false), 1500);
|
|
92
|
+
}}
|
|
93
|
+
title="Copy to clipboard"
|
|
94
|
+
className="mt-0.5 flex items-center gap-2 self-start rounded-md border border-border bg-secondary/40 px-2 py-1 font-mono text-[11px] text-foreground/90 transition-colors hover:bg-secondary"
|
|
95
|
+
>
|
|
96
|
+
<code>{command}</code>
|
|
97
|
+
{copied ? (
|
|
98
|
+
<CheckIcon className="size-3 text-success" />
|
|
99
|
+
) : (
|
|
100
|
+
<CopyIcon className="size-3 text-muted-foreground" />
|
|
101
|
+
)}
|
|
102
|
+
</button>
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export interface WelcomeChecklistActions {
|
|
107
|
+
onOpenSettings: () => void;
|
|
108
|
+
onSetupEnvironments: () => void;
|
|
109
|
+
onOpenEnvironments: () => void;
|
|
110
|
+
onOpenHelloOp: () => void;
|
|
111
|
+
onScoutInfrastructure: () => void;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const NOOP_ACTIONS: WelcomeChecklistActions = {
|
|
115
|
+
onOpenSettings: () => {},
|
|
116
|
+
onSetupEnvironments: () => {},
|
|
117
|
+
onOpenEnvironments: () => {},
|
|
118
|
+
onOpenHelloOp: () => {},
|
|
119
|
+
onScoutInfrastructure: () => {},
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Presentational checklist rendered from a resolved `/setup-status`. Split out
|
|
124
|
+
* from the dialog wrapper (which owns fetch/open state) so it renders purely
|
|
125
|
+
* from props — the component test drives it across fresh/configured/no-agent
|
|
126
|
+
* states without a live runner.
|
|
127
|
+
*/
|
|
128
|
+
export function WelcomeChecklist({
|
|
129
|
+
status,
|
|
130
|
+
actions = NOOP_ACTIONS,
|
|
131
|
+
chosenAgent,
|
|
132
|
+
onChooseAgent,
|
|
133
|
+
}: {
|
|
134
|
+
status: SetupStatus;
|
|
135
|
+
actions?: WelcomeChecklistActions;
|
|
136
|
+
/** The user's preferred agent (from Settings); defaults to the first detected. */
|
|
137
|
+
chosenAgent?: AgentKind;
|
|
138
|
+
onChooseAgent?: (kind: AgentKind) => void;
|
|
139
|
+
}) {
|
|
140
|
+
const { agents, environments, skills, ops, infrastructure } = status;
|
|
141
|
+
const hasAgent = agents.length > 0;
|
|
142
|
+
const skillsInstalled = skills.claude || skills.codex;
|
|
143
|
+
// With more than one agent CLI on PATH the row becomes a choice, not a fact.
|
|
144
|
+
const multipleAgents = agents.length > 1 && onChooseAgent !== undefined;
|
|
145
|
+
const activeAgent = chosenAgent ?? agents[0]?.kind;
|
|
146
|
+
|
|
147
|
+
const agentDetail = hasAgent
|
|
148
|
+
? multipleAgents
|
|
149
|
+
? 'Both detected — pick which one builds for you (changeable in Settings).'
|
|
150
|
+
: agents.map((a) => `${a.kind}${a.version ? ` ${a.version}` : ''}`).join(', ')
|
|
151
|
+
: 'None detected on PATH — install codex or claude to let the agent build for you.';
|
|
152
|
+
|
|
153
|
+
const envDetail = environments.configured
|
|
154
|
+
? `${environments.count} environment${environments.count === 1 ? '' : 's'}` +
|
|
155
|
+
(environments.protectedCount > 0 ? `, ${environments.protectedCount} protected` : '')
|
|
156
|
+
: "You're in Mock — nothing real is touched. Set up environments to point runs at real systems.";
|
|
157
|
+
|
|
158
|
+
const secretsStatus: RowStatus = !environments.configured
|
|
159
|
+
? 'unavailable'
|
|
160
|
+
: environments.anyAuthConfigured
|
|
161
|
+
? 'complete'
|
|
162
|
+
: 'incomplete';
|
|
163
|
+
const secretsDetail = !environments.configured
|
|
164
|
+
? 'Configure environments first — secrets and auth live per environment.'
|
|
165
|
+
: environments.anyAuthConfigured
|
|
166
|
+
? 'Auth configured on at least one environment.'
|
|
167
|
+
: 'Add the secrets and login your operations need.';
|
|
168
|
+
|
|
169
|
+
const progress = setupProgress(status);
|
|
170
|
+
|
|
171
|
+
return (
|
|
172
|
+
<div className="space-y-2.5">
|
|
173
|
+
<div className="flex items-baseline justify-between">
|
|
174
|
+
<SectionTitle>Setup checklist</SectionTitle>
|
|
175
|
+
<span className="text-[10.5px] tabular-nums text-muted-foreground">
|
|
176
|
+
{progress.done}/{progress.total} done
|
|
177
|
+
</span>
|
|
178
|
+
</div>
|
|
179
|
+
<div className="flex flex-col gap-1.5">
|
|
180
|
+
<ChecklistRow
|
|
181
|
+
status={hasAgent ? 'complete' : 'incomplete'}
|
|
182
|
+
title="Coding agent detected"
|
|
183
|
+
detail={agentDetail}
|
|
184
|
+
action={
|
|
185
|
+
<Button variant="outline" size="sm" onClick={actions.onOpenSettings}>
|
|
186
|
+
Settings
|
|
187
|
+
</Button>
|
|
188
|
+
}
|
|
189
|
+
>
|
|
190
|
+
{multipleAgents && (
|
|
191
|
+
<div className="mt-0.5 flex gap-1 self-start" role="group" aria-label="Choose coding agent">
|
|
192
|
+
{agents.map(({ kind, version }) => (
|
|
193
|
+
<button
|
|
194
|
+
key={kind}
|
|
195
|
+
type="button"
|
|
196
|
+
onClick={() => onChooseAgent(kind)}
|
|
197
|
+
aria-pressed={activeAgent === kind}
|
|
198
|
+
className={cn(
|
|
199
|
+
'cursor-pointer rounded-md border px-2 py-1 text-[11.5px] transition-colors',
|
|
200
|
+
activeAgent === kind
|
|
201
|
+
? 'border-ring bg-secondary/60 font-medium text-foreground'
|
|
202
|
+
: 'border-border text-muted-foreground hover:text-foreground',
|
|
203
|
+
)}
|
|
204
|
+
>
|
|
205
|
+
{AGENT_LABELS[kind]}
|
|
206
|
+
{version ? <span className="ml-1 font-normal text-muted-foreground">{version}</span> : null}
|
|
207
|
+
</button>
|
|
208
|
+
))}
|
|
209
|
+
</div>
|
|
210
|
+
)}
|
|
211
|
+
</ChecklistRow>
|
|
212
|
+
|
|
213
|
+
<ChecklistRow
|
|
214
|
+
status={environments.configured ? 'complete' : 'incomplete'}
|
|
215
|
+
title="Environments configured"
|
|
216
|
+
detail={envDetail}
|
|
217
|
+
action={
|
|
218
|
+
<div className="flex flex-col items-end gap-1">
|
|
219
|
+
<Button size="sm" onClick={actions.onSetupEnvironments}>
|
|
220
|
+
<SparklesIcon className="size-3.5" />
|
|
221
|
+
Set up with AI
|
|
222
|
+
</Button>
|
|
223
|
+
<button
|
|
224
|
+
type="button"
|
|
225
|
+
onClick={actions.onOpenEnvironments}
|
|
226
|
+
className="text-[10.5px] text-muted-foreground underline-offset-2 hover:text-foreground hover:underline"
|
|
227
|
+
>
|
|
228
|
+
Manage manually
|
|
229
|
+
</button>
|
|
230
|
+
</div>
|
|
231
|
+
}
|
|
232
|
+
/>
|
|
233
|
+
|
|
234
|
+
<ChecklistRow
|
|
235
|
+
status={secretsStatus}
|
|
236
|
+
title="Secrets & auth"
|
|
237
|
+
detail={secretsDetail}
|
|
238
|
+
action={
|
|
239
|
+
<Button
|
|
240
|
+
variant="outline"
|
|
241
|
+
size="sm"
|
|
242
|
+
disabled={!environments.configured}
|
|
243
|
+
onClick={actions.onOpenEnvironments}
|
|
244
|
+
>
|
|
245
|
+
Open
|
|
246
|
+
</Button>
|
|
247
|
+
}
|
|
248
|
+
/>
|
|
249
|
+
|
|
250
|
+
<ChecklistRow
|
|
251
|
+
status={skillsInstalled ? 'complete' : 'incomplete'}
|
|
252
|
+
title="Agent skills installed"
|
|
253
|
+
detail={
|
|
254
|
+
skillsInstalled
|
|
255
|
+
? 'emberflow-basics is installed for your agent.'
|
|
256
|
+
: 'Installed automatically by emberflow init — this project doesn’t have them yet. Add them (skills only, nothing else changes):'
|
|
257
|
+
}
|
|
258
|
+
>
|
|
259
|
+
{!skillsInstalled && <CopyCommand command={SKILLS_INSTALL_COMMAND} />}
|
|
260
|
+
</ChecklistRow>
|
|
261
|
+
|
|
262
|
+
<ChecklistRow
|
|
263
|
+
status={infrastructure.present ? 'complete' : 'incomplete'}
|
|
264
|
+
title="Infrastructure scouted"
|
|
265
|
+
detail={
|
|
266
|
+
infrastructure.present
|
|
267
|
+
? `${infrastructure.itemCount ?? 0} item${infrastructure.itemCount === 1 ? '' : 's'} discovered.`
|
|
268
|
+
: 'Have the agent scan this project for databases, APIs and providers it already uses.'
|
|
269
|
+
}
|
|
270
|
+
action={
|
|
271
|
+
<Button
|
|
272
|
+
variant="outline"
|
|
273
|
+
size="sm"
|
|
274
|
+
disabled={!hasAgent}
|
|
275
|
+
title={hasAgent ? undefined : 'Detect a coding agent first'}
|
|
276
|
+
onClick={actions.onScoutInfrastructure}
|
|
277
|
+
>
|
|
278
|
+
<SparklesIcon className="size-3.5" />
|
|
279
|
+
Scout
|
|
280
|
+
</Button>
|
|
281
|
+
}
|
|
282
|
+
/>
|
|
283
|
+
|
|
284
|
+
<ChecklistRow
|
|
285
|
+
status={ops.count > 1 ? 'complete' : 'incomplete'}
|
|
286
|
+
title="First operation"
|
|
287
|
+
detail={
|
|
288
|
+
ops.count > 1
|
|
289
|
+
? "You've built beyond the hello example."
|
|
290
|
+
: 'Only the hello example so far — open it to see how an operation is built.'
|
|
291
|
+
}
|
|
292
|
+
action={
|
|
293
|
+
<Button variant="outline" size="sm" onClick={actions.onOpenHelloOp}>
|
|
294
|
+
Open hello op
|
|
295
|
+
</Button>
|
|
296
|
+
}
|
|
297
|
+
/>
|
|
298
|
+
</div>
|
|
299
|
+
</div>
|
|
300
|
+
);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* First-run Welcome/Setup dialog. Mounted once (in the Toolbar). Fetches
|
|
305
|
+
* `/setup-status` on mount to decide whether to auto-open on a fresh project,
|
|
306
|
+
* and again whenever it's opened so the rows reflect the latest state. Always
|
|
307
|
+
* reachable from the Toolbar's Setup button (via the store's `welcomeOpen`).
|
|
308
|
+
*/
|
|
309
|
+
export function WelcomeDialog() {
|
|
310
|
+
const welcomeOpen = useBuilderStore((s) => s.welcomeOpen);
|
|
311
|
+
const setWelcomeOpen = useBuilderStore((s) => s.setWelcomeOpen);
|
|
312
|
+
const openSettingsFromWelcome = useBuilderStore((s) => s.openSettingsFromWelcome);
|
|
313
|
+
const beginEnvironmentSetup = useBuilderStore((s) => s.beginEnvironmentSetup);
|
|
314
|
+
const beginInfrastructureScout = useBuilderStore((s) => s.beginInfrastructureScout);
|
|
315
|
+
const switchWorkflow = useBuilderStore((s) => s.switchWorkflow);
|
|
316
|
+
const status = useBuilderStore((s) => s.setupStatus);
|
|
317
|
+
const refreshSetupStatus = useBuilderStore((s) => s.refreshSetupStatus);
|
|
318
|
+
const agentChoice = useBuilderStore((s) => s.agentChoice);
|
|
319
|
+
const setAgentChoice = useBuilderStore((s) => s.setAgentChoice);
|
|
320
|
+
const [envDialogOpen, setEnvDialogOpen] = useState(false);
|
|
321
|
+
|
|
322
|
+
// Mount: fetch once and auto-open on a fresh, undismissed project.
|
|
323
|
+
useEffect(() => {
|
|
324
|
+
let cancelled = false;
|
|
325
|
+
void refreshSetupStatus().then((s) => {
|
|
326
|
+
if (cancelled || !s) return;
|
|
327
|
+
const dismissed =
|
|
328
|
+
typeof localStorage !== 'undefined' && localStorage.getItem(WELCOME_DISMISSED_KEY) === '1';
|
|
329
|
+
if (s.ops.onlyHello && !s.environments.configured && !dismissed) {
|
|
330
|
+
setWelcomeOpen(true);
|
|
331
|
+
}
|
|
332
|
+
});
|
|
333
|
+
return () => {
|
|
334
|
+
cancelled = true;
|
|
335
|
+
};
|
|
336
|
+
// Mount-only: auto-open is a one-shot boot decision.
|
|
337
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
338
|
+
}, []);
|
|
339
|
+
|
|
340
|
+
// Refresh the rows every time the dialog opens (e.g. from the StatusBar chip).
|
|
341
|
+
useEffect(() => {
|
|
342
|
+
if (!welcomeOpen) return;
|
|
343
|
+
void refreshSetupStatus();
|
|
344
|
+
}, [welcomeOpen, refreshSetupStatus]);
|
|
345
|
+
|
|
346
|
+
/** A user-driven close (X / overlay / esc / Dismiss) records the dismissal so
|
|
347
|
+
* the dialog never auto-opens again; navigational actions close without it. */
|
|
348
|
+
const dismiss = () => {
|
|
349
|
+
setWelcomeOpen(false);
|
|
350
|
+
if (typeof localStorage !== 'undefined') localStorage.setItem(WELCOME_DISMISSED_KEY, '1');
|
|
351
|
+
};
|
|
352
|
+
|
|
353
|
+
const actions: WelcomeChecklistActions = {
|
|
354
|
+
onOpenSettings: openSettingsFromWelcome,
|
|
355
|
+
onSetupEnvironments: () => {
|
|
356
|
+
setWelcomeOpen(false);
|
|
357
|
+
beginEnvironmentSetup();
|
|
358
|
+
},
|
|
359
|
+
onOpenEnvironments: () => {
|
|
360
|
+
setWelcomeOpen(false);
|
|
361
|
+
setEnvDialogOpen(true);
|
|
362
|
+
},
|
|
363
|
+
onOpenHelloOp: () => {
|
|
364
|
+
setWelcomeOpen(false);
|
|
365
|
+
switchWorkflow('default/hello');
|
|
366
|
+
},
|
|
367
|
+
onScoutInfrastructure: () => {
|
|
368
|
+
setWelcomeOpen(false);
|
|
369
|
+
beginInfrastructureScout();
|
|
370
|
+
},
|
|
371
|
+
};
|
|
372
|
+
|
|
373
|
+
return (
|
|
374
|
+
<>
|
|
375
|
+
<Dialog open={welcomeOpen} onOpenChange={(o) => (o ? setWelcomeOpen(true) : dismiss())}>
|
|
376
|
+
<DialogContent className="max-w-lg">
|
|
377
|
+
<DialogTitle>Welcome to Emberflow</DialogTitle>
|
|
378
|
+
<DialogDescription>
|
|
379
|
+
A few steps to get this project ready to build and run. Everything here is optional —
|
|
380
|
+
you can revisit it any time from the toolbar.
|
|
381
|
+
</DialogDescription>
|
|
382
|
+
{status ? (
|
|
383
|
+
<WelcomeChecklist
|
|
384
|
+
status={status}
|
|
385
|
+
actions={actions}
|
|
386
|
+
chosenAgent={agentChoice.agent}
|
|
387
|
+
onChooseAgent={(kind) => setAgentChoice({ ...agentChoice, agent: kind })}
|
|
388
|
+
/>
|
|
389
|
+
) : (
|
|
390
|
+
<div className="py-6 text-center text-[12px] text-muted-foreground">
|
|
391
|
+
Checking your project…
|
|
392
|
+
</div>
|
|
393
|
+
)}
|
|
394
|
+
<div className="flex justify-end">
|
|
395
|
+
<Button variant="ghost" size="sm" onClick={dismiss}>
|
|
396
|
+
Dismiss
|
|
397
|
+
</Button>
|
|
398
|
+
</div>
|
|
399
|
+
</DialogContent>
|
|
400
|
+
</Dialog>
|
|
401
|
+
|
|
402
|
+
<EnvironmentsDialog
|
|
403
|
+
open={envDialogOpen}
|
|
404
|
+
onOpenChange={setEnvDialogOpen}
|
|
405
|
+
onBack={() => {
|
|
406
|
+
setEnvDialogOpen(false);
|
|
407
|
+
setWelcomeOpen(true);
|
|
408
|
+
}}
|
|
409
|
+
/>
|
|
410
|
+
</>
|
|
411
|
+
);
|
|
412
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { renderToStaticMarkup } from 'react-dom/server';
|
|
3
|
+
import { Fragment, type ReactNode } from 'react';
|
|
4
|
+
import { renderAgentMarkdown } from './agentMarkdown';
|
|
5
|
+
|
|
6
|
+
/** Render the block array to static HTML so we can assert on structure/text. */
|
|
7
|
+
function html(source: string): string {
|
|
8
|
+
return renderToStaticMarkup(<Fragment>{renderAgentMarkdown(source) as ReactNode}</Fragment>);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
describe('renderAgentMarkdown', () => {
|
|
12
|
+
it('renders bold and inline code together inline', () => {
|
|
13
|
+
const out = html('This is **bold** and `code` here.');
|
|
14
|
+
expect(out).toContain('<strong');
|
|
15
|
+
expect(out).toContain('>bold</strong>');
|
|
16
|
+
expect(out).toContain('<code');
|
|
17
|
+
expect(out).toContain('>code</code>');
|
|
18
|
+
// Single paragraph containing both.
|
|
19
|
+
expect((out.match(/<p/g) ?? []).length).toBe(1);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('renders italic', () => {
|
|
23
|
+
const out = html('an *emphasised* word');
|
|
24
|
+
expect(out).toContain('<em');
|
|
25
|
+
expect(out).toContain('>emphasised</em>');
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('renders a fenced code block as a <pre> preserving content literally', () => {
|
|
29
|
+
const out = html('before\n\n```\nconst x = **not bold**;\nline2\n```\n\nafter');
|
|
30
|
+
expect(out).toContain('<pre');
|
|
31
|
+
// Markers inside the fence stay literal — no <strong> emitted for them.
|
|
32
|
+
expect(out).toContain('const x = **not bold**;');
|
|
33
|
+
expect(out).toContain('line2');
|
|
34
|
+
// Prose around it still becomes paragraphs.
|
|
35
|
+
expect(out).toContain('>before</p>');
|
|
36
|
+
expect(out).toContain('>after</p>');
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('renders headings as slightly-bolder paragraphs, not giant tags', () => {
|
|
40
|
+
const out = html('# Title\n## Sub\n### Small');
|
|
41
|
+
expect(out).not.toContain('<h1');
|
|
42
|
+
expect(out).not.toContain('<h2');
|
|
43
|
+
expect(out).toContain('font-semibold');
|
|
44
|
+
expect(out).toContain('>Title</p>');
|
|
45
|
+
expect(out).toContain('>Sub</p>');
|
|
46
|
+
expect(out).toContain('>Small</p>');
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('renders unordered lists (flat)', () => {
|
|
50
|
+
const out = html('- one\n- two\n- three');
|
|
51
|
+
expect((out.match(/<ul/g) ?? []).length).toBe(1);
|
|
52
|
+
expect((out.match(/<li/g) ?? []).length).toBe(3);
|
|
53
|
+
expect(out).toContain('>one</li>');
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('renders ordered lists', () => {
|
|
57
|
+
const out = html('1. first\n2. second');
|
|
58
|
+
expect(out).toContain('<ol');
|
|
59
|
+
expect((out.match(/<li/g) ?? []).length).toBe(2);
|
|
60
|
+
expect(out).toContain('>first</li>');
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('splits paragraphs on blank lines', () => {
|
|
64
|
+
const out = html('para one\n\npara two');
|
|
65
|
+
expect((out.match(/<p/g) ?? []).length).toBe(2);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('passes plain text through unchanged', () => {
|
|
69
|
+
const out = html('just some plain prose with no markup');
|
|
70
|
+
expect(out).toContain('just some plain prose with no markup');
|
|
71
|
+
expect(out).not.toContain('<strong');
|
|
72
|
+
expect(out).not.toContain('<em');
|
|
73
|
+
expect(out).not.toContain('<code');
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('leaves malformed markdown as literal text', () => {
|
|
77
|
+
// Unclosed bold and a single unpaired star have no valid closing marker, so
|
|
78
|
+
// they fall back to literal text rather than emitting <strong>/<em>.
|
|
79
|
+
const out = html('an **unclosed bold marker\n\n5 * 3 = 15');
|
|
80
|
+
expect(out).not.toContain('<strong');
|
|
81
|
+
expect(out).not.toContain('<em');
|
|
82
|
+
expect(out).toContain('**unclosed bold marker');
|
|
83
|
+
expect(out).toContain('5 * 3 = 15');
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('does not treat backtick content as bold', () => {
|
|
87
|
+
const out = html('`**literal**`');
|
|
88
|
+
expect(out).toContain('>**literal**</code>');
|
|
89
|
+
expect(out).not.toContain('<strong');
|
|
90
|
+
});
|
|
91
|
+
});
|