diff --git a/packages/app/src/components/settings-v2/developer-tools-controller.ts b/packages/app/src/components/settings-v2/developer-tools-controller.ts index 12a6e867b..b15414690 100644 --- a/packages/app/src/components/settings-v2/developer-tools-controller.ts +++ b/packages/app/src/components/settings-v2/developer-tools-controller.ts @@ -25,6 +25,9 @@ export function createDeveloperToolsController() { const [pending, setPending] = createSignal(false) const [rebuildState, setRebuildState] = createSignal("idle") const [rebuildError, setRebuildError] = createSignal(undefined) + const [vsixBuildState, setVsixBuildState] = createSignal("idle") + const [vsixBuildError, setVsixBuildError] = createSignal(undefined) + const [vsixPath, setVsixPath] = createSignal(undefined) // On mount, check if we just came back from a rebuild (successful or in-progress). // The "rebuilding" flag survives iframe reloads caused by file-watcher churn @@ -102,6 +105,26 @@ export function createDeveloperToolsController() { // The window reload follows shortly — "rebuilt" flag is read on next mount } } + + // Devcontainer VSIX build status messages + if (d && d.source === "amicode" && d.kind === "dev-tools-build-vsix-status") { + if (d.state === "building") { + setVsixBuildState("rebuilding") + setVsixBuildError(undefined) + setVsixPath(undefined) + } else if (d.state === "failed") { + setVsixBuildState("failed") + setVsixBuildError(d.error ?? "Unknown error") + } else if (d.state === "done") { + if (typeof d.vsixPath === "string" && d.vsixPath.trim() !== "") { + setVsixBuildState("rebuilt") + setVsixPath(d.vsixPath) + } else { + setVsixBuildState("failed") + setVsixBuildError("Build completed but no output path was reported") + } + } + } } if (typeof window !== "undefined") { @@ -192,15 +215,45 @@ export function createDeveloperToolsController() { setAmicodePath: (value: string) => { settings.developer.setAmicodePath(value) }, - /** Trigger validation + apply on blur */ - commitOpencodePath: () => sendUpdate(), - commitAmicodePath: () => sendUpdate(), + /** Trigger validation + apply on blur — only in developer mode (not devcontainer-only mode) */ + commitOpencodePath: () => { + if (settings.developer.enabled()) sendUpdate() + }, + commitAmicodePath: () => { + if (settings.developer.enabled()) sendUpdate() + }, /** Trigger a full rebuild (local = from disk, remote = git pull first) */ rebuild, status, pending, rebuildState, rebuildError, + // Devcontainer VSIX build + devcontainerMode: settings.developer.devcontainerMode, + setDevcontainerMode: (value: boolean) => { + settings.developer.setDevcontainerMode(value) + }, + vsixOutputPath: settings.developer.vsixOutputPath, + setVsixOutputPath: (value: string) => { + settings.developer.setVsixOutputPath(value) + }, + buildVsix: () => { + if (!inAmicode()) return + if (vsixBuildState() === "rebuilding") return + setVsixBuildState("rebuilding") + setVsixBuildError(undefined) + setVsixPath(undefined) + window.parent.postMessage({ + source: "amicode", + kind: "dev-tools-build-vsix", + opencodePath: settings.developer.opencodePath(), + amicodePath: settings.developer.amicodePath(), + outputPath: settings.developer.vsixOutputPath(), + }, "*") + }, + vsixBuildState, + vsixBuildError, + vsixPath, } } diff --git a/packages/app/src/components/settings-v2/developer-tools.tsx b/packages/app/src/components/settings-v2/developer-tools.tsx index 5773b900a..904244708 100644 --- a/packages/app/src/components/settings-v2/developer-tools.tsx +++ b/packages/app/src/components/settings-v2/developer-tools.tsx @@ -57,7 +57,7 @@ const DeveloperToolsContent: Component<{ controller: DeveloperToolsController }> return ( - {/* Rebuild buttons */} + {/* Action buttons row — at the top for discoverability */}
+ +
+ props.controller.buildVsix()} + disabled={props.controller.vsixBuildState() === "rebuilding"} + > + {props.controller.vsixBuildState() === "rebuilding" ? "Building VSIX..." : "Build VSIX"} + +
+ +
+ + VSIX built: {props.controller.vsixPath() ?? ""} +
+
+ +
+ + {props.controller.vsixBuildError()} +
+
+
+ {/* Toggle 1: Developer mode (hot-reload, eager actions) */} + {/* Toggle 2: Devcontainer mode (vsix build, no eager actions) — independent */} - {language.t("settings.general.row.opencodePath.description")} - - {opencodeError()} - - - } + title="Devcontainer mode (experimental)" + description="Build a .vsix for manual installation instead of hot-reloading" > -
- props.controller.setOpencodePath(event.currentTarget.value)} - onBlur={() => props.controller.commitOpencodePath()} - placeholder={language.t("settings.general.row.opencodePath.placeholder")} - disabled={!props.controller.enabled()} - spellcheck={false} - autocorrect="off" - autocomplete="off" - autocapitalize="off" - aria-label={language.t("settings.general.row.opencodePath.title")} - /> -
+ props.controller.setDevcontainerMode(checked)} + />
- - {language.t("settings.general.row.amicodePath.description")} - - - {language.t("settings.general.row.amicodePath.building")} - - - - {amicodeError()} - - - - {language.t("settings.general.row.amicodePath.reloadNeeded")} - - - - } - > -
- props.controller.setAmicodePath(event.currentTarget.value)} - onBlur={() => props.controller.commitAmicodePath()} - placeholder={language.t("settings.general.row.amicodePath.placeholder")} - disabled={!props.controller.enabled()} - spellcheck={false} - autocorrect="off" - autocomplete="off" - autocapitalize="off" - aria-label={language.t("settings.general.row.amicodePath.title")} - /> -
-
+ {/* Path inputs — visible when EITHER mode is ON */} + + + {language.t("settings.general.row.opencodePath.description")} + + {opencodeError()} + + + } + > +
+ props.controller.setOpencodePath(event.currentTarget.value)} + onBlur={() => props.controller.commitOpencodePath()} + placeholder={language.t("settings.general.row.opencodePath.placeholder")} + spellcheck={false} + autocorrect="off" + autocomplete="off" + autocapitalize="off" + aria-label={language.t("settings.general.row.opencodePath.title")} + /> +
+
+ + + {language.t("settings.general.row.amicodePath.description")} + + + {language.t("settings.general.row.amicodePath.building")} + + + + {amicodeError()} + + + + {language.t("settings.general.row.amicodePath.reloadNeeded")} + + + + } + > +
+ props.controller.setAmicodePath(event.currentTarget.value)} + onBlur={() => props.controller.commitAmicodePath()} + placeholder={language.t("settings.general.row.amicodePath.placeholder")} + spellcheck={false} + autocorrect="off" + autocomplete="off" + autocapitalize="off" + aria-label={language.t("settings.general.row.amicodePath.title")} + /> +
+
+
+ + {/* VSIX output path — only when devcontainer mode is ON */} + + +
+ props.controller.setVsixOutputPath(e.currentTarget.value)} + placeholder="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/workspaces/artifacts/" + spellcheck={false} + autocorrect="off" + autocomplete="off" + autocapitalize="off" + aria-label="VSIX output path" + /> +
+
+
) } diff --git a/packages/app/src/context/settings.tsx b/packages/app/src/context/settings.tsx index 0443ed28b..121cdcb9f 100644 --- a/packages/app/src/context/settings.tsx +++ b/packages/app/src/context/settings.tsx @@ -55,6 +55,8 @@ export interface Settings { enabled: boolean opencodePath: string amicodePath: string + devcontainerMode: boolean + vsixOutputPath: string } keybinds: Record permissions: { @@ -220,6 +222,8 @@ const defaultSettings: Settings = { enabled: false, opencodePath: "", amicodePath: "", + devcontainerMode: false, + vsixOutputPath: "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/workspaces/artifacts/", }, keybinds: {}, permissions: { @@ -575,6 +579,14 @@ export const { use: useSettings, provider: SettingsProvider } = createSimpleCont setAmicodePath(value: string) { setStore("developer", "amicodePath", value) }, + devcontainerMode: withFallback(() => store.developer?.devcontainerMode, defaultSettings.developer.devcontainerMode), + setDevcontainerMode(value: boolean) { + setStore("developer", "devcontainerMode", value) + }, + vsixOutputPath: withFallback(() => store.developer?.vsixOutputPath, defaultSettings.developer.vsixOutputPath), + setVsixOutputPath(value: string) { + setStore("developer", "vsixOutputPath", value) + }, }, } },