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
14 changes: 14 additions & 0 deletions CONTEXT.md
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,20 @@ _Avoid_: Explorer (VS Code's native file explorer is separate), Panel (the in-ap
The multi-document file workspace in the side panel. Holds zero or more files as inner tabs, each rendering its content (markdown rendered with a toggle to edit; text/code files in a CodeMirror editor; images and PDFs inline). Files arrive via Sidebar single-click or a Chat file pill and accumulate as tabs — each closeable and drag-reorderable; opening an already-open file focuses its existing tab rather than duplicating it. A breadcrumb bar under each pane's tab strip shows the active file's project-relative path with interactive sibling navigation. Supports recursive split panes via edge-drop: dragging a tab toward a pane's edge divides the view, and each resulting pane keeps its own tab bar, breadcrumb, zoom, and preview/edit toggle. Empty panes auto-collapse; a minimum pane dimension is enforced so splits can't shrink below a usable size. Opens and activates automatically when the first file is selected; shows a placeholder when no file is open. For committed editing, double-click the Sidebar entry to open a native VS Code tab.
_Avoid_: Editor (Preview is a multi-document viewer, not a primary editor — committed editing belongs in a native VS Code tab), File browser (the Sidebar is still the primary project-wide file tree; the breadcrumb is a contextual sibling-navigation aid scoped to the open file, not a second tree)

### Developer tooling

**Rebuild (local)**:
One of the two developer-tools Rebuild buttons. Builds the amicode binary and extension from the current working tree exactly as it sits — no git checkout, no pull — so a developer's in-progress edits are what gets built. The inner-loop "build what I have" action. Its request mode is `local`.
_Avoid_: Rebuild Remotely (retired wording), local build (as the concept name — it is a Rebuild mode)

**Rebuild (from main)**:
The other developer-tools Rebuild button. Syncs the amicode repo to `origin/main` (fetch + checkout main + fast-forward-only pull) and then builds — the "reset to the shared tip and build that" action. Its request mode is `main`. The two Rebuild buttons diverge only at this git step; every later build phase is identical.
_Avoid_: Rebuild Remotely / Rebuild from Latest (retired wordings), remote rebuild (the fork-era `remote` mode is retired)

**binary live-swap (retired)**:
The retired mechanism by which the developer-tools "opencode repo path" field resolved a fork binary from a path on disk and set the `opencodeBinary` override to it, restarting the server. Retired with fork absorption (#1091/#1115): the binary is now produced by the in-repo overlay build, so the full local Rebuild is the single dev build path. The general `opencodeBinary` override itself survives — it is consumed by the boot/health paths and still cleared on the developer-mode toggle-off; only the path-field-fed swap is gone.
_Avoid_: opencode repo path field (removed), binary override (the general override is not the live-swap)

### Orthogonal axes

**Domain Pack**:
Expand Down
54 changes: 54 additions & 0 deletions docs/adr/0018-rebuild-button-semantics.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# The developer Rebuild has two modes — local builds the working tree, main syncs first — and the binary live-swap is retired

Status: accepted (2026-09-13)

The developer-tools Rebuild surface offers two buttons, and after the opencode fork was
absorbed (#1091) the amicode binary is built from the overlay/materialized tree in-repo —
there is no longer a separate opencode repository to point at or a fork binary to swap in.
This ADR records what the two buttons now mean and why the field that fed the old binary
live-swap is gone.

**The two modes.** The Rebuild request carries a `mode` with exactly two values.
`local` builds the current working tree **as it sits** — no git checkout, no pull — so a
developer's in-progress edits are what gets built. `main` syncs the amicode repo to
`origin/main` first (`git fetch origin && git checkout main && git pull --ff-only origin
main`) and then builds. The two are intentionally non-interchangeable: `local` is the
inner-loop "build what I have" action, `main` is the "reset to the shared tip and build
that" action. The vocabulary is `local | main` end to end — UI button, controller
payload, extension-host handler, and tests — replacing the fork-era `remote` value, which
is retired and resolves to the safe, non-destructive `local` default if it is ever seen.

**Why they must differ observably.** Before this decision the handler ignored `mode` and
always ran the checkout-main + fast-forward pull, so "Rebuild Locally" silently discarded
the working tree by pulling to main — the two buttons were identical and the local button
was actively misleading. The git step is now the single point where the two modes diverge,
derived from `mode` by one pure function so the difference is directly testable: `local`
emits no git command, `main` emits the fetch/checkout/ff-pull.

**The binary live-swap is retired.** The old surface carried an "opencode repo path" field
whose only job was to resolve a fork binary from a path on disk and set the
`amicode.opencodeBinary` override to it (a live-swap), restarting the server. In the
overlay-build world that path points at nothing meaningful: the binary is produced by the
in-repo build, not resolved from a sibling fork checkout. The field, its controller
getter/setter/commit, its settings-context entry, its localization keys, and the two
path-fed binary writes in the update handler are all removed. The full local Rebuild is now
the single dev build path.

**What is preserved.** The general `amicode.opencodeBinary` override setting is *not* the
live-swap and is kept intact: it is consumed by the fleet-health and boot-override paths,
and the developer-mode toggle-off must still clear it (alongside `devAssetRoot`) when
restoring the marketplace build. Only the binary write that the removed path field fed is
gone; the override's boot/health consumers and its toggle-off clear are untouched.

**Alternatives considered.** Keeping a single Rebuild button was rejected — developers
genuinely need both "build my tree" and "build the shared tip", and collapsing them is what
produced the silent data-losing pull. Repurposing the opencode-path field to point at the
amicode repo was rejected as redundant with the existing amicode-path field. Retiring the
buttons in favor of a CLI-only rebuild was rejected as a workflow regression for the
in-editor developer loop.

**Consequence.** Retiring the fork shell scripts (S9) and the orphaned coordinator
main-path (S10) are follow-ups outside this slice; this decision is the request surface and
the removal, not a change to the overlay build engine itself.

Implementation: harmoniqs/amicode#1115
46 changes: 23 additions & 23 deletions packages/app-bundle/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -161,31 +161,31 @@
"packages/app/src/context/server-session.ts": "a9e1f089ad328a705848d11ba25e890c5e374bb3a33066ffe67b6f09bd48ed16",
"packages/app/src/context/server-sync.tsx": "43154ffe6227203d778378d190af3c46d7f49098619721fea5e6263e8794dc84",
"packages/app/src/context/server.tsx": "08e978e4b4b3094221a2a0b92e069f39cbedb6d6cf95935881e21cde52265ee2",
"packages/app/src/context/settings.tsx": "53025fbfbd7eb641cdb0e7c49e21ced3617050f4f98ee45c745affdd8ab17b48",
"packages/app/src/context/settings.tsx": "19e078e81620ffa21a92797335009b84d4311485a37c5ede2f76906b734292ad",
"packages/app/src/context/split.tsx": "891aeb290b369b327a63ce548dcaab1daca08e41fab430648787be7f84e53ec0",
"packages/app/src/context/tabs.tsx": "eb494b5785defdd3a4901874be355ba8ddd7bab4c009359b558c413f9ee63ffa",
"packages/app/src/context/vault-panel.ts": "f91cb5fb49a9f0d46bd2a1b131f9a218b30147e2ad3617d2a2dc4f1e14599a53",
"packages/app/src/context/workbench.tsx": "69a44da2b69989bd2c67ebffdb49941841f3f4718d6a70d659c828b57928d9eb",
"packages/app/src/context/zoom-keybind.test.ts": "a1385c93639687a7dac7d21f8f02841d82a55e42465ce3836b97b16c82b52469",
"packages/app/src/i18n/ar.ts": "b4f9ca56ed157c34f6248bc053f09c1ce565159a30eb971570c77884b780e686",
"packages/app/src/i18n/br.ts": "1bef54ba8796b2c94ff6e6b1c40f924a130f203da919b096c1ea6d363ee28e50",
"packages/app/src/i18n/bs.ts": "1317264cf4bd5f6ae1fb0636209c6f169978eb62685ea3c1f7ab80c34c3bf499",
"packages/app/src/i18n/da.ts": "dea807e84b8cc7f9410dd492682523f51966bc79d0f8f47fbc83afd47cad52ea",
"packages/app/src/i18n/de.ts": "fa9a91f52895e4dc421847595bf2ed0a8e88d4ca8afd7c50c4fcb063979f3512",
"packages/app/src/i18n/en.ts": "da4fde0a74423baca743182041a4028e6b18650e01b6dc8d5b81974d75e7ace2",
"packages/app/src/i18n/es.ts": "d16b85c3b53558f74dbbc16866c253102920438c4df5488a7b1ff9ba431f733a",
"packages/app/src/i18n/fr.ts": "9f6871fe2556b6ef864a84a285d95793a242f92c94b8b6a8f3774cb045aea929",
"packages/app/src/i18n/ja.ts": "46f8392f5de380a3212afd73d879837a6e1c70b986188de7ee6c1fd3ae7dca96",
"packages/app/src/i18n/ko.ts": "fdc97342c447915cbf5d6387c07d4c445c53f54d4bb669f876d7344876ca8bd4",
"packages/app/src/i18n/no.ts": "6e1a75c32e3ea0d7576da0255316a8ab450e1e3d963db3d5b92d25c55971b987",
"packages/app/src/i18n/pl.ts": "cbbdb0bd13405da313bba396a91356500bcc95d3a68f68060689773200b49442",
"packages/app/src/i18n/rebuild-label-rename.test.ts": "c9ad20e491a5c9c6b8f6d56858271988e827398afe8b7cbe333e5f970fe76142",
"packages/app/src/i18n/ru.ts": "43f38453f2192102dace0690bce8b81a60e376bfa44f62133cb99ae55a392125",
"packages/app/src/i18n/th.ts": "527fd9de237b2397063c3ee39de62856d9c174af4b62afc48b53f266cd2ab533",
"packages/app/src/i18n/tr.ts": "08f8794e6858898ec0dcdd8b0c9ae7dae218ef3f0878354b1b0f1282c3727877",
"packages/app/src/i18n/uk.ts": "7b534484e8abd0f3efcd0447efa414cdf7c2694c59e614767477d5578c570d8c",
"packages/app/src/i18n/zh.ts": "d31d243ecaf762b747a67354c7f929317fd340cf6896b6cbd2b402e55ca0bf9a",
"packages/app/src/i18n/zht.ts": "e21b1f58be6482bcd3121861b3ed6620acc8878ae1729fdff2b05cce92eedac4",
"packages/app/src/i18n/ar.ts": "61d4386d766bf6c870a23963d75da3910d6b882431d038728e01bff50d3c285a",
"packages/app/src/i18n/br.ts": "fb90ce7d989b06d61ff2792f63ebdf17f828e5233553817a8be084996471e1b5",
"packages/app/src/i18n/bs.ts": "eb6bd1f45e7f6bd70b9ace7ca85150de1c0a19ada66507ac97459be44419ea06",
"packages/app/src/i18n/da.ts": "18ee0ecb46c02bac0c9f85126515167427c4ab496aaa9bce552de45742843c0d",
"packages/app/src/i18n/de.ts": "c6a954bda39d8a7fda7d93c9bfc6d954c4e403675a9c95389f9e1d0ac4064f06",
"packages/app/src/i18n/en.ts": "83e4410780d17104a49765b07256e1b4f6d6da15b848029dbd5910ecb0b829e7",
"packages/app/src/i18n/es.ts": "1996572d2aff5931bb6fe3f634b3b055ac8c08327d22338939ff2121120110bc",
"packages/app/src/i18n/fr.ts": "fd496581ff6825ea323dec3ae18c2e1da4694a924fad9f1536a5a9bf1c1adb7a",
"packages/app/src/i18n/ja.ts": "3d37435f6761b0210db71ac85b8df5b8a0a31af61ad12a26374b595e0f3077ab",
"packages/app/src/i18n/ko.ts": "d10f60b9abd1b5ffbe0deaed8db4b17e77c9ba2c230b7b832bc4df6f48a7c5ee",
"packages/app/src/i18n/no.ts": "d6f23e78bb2b9f8a8a7ca35fa9a9fe8857c78fbe2e4efa93ad688bb00442502d",
"packages/app/src/i18n/pl.ts": "2479ba155669ce47d81526b426d0cbb9aa601884b00b258b71a86a6ce09d7f4a",
"packages/app/src/i18n/rebuild-label-rename.test.ts": "c481e79fcef5bd2edebdc3f4818afa8588787cb82110651cb7769958b356351d",
"packages/app/src/i18n/ru.ts": "6d3107cd3436d8ab6a1b5086c337af5331166acce2b51b2169bb3c1e75a6a81e",
"packages/app/src/i18n/th.ts": "81ad962167450fbdbb52f50ade1c2f49823fafcfdb3e781204f788c4ed696397",
"packages/app/src/i18n/tr.ts": "28862f80793b2e92c64280cb923b6c2321b04b8fea8da574220fd36abef4363c",
"packages/app/src/i18n/uk.ts": "428ad662bd8389e86458dc18eeb69e262006f6065ae183a6e082d879181d034a",
"packages/app/src/i18n/zh.ts": "99cb8baa717976e4f7e1bc682cffaa58661030d819d1b40d9dec6f8b56453c80",
"packages/app/src/i18n/zht.ts": "c330050df718d50f7cc55eb8ab4a1a5cde859fad9ca95f68315f0c5f399e36aa",
"packages/app/src/pages/error.tsx": "db9ba0847cfb205100ad6133cce30c6ea2ce983802af44947e6d2b746c2a6ec5",
"packages/app/src/pages/home-projects.test.ts": "a90155c37210a0e5b7a095bbfc3ba2d40681f02927d9726148a706bed5ba7891",
"packages/app/src/pages/home-projects.ts": "d55124578dc839b98e2084b488d818e30e4a1abf1ae264499b83e403c2aabca7",
Expand Down Expand Up @@ -611,12 +611,12 @@
"packages/app/src/components/session/vscode-explorer-file-icon.tsx": "b5ba0cafe61594dd2afe45ef6e070e512861162933c85e9545db2ec697d50f17",
"packages/app/src/components/settings-v2/data-storage-controller.ts": "fa5d143cc101f3a3b9d5ad445edddc981e0d02783021d52dbfed6c8d8bf62498",
"packages/app/src/components/settings-v2/data-storage.tsx": "a56b423b32c952c4379688d88d13a9f43b163a4169b9393f8c35b18a763a1180",
"packages/app/src/components/settings-v2/developer-tools-controller.ts": "8888e7da23d0e092c531c706470edb7ae410aef1aa8f3067729b3f7dfec5f48d",
"packages/app/src/components/settings-v2/developer-tools-controller.ts": "ad7bd9bd13271e98a074f59a479efda1a7a874e04086e8eaa3c67422c27cbd3c",
"packages/app/src/components/settings-v2/developer-tools-rebuild-flags.test.ts": "4d11815fe5acfa930bc418d0be82602f03821997300d0deef0aa2da2a743b3b0",
"packages/app/src/components/settings-v2/developer-tools-rebuild-flags.ts": "561a75fa456b832d4af6f782c7ecf5269ce768fc2adc59c15a79197a2dde84c0",
"packages/app/src/components/settings-v2/developer-tools-request-state.test.ts": "29711b3bc17fddbb8d046e104bc78e775a9ec7c5aa0ee2e181fbcd8b2b5e2135",
"packages/app/src/components/settings-v2/developer-tools-request-state.ts": "e9678ed32e7c2562b18143f39a51c0a94dbf0a0884d056653c6c77e4cd07a9bd",
"packages/app/src/components/settings-v2/developer-tools.tsx": "ee2d586cfa1202f5fc253b502dd8752256166192541e7addab6bb5da18d1d0f5",
"packages/app/src/components/settings-v2/developer-tools.tsx": "0e3a739a1ed3cb6311f93174080a3bce71219cee9bcaccd0719d3bf9e6932988",
"packages/app/src/components/settings-v2/dialog-settings-v2.tsx": "a1e1fb7eea7bbad7708b97a721b54a4897c8cb6f582b71d6bb7c8ca7f44930db",
"packages/app/src/components/settings-v2/general.tsx": "2abf6c9c7b4b9bf020ed224dbc7fff4574db47711956fc08e1b8e84e781d395a",
"packages/app/src/components/settings-v2/permission-scope-relocation.test.ts": "39539c4fc4b7433f08b913100f48a7fa75d6ab47615ba697aa9ae8eee09d316b",
Expand Down Expand Up @@ -968,7 +968,7 @@
"review": "harmoniqs/amicode#1004"
}
],
"extracted_at": "2026-09-14T04:43:06.305Z",
"extracted_at": "2026-09-14T05:04:46.530Z",
"per_package": {
"packages/app": {
"A": 164,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export function createDeveloperToolsController() {

// 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
// (e.g. git checkout in a watched workspace folder during remote rebuild).
// (e.g. git checkout in a watched workspace folder during a from-main rebuild).
onMount(() => {
try {
const wasRebuilding = localStorage.getItem("amicode:devtools-rebuilding") === "1"
Expand Down Expand Up @@ -179,14 +179,13 @@ export function createDeveloperToolsController() {
source: "amicode",
kind: "dev-tools-update",
enabled: settings.developer.enabled(),
opencodePath: settings.developer.opencodePath(),
amicodePath: settings.developer.amicodePath(),
},
"*",
)
}

const rebuild = (mode: "local" | "remote") => {
const rebuild = (mode: "local" | "main") => {
if (!inAmicode()) return
if (rebuildState() === "rebuilding") return // prevent double-clicks
setRebuildState("rebuilding")
Expand All @@ -197,7 +196,6 @@ export function createDeveloperToolsController() {
source: "amicode",
kind: "dev-tools-rebuild",
mode,
opencodePath: settings.developer.opencodePath(),
amicodePath: settings.developer.amicodePath(),
},
"*",
Expand Down Expand Up @@ -229,29 +227,21 @@ export function createDeveloperToolsController() {
source: "amicode",
kind: "dev-tools-update",
enabled: false,
opencodePath: settings.developer.opencodePath(),
amicodePath: settings.developer.amicodePath(),
},
"*",
)
}
},
opencodePath: settings.developer.opencodePath,
setOpencodePath: (value: string) => {
settings.developer.setOpencodePath(value)
},
amicodePath: settings.developer.amicodePath,
setAmicodePath: (value: string) => {
settings.developer.setAmicodePath(value)
},
/** 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) */
/** Trigger a full rebuild (local = build the working tree as-is; main = checkout main + ff-pull first) */
rebuild,
status,
pending,
Expand All @@ -275,7 +265,6 @@ export function createDeveloperToolsController() {
window.parent.postMessage({
source: "amicode",
kind: "dev-tools-build-vsix",
opencodePath: settings.developer.opencodePath(),
amicodePath: settings.developer.amicodePath(),
outputPath: settings.developer.vsixOutputPath(),
}, "*")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,11 +73,6 @@ const RebuildErrorPanel: Component<{ controller: DeveloperToolsController }> = (

const DeveloperToolsContent: Component<{ controller: DeveloperToolsController }> = (props) => {
const language = useLanguage()
const opencodeError = () => {
const s = props.controller.status()
if (!s || s.opencodeValid) return undefined
return s.opencodeError ?? language.t("settings.general.row.opencodePath.error.notFound")
}
const amicodeError = () => {
const s = props.controller.status()
if (!s || s.amicodeValid) return undefined
Expand All @@ -103,7 +98,7 @@ const DeveloperToolsContent: Component<{ controller: DeveloperToolsController }>
<ButtonV2
size="small"
variant="neutral"
onClick={() => props.controller.rebuild("remote")}
onClick={() => props.controller.rebuild("main")}
disabled={isRebuilding()}
>
{language.t("settings.general.row.devTools.rebuildRemotely")}
Expand Down Expand Up @@ -162,40 +157,6 @@ const DeveloperToolsContent: Component<{ controller: DeveloperToolsController }>

{/* 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" classList={{ "settings-v2-field-stale": validating() }}>
{opencodeError()}
</span>
</Show>
<Show when={validating()}>
<span class="settings-v2-field-info">Validating…</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={
Expand Down
Loading
Loading