Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ export function createDeveloperToolsController() {
const [pending, setPending] = createSignal(false)
const [rebuildState, setRebuildState] = createSignal<RebuildState>("idle")
const [rebuildError, setRebuildError] = createSignal<string | undefined>(undefined)
const [vsixBuildState, setVsixBuildState] = createSignal<RebuildState>("idle")
const [vsixBuildError, setVsixBuildError] = createSignal<string | undefined>(undefined)
const [vsixPath, setVsixPath] = createSignal<string | undefined>(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
Expand Down Expand Up @@ -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")
}
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}
}

if (typeof window !== "undefined") {
Expand Down Expand Up @@ -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,
}
}

Expand Down
192 changes: 126 additions & 66 deletions packages/app/src/components/settings-v2/developer-tools.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ const DeveloperToolsContent: Component<{ controller: DeveloperToolsController }>

return (
<SettingsListV2>
{/* Rebuild buttons */}
{/* Action buttons row — at the top for discoverability */}
<Show when={props.controller.enabled()}>
<div class="devtools-rebuild-row">
<ButtonV2
Expand All @@ -78,7 +78,32 @@ const DeveloperToolsContent: Component<{ controller: DeveloperToolsController }>
</ButtonV2>
</div>
</Show>
<Show when={props.controller.devcontainerMode()}>
<div class="devtools-rebuild-row">
<ButtonV2
size="small"
variant="neutral"
onClick={() => props.controller.buildVsix()}
disabled={props.controller.vsixBuildState() === "rebuilding"}
>
{props.controller.vsixBuildState() === "rebuilding" ? "Building VSIX..." : "Build VSIX"}
</ButtonV2>
</div>
<Show when={props.controller.vsixBuildState() === "rebuilt"}>
<div class="devtools-rebuild-status devtools-rebuild-status--done">
<span class="devtools-status-dot devtools-status-dot--green" />
<span>VSIX built: {props.controller.vsixPath() ?? ""}</span>
</div>
</Show>
<Show when={props.controller.vsixBuildState() === "failed"}>
<div class="devtools-rebuild-status devtools-rebuild-status--failed">
<span class="devtools-status-dot devtools-status-dot--red" />
<span>{props.controller.vsixBuildError()}</span>
</div>
</Show>
</Show>

{/* Toggle 1: Developer mode (hot-reload, eager actions) */}
<SettingsRowV2
title={language.t("settings.general.row.developerMode.title")}
description={language.t("settings.general.row.developerMode.description")}
Expand All @@ -91,75 +116,110 @@ const DeveloperToolsContent: Component<{ controller: DeveloperToolsController }>
</div>
</SettingsRowV2>

{/* Toggle 2: Devcontainer mode (vsix build, no eager actions) — independent */}
<SettingsRowV2
title={language.t("settings.general.row.opencodePath.title")}
description={
<>
{language.t("settings.general.row.opencodePath.description")}
<Show when={opencodeError()}>
<span class="settings-v2-field-error">{opencodeError()}</span>
</Show>
</>
}
title="Devcontainer mode (experimental)"
description="Build a .vsix for manual installation instead of hot-reloading"
>
<div class="w-full sm:w-[280px]">
<TextInputV2
data-action="settings-opencode-path"
type="text"
appearance="base"
value={props.controller.opencodePath()}
onInput={(event) => 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")}
/>
</div>
<ToggleSwitch
checked={props.controller.devcontainerMode()}
onChange={(checked) => props.controller.setDevcontainerMode(checked)}
/>
</SettingsRowV2>

<SettingsRowV2
title={language.t("settings.general.row.amicodePath.title")}
description={
<>
{language.t("settings.general.row.amicodePath.description")}
<Show when={building()}>
<span class="settings-v2-field-info">
{language.t("settings.general.row.amicodePath.building")}
</span>
</Show>
<Show when={amicodeError()}>
<span class="settings-v2-field-error">{amicodeError()}</span>
</Show>
<Show when={reloadNeeded()}>
<span class="settings-v2-field-warning">
{language.t("settings.general.row.amicodePath.reloadNeeded")}
</span>
</Show>
</>
}
>
<div class="w-full sm:w-[280px]">
<TextInputV2
data-action="settings-amicode-path"
type="text"
appearance="base"
value={props.controller.amicodePath()}
onInput={(event) => 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")}
/>
</div>
</SettingsRowV2>
{/* Path inputs — visible when EITHER mode is ON */}
<Show when={props.controller.enabled() || props.controller.devcontainerMode()}>
<SettingsRowV2
title={language.t("settings.general.row.opencodePath.title")}
description={
<>
{language.t("settings.general.row.opencodePath.description")}
<Show when={opencodeError()}>
<span class="settings-v2-field-error">{opencodeError()}</span>
</Show>
</>
}
>
<div class="w-full sm:w-[280px]">
<TextInputV2
data-action="settings-opencode-path"
type="text"
appearance="base"
value={props.controller.opencodePath()}
onInput={(event) => 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")}
/>
</div>
</SettingsRowV2>

<SettingsRowV2
title={language.t("settings.general.row.amicodePath.title")}
description={
<>
{language.t("settings.general.row.amicodePath.description")}
<Show when={building()}>
<span class="settings-v2-field-info">
{language.t("settings.general.row.amicodePath.building")}
</span>
</Show>
<Show when={amicodeError()}>
<span class="settings-v2-field-error">{amicodeError()}</span>
</Show>
<Show when={reloadNeeded()}>
<span class="settings-v2-field-warning">
{language.t("settings.general.row.amicodePath.reloadNeeded")}
</span>
</Show>
</>
}
>
<div class="w-full sm:w-[280px]">
<TextInputV2
data-action="settings-amicode-path"
type="text"
appearance="base"
value={props.controller.amicodePath()}
onInput={(event) => 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")}
/>
</div>
</SettingsRowV2>
</Show>

{/* VSIX output path — only when devcontainer mode is ON */}
<Show when={props.controller.devcontainerMode()}>
<SettingsRowV2
title="VSIX output path"
description="Directory where the built .vsix is emitted"
>
<div class="w-full sm:w-[280px]">
<TextInputV2
type="text"
appearance="base"
value={props.controller.vsixOutputPath()}
onInput={(e) => props.controller.setVsixOutputPath(e.currentTarget.value)}
placeholder="/workspaces/artifacts/"
spellcheck={false}
autocorrect="off"
autocomplete="off"
autocapitalize="off"
aria-label="VSIX output path"
/>
</div>
</SettingsRowV2>
</Show>
</SettingsListV2>
)
}
Expand Down
12 changes: 12 additions & 0 deletions packages/app/src/context/settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ export interface Settings {
enabled: boolean
opencodePath: string
amicodePath: string
devcontainerMode: boolean
vsixOutputPath: string
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}
keybinds: Record<string, string>
permissions: {
Expand Down Expand Up @@ -220,6 +222,8 @@ const defaultSettings: Settings = {
enabled: false,
opencodePath: "",
amicodePath: "",
devcontainerMode: false,
vsixOutputPath: "/workspaces/artifacts/",
},
keybinds: {},
permissions: {
Expand Down Expand Up @@ -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)
},
},
}
},
Expand Down
Loading