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
16 changes: 15 additions & 1 deletion docs/CORE-PATCHES.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ So a full rebuild from nothing is: `bootstrap.sh` (clone → brand → extension
To re-create the core patch after changing core files in `vscode/`:

```bash
# STRUCTURAL patch only (11 files). Display-string rebrands are NOT here — they live in scripts/de-brand.mjs.
# STRUCTURAL patch only (14 files). Display-string rebrands are NOT here — they live in scripts/de-brand.mjs.
git -C vscode diff HEAD -- \
src/vs/workbench/contrib/files/browser/files.contribution.ts \
build/lib/extensions.ts build/lib/copilot.ts \
Expand All @@ -38,12 +38,22 @@ git -C vscode diff HEAD -- \
src/vs/workbench/contrib/welcomeOnboarding/browser/welcomeOnboarding.contribution.ts \
src/vs/workbench/api/node/loopbackServer.ts \
src/vs/workbench/contrib/chat/browser/chatSetup/chatSetupContributions.ts \
src/vs/base/common/product.ts \
src/vs/platform/dialogs/electron-browser/dialog.ts \
src/vs/workbench/contrib/update/browser/updateTooltip.ts \
> patches/levelcode-core.patch
# NOTE 1: use `diff HEAD` (not plain `diff`) — bootstrap's `git apply` may leave these STAGED,
# and plain `git diff` shows only UNSTAGED changes, silently dropping the staged patches.
# NOTE 2: regenerate BEFORE running de-brand.mjs. de-brand's global MS-doc-link sweep (pass 2) also
# strips links from `files.contribution.ts` (a patched file); if you regen after de-brand, those
# link-strips leak into the patch. Order: patch → regen → de-brand (this is also bootstrap's order).
# NOTE 3: if the checkout is ALREADY de-branded (the usual case after a build) do NOT regen wholesale —
# it silently fattened `files.contribution.ts` by ~95 lines of link-strips the one time we tried. Instead
# APPEND only the new file's entry to the existing patch, which stays valid because bootstrap applies it
# to a fresh, pre-de-brand checkout:
# git -C vscode diff HEAD -- <only/the/new/file.ts> >> patches/levelcode-core.patch
# Then verify: the pre-existing entries are byte-identical to before, the appended entry contains no
# `aka.ms`/`code.visualstudio.com` strips, and `git -C vscode apply --check --reverse` accepts it.
```

To find every core touch in the checkout:
Expand Down Expand Up @@ -74,6 +84,10 @@ compile-client` after touching a core file.**
| 10 | `api/node/loopbackServer.ts` (`getHtml`) | Replace the `this._appName === 'Visual Studio Code'` / `'… - Insiders'` branches (which embedded the VS Code stable/Insiders **shields**, falling through to the blue VS Code **"book"** default) with a single **LevelCode chevron** data-URI. | De-brand (WS-C/L5). A latent bug **and** a leak: `appName` is now "LevelCode" so no branch matched → every GitHub OAuth success page flashed a VS Code logo. `this._appName` is still used in the page text, so no unused-field error. |
| 11 | `chat/browser/chatSetup/chatSetupContributions.ts` | **Hide** the two GitHub Copilot sign-in call-to-actions (Accounts menu + title bar) with `when: ContextKeyExpr.false()`, and drop the two imports that becomes unused (`ChatEntitlementContextKeys`, `InEditorZenModeContext` — strict build). | De-brand (WS-A/B4). A sign-in funnel for an extension we don't ship. **Gated per MENU ITEM on purpose.** The previous attempt forced `IChatEntitlementService.setForceHidden(true)` instead — but `Setup.hidden` is upstream's *hide-ALL-chat/agent-UI* flag, not a CTA gate: it also gates `OPEN_AGENTS_WINDOW_PRECONDITION` (constants.ts), the **Agent Plugins view**, chat participants and several chat/plugin actions. That shipped in v0.6.0–v0.7.0 and made **Open Agents Window disappear from the Command Palette**. Never gate a de-brand on `Setup.hidden`; gate the item. |

| 12 | `src/vs/base/common/product.ts` | Add optional `levelcodeVersion` + `levelcodeReleaseDate` to `IProductConfiguration`. | `version` is the Code-OSS base (`1.126.0`) and **must stay 1.x** — it is what extensions' `engines.vscode` is validated against, so it cannot be renamed to the LevelCode release. These carry the human-facing release identity alongside it. Stamped into the built `product.json` by `scripts/stamp-levelcode-version.mjs` (run from `build-macos.sh`, tag-derived); both optional, so a dev build with no reachable tag still renders. |
| 13 | `update/browser/updateTooltip.ts` | `Current Version:` shows `levelcodeVersion ?? version`; `Released` prefers `levelcodeReleaseDate`. | The update tooltip read **“Current Version: 1.126.0 (cdf2549)” — a Code-OSS version next to a LevelCode commit**, and a “Released” date from the upstream base's build (months before the build the user installed). Reported as counter-intuitive after the first real auto-update. `Latest Version:` already used the feed's `productVersion` and needed no change. |
| 14 | `platform/dialogs/electron-browser/dialog.ts` | About shows `0.8.0 … — Code-OSS 1.126.0`, and the Date row prefers `levelcodeReleaseDate`. | Same root cause as #13, on the native About dialog. Keeps **both** here on purpose: About is pasted into bug reports, so the release version identifies the build while the base version explains extension-compatibility behaviour. |

## String + link rebrands — `scripts/de-brand.mjs` (NOT in the patch)

Content-based replacements — matched on the string, not its line number — so they survive upstream line
Expand Down
101 changes: 101 additions & 0 deletions patches/levelcode-core.patch
Original file line number Diff line number Diff line change
Expand Up @@ -290,3 +290,104 @@ index 1a1f1e1d..56af75ac 100644
- order: 3
-});
+// [LevelCode] Removed the Help ▸ "Editor Playground" menu item (Microsoft VS Code walkthrough content).
diff --git a/src/vs/base/common/product.ts b/src/vs/base/common/product.ts
index 5a1a2018..7d238537 100644
--- a/src/vs/base/common/product.ts
+++ b/src/vs/base/common/product.ts
@@ -89,6 +89,25 @@ export interface IProductConfiguration {
readonly quality?: string;
readonly commit?: string;

+ /**
+ * [LevelCode] The LevelCode RELEASE version ("0.8.0"), stamped at build time from the git tag.
+ *
+ * Distinct from `version`, which is and must remain the Code-OSS base ("1.126.0") — that is the
+ * value extensions' `engines.vscode` is validated against, so renaming it would reject every
+ * extension requiring ^1.x. Use this for anything a HUMAN reads (update UI, About); use `version`
+ * for compatibility checks. Absent in dev builds, so always fall back to `version`.
+ */
+ readonly levelcodeVersion?: string;
+
+ /**
+ * [LevelCode] When this build's commit was COMMITTED (ISO 8601, git `%cI`), stamped alongside
+ * `levelcodeVersion`. Committer date, not author date: "Released" means when the build's commit
+ * landed, and a cherry-picked or rebased commit's author date can predate the release by weeks.
+ * `date` is the Code-OSS build date and is misleading in release UI — it reflects the upstream base,
+ * not when this LevelCode release shipped.
+ */
+ readonly levelcodeReleaseDate?: string;
+
readonly nameShort: string;
readonly nameLong: string;

diff --git a/src/vs/platform/dialogs/electron-browser/dialog.ts b/src/vs/platform/dialogs/electron-browser/dialog.ts
index ec26804c..766198c2 100644
--- a/src/vs/platform/dialogs/electron-browser/dialog.ts
+++ b/src/vs/platform/dialogs/electron-browser/dialog.ts
@@ -11,19 +11,33 @@ import { IProductService } from '../../product/common/productService.js';
import { process } from '../../../base/parts/sandbox/electron-browser/globals.js';

export function createNativeAboutDialogDetails(productService: IProductService, osProps: IOSProperties): { title: string; details: string; detailsToCopy: string } {
- let version = productService.version;
+ // [LevelCode] Lead with the RELEASE version — that is what a user installed and what the release,
+ // the update feed and the download page all call it. `productService.version` is the Code-OSS base
+ // ("1.126.0") and must stay 1.x for extensions' engines.vscode, so it cannot simply be renamed.
+ // About is a diagnostics surface people paste into bug reports, so keep BOTH: the release version
+ // identifies the build, the base version explains extension-compatibility behaviour. Dev builds
+ // carry no stamp and fall back to showing the base alone.
+ let version = productService.levelcodeVersion ?? productService.version;
if (productService.target) {
version = `${version} (${productService.target} setup)`;
} else if (productService.darwinUniversalAssetId) {
version = `${version} (Universal)`;
}
+ if (productService.levelcodeVersion) {
+ version = `${version} — Code-OSS ${productService.version}`;
+ }

const getDetails = (useAgo: boolean): string => {
return localize({ key: 'aboutDetail', comment: ['Electron, Chromium, Node.js and V8 are product names that need no translation'] },
"Version: {0}\nCommit: {1}\nDate: {2}\nElectron: {3}\nElectronBuildId: {4}\nChromium: {5}\nNode.js: {6}\nV8: {7}\nOS: {8}",
version,
productService.commit || 'Unknown',
- productService.date ? `${productService.date}${useAgo ? ' (' + fromNow(new Date(productService.date), true) + ')' : ''}` : 'Unknown',
+ // [LevelCode] Same reasoning as the version: `date` is the Code-OSS base's build date, reading as a
+ // release date months before the build the user actually installed.
+ (() => {
+ const d = productService.levelcodeReleaseDate ?? productService.date;
+ return d ? `${d}${useAgo ? ' (' + fromNow(new Date(d), true) + ')' : ''}` : 'Unknown';
+ })(),
process.versions['electron'],
process.versions['microsoft-build'],
process.versions['chrome'],
diff --git a/src/vs/workbench/contrib/update/browser/updateTooltip.ts b/src/vs/workbench/contrib/update/browser/updateTooltip.ts
index 85e34cca..3f9b67ba 100644
--- a/src/vs/workbench/contrib/update/browser/updateTooltip.ts
+++ b/src/vs/workbench/contrib/update/browser/updateTooltip.ts
@@ -137,7 +137,11 @@ export class UpdateTooltip extends Disposable {
}

private updateCurrentVersion() {
- const productVersion = this.productService.version;
+ // [LevelCode] Show the RELEASE version ("0.8.0"), never `version` — that is the Code-OSS base
+ // ("1.126.0"), which must stay 1.x for extensions' engines.vscode. Pairing it with a LevelCode
+ // commit made this read as a different product than the one that was installed. Dev builds carry
+ // no stamp, so fall back rather than showing nothing.
+ const productVersion = this.productService.levelcodeVersion ?? this.productService.version;
if (productVersion) {
const currentCommitId = this.productService.commit?.substring(0, 7);
this.currentVersionNode.textContent = currentCommitId
@@ -381,8 +385,11 @@ export class UpdateTooltip extends Disposable {
this.latestVersionNode.parentElement!.style.display = 'none';
}

- // Release date
- const releaseDate = update?.timestamp ?? tryParseDate(this.productService.date);
+ // [LevelCode] Release date: for the running build prefer the LevelCode date — `date` is the Code-OSS
+ // base's build date, so it reported a "Released" months before the release the user installed.
+ const releaseDate = update?.timestamp
+ ?? tryParseDate(this.productService.levelcodeReleaseDate)
+ ?? tryParseDate(this.productService.date);
if (typeof releaseDate === 'number' && releaseDate > 0) {
this.releaseDateNode.textContent = localize('updateTooltip.releasedLabel', "Released {0}", formatDate(releaseDate));
this.releaseDateNode.style.display = '';
21 changes: 21 additions & 0 deletions scripts/build-macos.sh
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,27 @@ node "$SCRIPT_DIR/strip-proprietary.mjs" "$BUILT_APP/LevelCode.app/Contents/Reso
echo "[build] Hiding not-yet-ready features from the app (LevelCode Sync) …"
node "$SCRIPT_DIR/strip-unreleased.mjs" "$BUILT_APP/LevelCode.app/Contents/Resources/app"

# Stamp the LevelCode RELEASE version for anything a human reads (update tooltip, About). product.json
# `version` deliberately stays the Code-OSS base — it is what extensions' engines.vscode is validated
# against — so the release version rides ALONGSIDE it rather than replacing it. Without this the update
# UI reports "1.126.0", the upstream base, next to a LevelCode commit. See stamp-levelcode-version.mjs.
# Tag-derived. NOTE the deliberate absence of --abbrev=0: at the exact tag (what CI checks out for a
# release) `describe` returns a clean "v0.8.0", but OFF the tag it returns "v0.8.0-1-g404ef20". With
# --abbrev=0 a dev build five commits past a release stamps a bare "0.8.0" and impersonates it in the
# UI; the suffix makes such a build self-identifying. A checkout with no reachable tag skips the stamp
# entirely and the UI falls back to `version`.
# %cI is the COMMITTER date on purpose: "Released" means when this build's commit landed, not when the
# work was originally written — a cherry-picked commit's author date can predate the release by weeks.
if LC_VERSION="$(git -C "$ROOT_DIR" describe --tags 2>/dev/null)" && [ -n "$LC_VERSION" ]; then
LC_DATE="$(git -C "$ROOT_DIR" log -1 --format=%cI HEAD 2>/dev/null || true)"
echo "[build] Stamping the LevelCode release version ($LC_VERSION) …"
node "$SCRIPT_DIR/stamp-levelcode-version.mjs" \
"$BUILT_APP/LevelCode.app/Contents/Resources/app" "$LC_VERSION" "$LC_DATE"
else
echo "[build] WARN: no reachable git tag — skipping the release-version stamp. The update UI will"
echo "[build] show the Code-OSS base version instead of a LevelCode one."
fi

# The .app inside is named from product.json nameLong -> "LevelCode.app".
echo "[build] Done."
echo "[build] Output folder: $BUILT_APP"
Expand Down
77 changes: 77 additions & 0 deletions scripts/stamp-levelcode-version.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/*---------------------------------------------------------------------------------------------
* LevelCode — stamp the RELEASE version into the BUILT app's product.json.
*
* Usage: node stamp-levelcode-version.mjs <app>/Contents/Resources/app <version> [isoDate]
*
* WHY THIS EXISTS
* product.json `version` is, and must remain, the Code-OSS base ("1.126.0"). That value is what
* extensions' `engines.vscode` is validated against — set it to "0.8.0" and every extension
* requiring ^1.x is rejected. But it is also what the update UI and About dialog display, so a
* LevelCode build reported itself as "1.126.0", released on the upstream base's build date, while
* carrying a LevelCode commit. Half Code-OSS, half LevelCode, and confusing either way.
*
* So we add SEPARATE, human-facing fields rather than overloading `version`:
* levelcodeVersion "0.8.0" ← from the git tag
* levelcodeReleaseDate "2026-07-20T02:51:22Z" ← when this build's commit was COMMITTED (%cI)
*
* Compatibility checks keep reading `version`; humans read these. Both are optional in the type,
* so a dev build with neither still renders (it falls back to `version`).
*
* Idempotent + loud: re-stamping overwrites, and every outcome is printed.
*--------------------------------------------------------------------------------------------*/
import fs from "node:fs";
import path from "node:path";

const appDir = process.argv[2];
const version = process.argv[3];
const isoDate = process.argv[4];

if (!appDir || !version) {
console.error("[stamp] ERROR: usage: stamp-levelcode-version.mjs <.../Resources/app> <version> [isoDate]");
process.exit(1);
}
if (!fs.existsSync(appDir)) {
console.error(`[stamp] ERROR: ${appDir} not found — pass the built app's Contents/Resources/app dir.`);
process.exit(1);
}

