From 8e6748a459af1ecb734cafd0e28d0fba0b4e2f62 Mon Sep 17 00:00:00 2001 From: Pablo P Varela Date: Mon, 20 Apr 2026 19:38:08 +0200 Subject: [PATCH 1/9] feat: add 'Fix with Doce' button to preview error state When the preview server fails to start, show a 'Fix with Doce' button that sends the error message to the AI chat for automated troubleshooting. - Add onFixWithDoce callback prop to PreviewPanel - Wire up handleSend from useChatPanel in ProjectContentWrapper - Send formatted prompt with error details to AI agent --- src/components/preview/PreviewPanel.tsx | 19 ++++++++++-- .../projects/ProjectContentWrapper.tsx | 31 ++++++++++++------- 2 files changed, 35 insertions(+), 15 deletions(-) diff --git a/src/components/preview/PreviewPanel.tsx b/src/components/preview/PreviewPanel.tsx index 3d48cf24..5dc7fda6 100644 --- a/src/components/preview/PreviewPanel.tsx +++ b/src/components/preview/PreviewPanel.tsx @@ -70,6 +70,8 @@ interface PreviewPanelProps { userMessageCount: number, isStreaming: boolean, ) => void; + /** Callback to send a "Fix with Doce" message to the chat */ + onFixWithDoce?: (errorMessage: string) => void; } type PreviewState = @@ -92,6 +94,7 @@ export function PreviewPanel({ models = [], onOpenFile, onStreamingStateChange, + onFixWithDoce, }: PreviewPanelProps) { const isMobile = useIsMobile(); const { baseUrl } = useBaseUrlSetting(); @@ -673,9 +676,19 @@ export function PreviewPanel({ {message || "Check the terminal for details."}

- +
+ + {onFixWithDoce && message && ( + + )} +
) : null} diff --git a/src/components/projects/ProjectContentWrapper.tsx b/src/components/projects/ProjectContentWrapper.tsx index 4dade1a9..25dfbb61 100644 --- a/src/components/projects/ProjectContentWrapper.tsx +++ b/src/components/projects/ProjectContentWrapper.tsx @@ -6,6 +6,7 @@ import { ErrorBoundary } from "@/components/error/ErrorBoundary"; import { PreviewPanel } from "@/components/preview/PreviewPanel"; import { ResizableSeparator } from "@/components/preview/ResizableSeparator"; import { ContainerStartupDisplay } from "@/components/setup/ContainerStartupDisplay"; +import { useChatPanel } from "@/hooks/useChatPanel"; import { useLiveState } from "@/hooks/useLiveState"; import { useResizablePanel } from "@/hooks/useResizablePanel"; import { useChatLayout } from "@/stores/useChatLayout"; @@ -57,6 +58,21 @@ export function ProjectContentWrapper({ (s) => s.pendingByProjectId.get(projectId) ?? null, ); + // Get chat send function for "Fix with Doce" feature + const { handleSend } = useChatPanel({ + projectId, + models, + onStreamingStateChange: (count, streaming) => { + setUserMessageCount(count); + setIsStreaming(streaming); + }, + }); + + const handleFixWithDoce = (errorMessage: string) => { + const prompt = `The preview server failed to start with this error:\n\n${errorMessage}\n\nPlease fix this issue.`; + void handleSend(prompt); + }; + useEffect(() => { if (!showStartupDisplay) return; if ( @@ -115,10 +131,7 @@ export function ProjectContentWrapper({ isStreaming={isStreaming} models={models} onOpenFile={setFileToOpen} - onStreamingStateChange={(count, streaming) => { - setUserMessageCount(count); - setIsStreaming(streaming); - }} + onFixWithDoce={handleFixWithDoce} /> ) : ( @@ -151,10 +164,7 @@ export function ProjectContentWrapper({ projectId={projectId} models={models} onOpenFile={setFileToOpen} - onStreamingStateChange={(count, streaming) => { - setUserMessageCount(count); - setIsStreaming(streaming); - }} + hideDetachToggle={false} /> @@ -194,6 +204,7 @@ export function ProjectContentWrapper({ onFileOpened={() => setFileToOpen(null)} userMessageCount={userMessageCount} isStreaming={isStreaming} + onFixWithDoce={handleFixWithDoce} /> @@ -203,10 +214,6 @@ export function ProjectContentWrapper({ projectId={projectId} models={models} onOpenFile={setFileToOpen} - onStreamingStateChange={(count, streaming) => { - setUserMessageCount(count); - setIsStreaming(streaming); - }} /> )} From dd40e88ebd656025270d19a00c06642da6b1e15a Mon Sep 17 00:00:00 2001 From: Pablo P Varela Date: Thu, 23 Apr 2026 19:03:41 +0200 Subject: [PATCH 2/9] trigger ci From 533da0068b8bbb6b944bb982ee2bc257c45c0420 Mon Sep 17 00:00:00 2001 From: Pablo P Varela Date: Thu, 23 Apr 2026 19:05:12 +0200 Subject: [PATCH 3/9] fix: disable tailscale cache on self-hosted runner --- .github/workflows/pr-preview.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/pr-preview.yml b/.github/workflows/pr-preview.yml index f93eb0f4..c24b3d1f 100644 --- a/.github/workflows/pr-preview.yml +++ b/.github/workflows/pr-preview.yml @@ -113,6 +113,7 @@ jobs: uses: tailscale/github-action@v4 with: authkey: ${{ secrets.TAILSCALE_AUTHKEY }} + use-cache: false - name: Extract PR number id: pr @@ -302,6 +303,7 @@ jobs: uses: tailscale/github-action@v4 with: authkey: ${{ secrets.TAILSCALE_AUTHKEY }} + use-cache: false - name: Extract PR number id: pr From d04bd65f2328d6e50a56c68496a1a1d065f70693 Mon Sep 17 00:00:00 2001 From: Pablo P Varela Date: Thu, 23 Apr 2026 19:06:29 +0200 Subject: [PATCH 4/9] fix: clean tailscale cache before connecting --- .github/workflows/pr-preview.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/pr-preview.yml b/.github/workflows/pr-preview.yml index c24b3d1f..ed493faf 100644 --- a/.github/workflows/pr-preview.yml +++ b/.github/workflows/pr-preview.yml @@ -109,6 +109,9 @@ jobs: echo "Dockerfile unchanged, will reuse main branch image" fi + - name: Clean Tailscale cache + run: rm -f ~/.cache/tailscale.tgz + - name: Connect to Tailscale uses: tailscale/github-action@v4 with: @@ -299,6 +302,9 @@ jobs: contents: read steps: + - name: Clean Tailscale cache + run: rm -f ~/.cache/tailscale.tgz + - name: Connect to Tailscale uses: tailscale/github-action@v4 with: From feb1308760e8c9b1600878a1e4117154fd1ab1e0 Mon Sep 17 00:00:00 2001 From: Pablo P Varela Date: Thu, 23 Apr 2026 19:12:28 +0200 Subject: [PATCH 5/9] trigger ci with new reusable tailscale key From 3473ff9b32a81038afcb520f95a2f35ac2d66f0d Mon Sep 17 00:00:00 2001 From: Pablo P Varela Date: Thu, 23 Apr 2026 19:13:56 +0200 Subject: [PATCH 6/9] trigger build: force docker image build From 09b9882f3186428111228be441cd43b1cb6258b3 Mon Sep 17 00:00:00 2001 From: Pablo P Varela Date: Thu, 23 Apr 2026 19:14:05 +0200 Subject: [PATCH 7/9] chore: trigger docker build --- Dockerfile | 1 + 1 file changed, 1 insertion(+) diff --git a/Dockerfile b/Dockerfile index 3a52ecfb..a922c824 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,5 @@ # Multi-stage build for production deployment +# Trigger rebuild: 2026-04-23 # Builder stage FROM node:22-slim AS builder From a9a8b70a12cea5f4c2ad832795da50ca8a355f3c Mon Sep 17 00:00:00 2001 From: Pablo P Varela Date: Thu, 23 Apr 2026 19:14:56 +0200 Subject: [PATCH 8/9] fix: kill tailscale processes before installing --- .github/workflows/pr-preview.yml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/.github/workflows/pr-preview.yml b/.github/workflows/pr-preview.yml index ed493faf..c70d1f60 100644 --- a/.github/workflows/pr-preview.yml +++ b/.github/workflows/pr-preview.yml @@ -112,6 +112,12 @@ jobs: - name: Clean Tailscale cache run: rm -f ~/.cache/tailscale.tgz + - name: Stop any running Tailscale + run: | + sudo pkill -f tailscaled 2>/dev/null || true + sudo rm -f /usr/local/bin/tailscale /usr/local/bin/tailscaled 2>/dev/null || true + sleep 2 + - name: Connect to Tailscale uses: tailscale/github-action@v4 with: @@ -305,6 +311,12 @@ jobs: - name: Clean Tailscale cache run: rm -f ~/.cache/tailscale.tgz + - name: Stop any running Tailscale + run: | + sudo pkill -f tailscaled 2>/dev/null || true + sudo rm -f /usr/local/bin/tailscale /usr/local/bin/tailscaled 2>/dev/null || true + sleep 2 + - name: Connect to Tailscale uses: tailscale/github-action@v4 with: From 10dbeff1fb6cd8e30fb3f506dc88e10035448209 Mon Sep 17 00:00:00 2001 From: Pablo P Varela Date: Thu, 23 Apr 2026 19:29:28 +0200 Subject: [PATCH 9/9] fix: remove API key prefix validation to support any provider key format Remove hardcoded prefix checks (sk-, api-, sk-ant-) from all validators. Validation now relies solely on actual API calls to test key validity. This enables providers like Fireworks AI (fw- prefix) and any future providers with non-standard key formats to work without code changes. --- src/server/opencode/apiKeyValidation.ts | 29 ------------------------- 1 file changed, 29 deletions(-) diff --git a/src/server/opencode/apiKeyValidation.ts b/src/server/opencode/apiKeyValidation.ts index dfe53181..9358046a 100644 --- a/src/server/opencode/apiKeyValidation.ts +++ b/src/server/opencode/apiKeyValidation.ts @@ -67,14 +67,6 @@ function validateOpenAICompatibleKeyEffect(baseUrl: string) { apiKey: string, ): Effect.Effect => Effect.gen(function* () { - if (!apiKey.startsWith("sk-") && !apiKey.startsWith("api-")) { - return { - valid: false, - error: - "Invalid API key format. Keys typically start with 'sk-' or 'api-'.", - }; - } - const response = yield* Effect.tryPromise({ try: () => fetch(`${baseUrl}/models`, { @@ -197,14 +189,6 @@ function validateAnthropicKeyEffect( apiKey: string, ): Effect.Effect { return Effect.gen(function* () { - if (!apiKey.startsWith("sk-ant-")) { - return { - valid: false, - error: - "Invalid Anthropic API key format. Keys should start with 'sk-ant-'.", - }; - } - const response = yield* Effect.tryPromise({ try: () => fetch("https://api.anthropic.com/v1/models", { @@ -276,13 +260,6 @@ function validateOpenAIKeyEffect( apiKey: string, ): Effect.Effect { return Effect.gen(function* () { - if (!apiKey.startsWith("sk-")) { - return { - valid: false, - error: "Invalid OpenAI API key format. Keys should start with 'sk-'.", - }; - } - const response = yield* Effect.tryPromise({ try: () => fetch("https://api.openai.com/v1/models", { @@ -350,11 +327,5 @@ export async function validateOpenAIKey( function validateOpencodeKeyEffect( apiKey: string, ): Effect.Effect { - if (!apiKey.startsWith("sk-")) { - return Effect.succeed({ - valid: false, - error: "Invalid OpenCode API key format. Keys should start with 'sk-'.", - }); - } return Effect.succeed({ valid: true }); }