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 });
}