// A release version, not a tag: "v0.8.0" would render as "Current Version: v0.8.0".
//
// Accepted: X.Y.Z, optionally followed by a `-` or `+` suffix. The suffix is REQUIRED, not tolerated —
// build-macos.sh runs `git describe --tags` without --abbrev=0, so an off-tag build passes
// "v0.8.0-1-g404ef20" and must keep that suffix: it is exactly what stops a dev build from
// impersonating the release it happens to sit after. An exact-tag build (what CI does for a release)
// passes a clean "v0.8.0" and gets a clean "0.8.0".
//
// Rejected: anything else — "v0.8", a branch name, an empty describe. Those exit non-zero so the build
// fails rather than shipping a nonsense product version.
const clean = String(version).replace(/^v/, "");
if (!/^\d+\.\d+\.\d+([-+].+)?$/.test(clean)) {
console.error(`[stamp] ERROR: "${version}" is not X.Y.Z or X.Y.Z-<suffix> — refusing to stamp.`);
process.exit(1);
}

const productPath = path.join(appDir, "product.json");
try {
const product = JSON.parse(fs.readFileSync(productPath, "utf8"));
const previous = product.levelcodeVersion;

product.levelcodeVersion = clean;
if (isoDate && !Number.isNaN(Date.parse(isoDate))) {
product.levelcodeReleaseDate = new Date(isoDate).toISOString();
} else if (isoDate) {
console.warn(`[stamp] WARN: ignoring unparseable date "${isoDate}" — the UI will fall back to product.date.`);
}

fs.writeFileSync(productPath, JSON.stringify(product, null, "\t") + "\n");
console.log(
`[stamp] product.json levelcodeVersion = ${clean}` +
(product.levelcodeReleaseDate ? `, levelcodeReleaseDate = ${product.levelcodeReleaseDate}` : "") +
(previous && previous !== clean ? ` (was ${previous})` : "")
);
// `version` stays put on purpose — see the header.
console.log(`[stamp] product.json version left at ${product.version} (Code-OSS base, for engines.vscode).`);
} catch (e) {
console.error(`[stamp] ERROR: could not stamp ${productPath}: ${(e && e.message) || e}`);
process.exit(1);
}