Fix Antigravity model list and enhance clipboard-guided handoff UX
This commit is contained in:
1 parent
0467eebf0e
commit
29ef28b7cf
4 files changed
+143
-42
No files matched your search
+111
-35
@@ -1851,14 +1851,13 @@ const DEV_TARGETS = {
|
|||||||
antigravity: {
|
antigravity: {
|
||||||
id: "antigravity",
|
id: "antigravity",
|
||||||
label: "🛸 Google Antigravity",
|
label: "🛸 Google Antigravity",
|
||||||
defaultModel: "gemini-3.1-pro",
|
defaultModel: "gemini-3.8-flash",
|
||||||
models: [
|
models: [
|
||||||
{ id: "gemini-3.1-pro", label: "Gemini 3.1 Pro (Recommended — Full-Stack & Architecture)" },
|
{ id: "gemini-4-argon", label: "Gemini 4 Argon (Medium)" },
|
||||||
{ id: "gemini-3.0-pro", label: "Gemini 3.0 Pro (Deep Reasoning & Complex Refactors)" },
|
{ id: "gemini-3.8-flash", label: "Gemini 3.8 Flash (Medium)" },
|
||||||
{ id: "gemini-3.1-flash", label: "Gemini 3.1 Flash (Fast Iteration & UI Prototyping)" },
|
{ id: "claude-opus-5.5", label: "Claude Opus 5.5 (Medium)" },
|
||||||
{ id: "gemini-3.1-flash-lite", label: "Gemini 3.1 Flash-Lite (Lightweight & Rapid Tasks)" },
|
{ id: "claude-sonnet-5.5", label: "Claude Sonnet 5.5 (Medium)" },
|
||||||
{ id: "claude-sonnet-4.5", label: "Claude 4.5 Sonnet (via Antigravity Model Selector)" },
|
{ id: "gpt-oss-120b", label: "GPT-OSS 120B (Medium)" }
|
||||||
{ id: "claude-opus-4.5", label: "Claude 4.5 Opus (via Antigravity Model Selector)" }
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
codex: {
|
codex: {
|
||||||
@@ -1875,6 +1874,33 @@ const DEV_TARGETS = {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
async function copyTextToClipboard(text) {
|
||||||
|
if (!text) return false;
|
||||||
|
if (navigator.clipboard && window.isSecureContext) {
|
||||||
|
try {
|
||||||
|
await navigator.clipboard.writeText(text);
|
||||||
|
return true;
|
||||||
|
} catch {
|
||||||
|
// Fall through to fallback
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const textArea = document.createElement("textarea");
|
||||||
|
textArea.value = text;
|
||||||
|
textArea.style.position = "fixed";
|
||||||
|
textArea.style.left = "-999999px";
|
||||||
|
textArea.style.top = "-999999px";
|
||||||
|
document.body.appendChild(textArea);
|
||||||
|
textArea.focus();
|
||||||
|
textArea.select();
|
||||||
|
const successful = document.execCommand("copy");
|
||||||
|
textArea.remove();
|
||||||
|
return successful;
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let activeDevTarget = "antigravity";
|
let activeDevTarget = "antigravity";
|
||||||
|
|
||||||
function populateDevModelOptions(targetApp) {
|
function populateDevModelOptions(targetApp) {
|
||||||
@@ -1940,6 +1966,14 @@ async function openDevHandoffModal(defaultTarget = "antigravity") {
|
|||||||
homelabCb.checked = state.activeIdea.category === "project" || !isCurrentProductTShirt();
|
homelabCb.checked = state.activeIdea.category === "project" || !isCurrentProductTShirt();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const instructionNote = document.getElementById("dev-handoff-instruction-note");
|
||||||
|
if (instructionNote) {
|
||||||
|
instructionNote.innerHTML =
|
||||||
|
activeDevTarget === "codex"
|
||||||
|
? "🤖 Submitting opens OpenAI Codex with the prompt preloaded and saves the full spec to your workspace."
|
||||||
|
: "🛸 Submitting switches focus to Antigravity and copies the complete project specification to your clipboard. Simply press <kbd>Ctrl+V</kbd> in Antigravity's chat to start coding.";
|
||||||
|
}
|
||||||
|
|
||||||
document.getElementById("dev-handoff-result-box")?.classList.add("hidden");
|
document.getElementById("dev-handoff-result-box")?.classList.add("hidden");
|
||||||
modal.classList.remove("hidden");
|
modal.classList.remove("hidden");
|
||||||
await refreshDevSpecPreview();
|
await refreshDevSpecPreview();
|
||||||
@@ -2806,6 +2840,13 @@ function bindGlobalEvents() {
|
|||||||
? "🚀 Submit & Open in OpenAI Codex"
|
? "🚀 Submit & Open in OpenAI Codex"
|
||||||
: "🚀 Submit & Launch in Antigravity";
|
: "🚀 Submit & Launch in Antigravity";
|
||||||
}
|
}
|
||||||
|
const instructionNote = document.getElementById("dev-handoff-instruction-note");
|
||||||
|
if (instructionNote) {
|
||||||
|
instructionNote.innerHTML =
|
||||||
|
activeDevTarget === "codex"
|
||||||
|
? "🤖 Submitting opens OpenAI Codex with the prompt preloaded and saves the full spec to your workspace."
|
||||||
|
: "🛸 Submitting switches focus to Antigravity and copies the complete project specification to your clipboard. Simply press <kbd>Ctrl+V</kbd> in Antigravity's chat to start coding.";
|
||||||
|
}
|
||||||
await refreshDevSpecPreview();
|
await refreshDevSpecPreview();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -2816,8 +2857,8 @@ function bindGlobalEvents() {
|
|||||||
document.getElementById("btn-copy-dev-spec")?.addEventListener("click", async () => {
|
document.getElementById("btn-copy-dev-spec")?.addEventListener("click", async () => {
|
||||||
const text = document.getElementById("dev-spec-preview-textarea")?.value || "";
|
const text = document.getElementById("dev-spec-preview-textarea")?.value || "";
|
||||||
if (!text) return;
|
if (!text) return;
|
||||||
await navigator.clipboard.writeText(text);
|
const ok = await copyTextToClipboard(text);
|
||||||
showToast("📋 Copied full development specification to clipboard!");
|
showToast(ok ? "📋 Copied full development specification to clipboard!" : "⚠️ Unable to auto-copy; please copy text manually.");
|
||||||
});
|
});
|
||||||
|
|
||||||
document.getElementById("btn-download-dev-spec")?.addEventListener("click", () => {
|
document.getElementById("btn-download-dev-spec")?.addEventListener("click", () => {
|
||||||
@@ -2852,11 +2893,7 @@ function bindGlobalEvents() {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
// 1. Copy full specification to clipboard so it's immediately ready to paste (Ctrl+V) in Antigravity or Codex
|
// 1. Copy full specification to clipboard so it's immediately ready to paste (Ctrl+V) in Antigravity or Codex
|
||||||
try {
|
await copyTextToClipboard(customPrompt);
|
||||||
await navigator.clipboard.writeText(customPrompt);
|
|
||||||
} catch {
|
|
||||||
// Ignore clipboard permission errors if non-HTTPS context
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. Persist handoff package & workspace on server
|
// 2. Persist handoff package & workspace on server
|
||||||
const res = await apiFetch(`/api/ideas/${state.activeIdeaId}/dev-handoff`, {
|
const res = await apiFetch(`/api/ideas/${state.activeIdeaId}/dev-handoff`, {
|
||||||
@@ -2876,30 +2913,69 @@ function bindGlobalEvents() {
|
|||||||
|
|
||||||
if (resultBox) {
|
if (resultBox) {
|
||||||
resultBox.classList.remove("hidden");
|
resultBox.classList.remove("hidden");
|
||||||
resultBox.innerHTML = `
|
const modelName = handoff.modelLabel || handoff.model;
|
||||||
<div style="display:flex;justify-content:space-between;align-items:center;flex-wrap:wrap;gap:0.5rem;">
|
|
||||||
<strong>✅ Ready in ${escapeHtml(handoff.targetLabel)} (${escapeHtml(handoff.model)})</strong>
|
if (activeDevTarget === "antigravity") {
|
||||||
<div style="display:flex;gap:0.4rem;">
|
resultBox.innerHTML = `
|
||||||
<a href="${escapeHtml(handoff.deepLinkUrl)}" class="btn btn-primary btn-xs">
|
<div style="background:linear-gradient(135deg, rgba(76,140,245,0.12), rgba(52,199,89,0.12)); border:1px solid rgba(76,140,245,0.35); border-radius:8px; padding:0.85rem; margin-top:0.4rem;">
|
||||||
🚀 Open ${activeDevTarget === "codex" ? "Codex App" : "Antigravity App"}
|
<div style="display:flex;justify-content:space-between;align-items:center;flex-wrap:wrap;gap:0.5rem;">
|
||||||
</a>
|
<div style="font-weight:700;font-size:0.95rem;display:flex;align-items:center;gap:0.4rem;">
|
||||||
<a href="${escapeHtml(handoff.specUrl)}" target="_blank" rel="noopener noreferrer" class="btn btn-ghost btn-xs">
|
<span>🛸</span> Switched to Google Antigravity!
|
||||||
📄 View SPEC.md
|
</div>
|
||||||
</a>
|
<div style="display:flex;gap:0.4rem;flex-wrap:wrap;">
|
||||||
|
<button type="button" id="btn-copy-dev-spec-again" class="btn btn-secondary btn-xs">
|
||||||
|
📋 Copy Spec Again
|
||||||
|
</button>
|
||||||
|
<a href="${escapeHtml(handoff.deepLinkUrl)}" class="btn btn-primary btn-xs">
|
||||||
|
🛸 Re-Focus Antigravity
|
||||||
|
</a>
|
||||||
|
<a href="${escapeHtml(handoff.specUrl)}" target="_blank" rel="noopener noreferrer" class="btn btn-ghost btn-xs">
|
||||||
|
📄 View SPEC.md
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="margin-top:0.6rem;padding:0.65rem 0.8rem;background:var(--bg-card);border-radius:6px;border:1px dashed var(--border-color);font-size:0.85rem;line-height:1.5;">
|
||||||
|
<div style="color:var(--text-main);font-weight:600;">
|
||||||
|
👉 Ready to develop with <strong>${escapeHtml(modelName)}</strong>!
|
||||||
|
</div>
|
||||||
|
<div style="margin-top:0.25rem;color:var(--text-secondary);">
|
||||||
|
The full specification for <strong>${escapeHtml(state.activeIdea.title)}</strong> has been loaded onto your clipboard! Since Antigravity requires starting or selecting your conversation, simply click into Antigravity's chat bar and press <kbd style="background:var(--bg-base);padding:2px 6px;border-radius:4px;border:1px solid var(--border-color);font-weight:700;">Ctrl+V</kbd> (or <kbd style="background:var(--bg-base);padding:2px 6px;border-radius:4px;border:1px solid var(--border-color);font-weight:700;">Cmd+V</kbd>) to paste the prompt and begin.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
`;
|
||||||
<p class="muted-label" style="margin-top:0.25rem;">
|
|
||||||
Full specification has been <strong>copied to your clipboard (<kbd>Ctrl+V</kbd>)</strong> and saved to server workspace <code>${escapeHtml(handoff.slug)}</code>.
|
document.getElementById("btn-copy-dev-spec-again")?.addEventListener("click", async () => {
|
||||||
</p>
|
const reCopied = await copyTextToClipboard(customPrompt);
|
||||||
${
|
showToast(reCopied ? "📋 Specification copied to clipboard! Press Ctrl+V in Antigravity." : "⚠️ Please copy manually from the preview below.");
|
||||||
handoff.codexExecution?.output
|
});
|
||||||
? `<pre style="max-height:160px;overflow:auto;background:var(--bg-base);padding:0.5rem;border-radius:6px;font-size:0.75rem;white-space:pre-wrap;margin-top:0.35rem;">${escapeHtml(handoff.codexExecution.output)}</pre>`
|
} else {
|
||||||
: ""
|
resultBox.innerHTML = `
|
||||||
}
|
<div style="display:flex;justify-content:space-between;align-items:center;flex-wrap:wrap;gap:0.5rem;">
|
||||||
`;
|
<strong>✅ Ready in ${escapeHtml(handoff.targetLabel)} (${escapeHtml(handoff.model)})</strong>
|
||||||
|
<div style="display:flex;gap:0.4rem;">
|
||||||
|
<a href="${escapeHtml(handoff.deepLinkUrl)}" class="btn btn-primary btn-xs">
|
||||||
|
🚀 Open Codex App
|
||||||
|
</a>
|
||||||
|
<a href="${escapeHtml(handoff.specUrl)}" target="_blank" rel="noopener noreferrer" class="btn btn-ghost btn-xs">
|
||||||
|
📄 View SPEC.md
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p class="muted-label" style="margin-top:0.25rem;">
|
||||||
|
Full specification has been <strong>copied to your clipboard (<kbd>Ctrl+V</kbd>)</strong> and saved to server workspace <code>${escapeHtml(handoff.slug)}</code>.
|
||||||
|
</p>
|
||||||
|
${
|
||||||
|
handoff.codexExecution?.output
|
||||||
|
? `<pre style="max-height:160px;overflow:auto;background:var(--bg-base);padding:0.5rem;border-radius:6px;font-size:0.75rem;white-space:pre-wrap;margin-top:0.35rem;">${escapeHtml(handoff.codexExecution.output)}</pre>`
|
||||||
|
: ""
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
showToast(`🚀 Copied full spec & launching ${handoff.targetLabel} (${handoff.model})!`);
|
showToast(`🛸 Copied spec & launching Antigravity! Paste with Ctrl+V.`);
|
||||||
await loadIdeas();
|
await loadIdeas();
|
||||||
renderDrawerContent();
|
renderDrawerContent();
|
||||||
|
|
||||||
|
|||||||
@@ -936,6 +936,10 @@
|
|||||||
|
|
||||||
<div id="dev-handoff-result-box" class="nb-edit-box hidden"></div>
|
<div id="dev-handoff-result-box" class="nb-edit-box hidden"></div>
|
||||||
|
|
||||||
|
<div id="dev-handoff-instruction-note" class="muted-label" style="font-size:0.8rem; padding:0.4rem 0.6rem; background:rgba(76,140,245,0.08); border-radius:6px; border-left:3px solid var(--accent-primary); margin-top:0.4rem;">
|
||||||
|
🛸 Submitting switches focus to Antigravity and copies the complete project specification to your clipboard. Simply press <kbd>Ctrl+V</kbd> in Antigravity's chat to start coding.
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button type="button" id="btn-cancel-dev-modal" class="btn btn-ghost">Cancel</button>
|
<button type="button" id="btn-cancel-dev-modal" class="btn btn-ghost">Cancel</button>
|
||||||
<button type="submit" id="btn-submit-dev-handoff" class="btn btn-primary">
|
<button type="submit" id="btn-submit-dev-handoff" class="btn btn-primary">
|
||||||
|
|||||||
@@ -424,14 +424,13 @@ export const DEV_HANDOFF_TARGETS = {
|
|||||||
id: "antigravity",
|
id: "antigravity",
|
||||||
label: "🛸 Google Antigravity",
|
label: "🛸 Google Antigravity",
|
||||||
protocol: "antigravity://",
|
protocol: "antigravity://",
|
||||||
defaultModel: "gemini-3.1-pro",
|
defaultModel: "gemini-3.8-flash",
|
||||||
models: [
|
models: [
|
||||||
{ id: "gemini-3.1-pro", label: "Gemini 3.1 Pro (Recommended — Full-Stack & Architecture)" },
|
{ id: "gemini-4-argon", label: "Gemini 4 Argon (Medium)" },
|
||||||
{ id: "gemini-3.0-pro", label: "Gemini 3.0 Pro (Deep Reasoning & Complex Refactors)" },
|
{ id: "gemini-3.8-flash", label: "Gemini 3.8 Flash (Medium)" },
|
||||||
{ id: "gemini-3.1-flash", label: "Gemini 3.1 Flash (Fast Iteration & UI Prototyping)" },
|
{ id: "claude-opus-5.5", label: "Claude Opus 5.5 (Medium)" },
|
||||||
{ id: "gemini-3.1-flash-lite", label: "Gemini 3.1 Flash-Lite (Lightweight & Rapid Tasks)" },
|
{ id: "claude-sonnet-5.5", label: "Claude Sonnet 5.5 (Medium)" },
|
||||||
{ id: "claude-sonnet-4.5", label: "Claude 4.5 Sonnet (via Antigravity Model Selector)" },
|
{ id: "gpt-oss-120b", label: "GPT-OSS 120B (Medium)" }
|
||||||
{ id: "claude-opus-4.5", label: "Claude 4.5 Opus (via Antigravity Model Selector)" }
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
codex: {
|
codex: {
|
||||||
@@ -688,10 +687,14 @@ export async function executeDevelopmentHandoff({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const modelObj = targetMeta.models.find((m) => m.id === chosenModel);
|
||||||
|
const modelLabel = modelObj ? modelObj.label : chosenModel;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
targetApp,
|
targetApp,
|
||||||
targetLabel: targetMeta.label,
|
targetLabel: targetMeta.label,
|
||||||
model: chosenModel,
|
model: chosenModel,
|
||||||
|
modelLabel,
|
||||||
slug,
|
slug,
|
||||||
specMarkdown: compiledSpec,
|
specMarkdown: compiledSpec,
|
||||||
specUrl,
|
specUrl,
|
||||||
|
|||||||
@@ -269,6 +269,24 @@ test("Development Handoff endpoint compiles full Spark spec and submits to Antig
|
|||||||
assert.equal(handoffData.handoff.model, "gpt-5.6-sol");
|
assert.equal(handoffData.handoff.model, "gpt-5.6-sol");
|
||||||
assert.match(handoffData.handoff.deepLinkUrl, /^codex:\/\/threads\/new/);
|
assert.match(handoffData.handoff.deepLinkUrl, /^codex:\/\/threads\/new/);
|
||||||
assert.equal(handoffData.idea.status, "building");
|
assert.equal(handoffData.idea.status, "building");
|
||||||
|
|
||||||
|
// Verify Antigravity handoff with updated models (Gemini 3.8 Flash default)
|
||||||
|
const agyRes = await fetch(`${baseUrl}/api/ideas/1/dev-handoff`, {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({
|
||||||
|
targetApp: "antigravity",
|
||||||
|
model: "gemini-3.8-flash",
|
||||||
|
includeHomelab: true,
|
||||||
|
moveStatusToBuilding: true
|
||||||
|
})
|
||||||
|
});
|
||||||
|
assert.equal(agyRes.status, 200);
|
||||||
|
const agyData = await agyRes.json();
|
||||||
|
assert.equal(agyData.handoff.targetApp, "antigravity");
|
||||||
|
assert.equal(agyData.handoff.model, "gemini-3.8-flash");
|
||||||
|
assert.equal(agyData.handoff.modelLabel, "Gemini 3.8 Flash (Medium)");
|
||||||
|
assert.match(agyData.handoff.deepLinkUrl, /^antigravity:\/\/new\?model=gemini-3\.8-flash/);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user