From 548ea5deb1942051c609a6b6bdcce4e9ae2151ba Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Thu, 22 Oct 2026 13:37:28 -0700 Subject: [PATCH] feat(desktop): open What's new in beta from the command palette and Settings (#57940) --- packages/desktop/electron.vite.config.ts | 9 ++- .../src/updater/beta-dialog.stories.tsx | 31 ++++++++-- .../src/updater/beta-dialog.tsx | 29 +++++++--- .../src/updater/beta-notes.test.ts | 34 ++++++++--- .../gui-extensions/src/updater/beta-notes.ts | 21 +++++-- .../gui-extensions/src/updater/beta-page.tsx | 58 +++++++++++++++++++ .../gui-extensions/src/updater/i18n/en.ts | 4 +- .../gui-extensions/src/updater/renderer.tsx | 36 ++++++++++++ .../mocks/gui-extensions/settings-page.tsx | 7 +++ 9 files changed, 201 insertions(+), 28 deletions(-) create mode 100644 packages/gui-extensions/src/updater/beta-page.tsx create mode 100644 packages/stories/mocks/gui-extensions/settings-page.tsx diff --git a/packages/desktop/electron.vite.config.ts b/packages/desktop/electron.vite.config.ts index 619bac64d73..d80d820d144 100644 --- a/packages/desktop/electron.vite.config.ts +++ b/packages/desktop/electron.vite.config.ts @@ -55,9 +55,14 @@ export default defineConfig(async ({ command }) => { const channel = resolveChannel() const nodePtyPkg = nodePtyPackage() - // Beta builds carry their commit and recent history for the updater's "What's new in beta"; other channels none. + // Beta builds carry their commit and recent history for the updater's "What's new in beta"; the dev server carries + // them without asking GitHub, so the page and command can be tried; other channels none. const betaNotes = - channel === "beta" ? await BetaNotes.enrich(BetaNotes.embed(), { token: process.env.GITHUB_TOKEN }) : undefined + channel === "beta" + ? await BetaNotes.enrich(BetaNotes.embed(), { token: process.env.GITHUB_TOKEN }) + : command === "serve" + ? BetaNotes.embed() + : undefined return { main: { diff --git a/packages/gui-extensions/src/updater/beta-dialog.stories.tsx b/packages/gui-extensions/src/updater/beta-dialog.stories.tsx index 2260a8bb5dc..24ef2577731 100644 --- a/packages/gui-extensions/src/updater/beta-dialog.stories.tsx +++ b/packages/gui-extensions/src/updater/beta-dialog.stories.tsx @@ -1,8 +1,10 @@ import { Button } from "@opencode/ui/button" import { useDialog } from "@opencode/ui/context/dialog" import { Option } from "effect" -import { onMount } from "solid-js" +import { createSignal, onMount, Show } from "solid-js" +import { SettingsPanel } from "../../../stories/mocks/gui-extensions/settings-page" import { DialogBetaNotes } from "./beta-dialog" +import { BetaNotesView } from "./beta-page" import { sliceBetaNotes, type EmbeddedNotes } from "./beta-notes" type Author = { name: string; login?: string; avatar?: string } @@ -128,8 +130,10 @@ const embedded = (picked: typeof commits): EmbeddedNotes => ({ const offline = (picked: typeof commits): typeof commits => picked.map(([type, summary, pr, author]) => [type, summary, pr, { name: author.name }]) +// View all opens the What's new page in place of the story, as the app opens it in Settings. function Fixture(props: { picked: typeof commits; previous?: string; previousCommit?: string }) { const dialog = useDialog() + const [page, setPage] = createSignal(false) const notes = Option.getOrThrow(sliceBetaNotes(embedded(props.picked), props.previousCommit ?? sha(999))) const open = () => @@ -139,12 +143,22 @@ function Fixture(props: { picked: typeof commits; previous?: string; previousCom previous={props.previous ?? "0.0.0-beta-21180"} dialog={{ close: () => dialog.close() }} open={(href) => window.open(href, "_blank")} + viewAll={() => { + dialog.close() + setPage(true) + }} /> )) onMount(open) - return + return ( + Open What's new in beta}> + + window.open(href, "_blank")} /> + + + ) } export default { @@ -163,7 +177,7 @@ export default { /** A typical update: a few features and fixes. */ export const Typical = { render: () => } -/** More than ten changes: the rest are linked on GitHub. */ +/** More than ten changes: View all opens the What's new page. */ export const Many = { render: () => , } @@ -176,7 +190,16 @@ export const FixesOnly = { render: () => type === "fix").slice(0, 4)} />, } -/** The previous build is older than the embedded history: the newest changes stand in, and the rest are linked. */ +/** The previous build is older than the embedded history: the newest changes stand in, and View all shows the rest. */ export const PreviousBuildUnknown = { render: () => , } + +/** Settings > What's new in beta: every change the build carries, from ⌘K, Settings or the dialog's View all. */ +export const Page = { + render: () => ( + + window.open(href, "_blank")} /> + + ), +} diff --git a/packages/gui-extensions/src/updater/beta-dialog.tsx b/packages/gui-extensions/src/updater/beta-dialog.tsx index 7155f4714e3..87a51fe29f6 100644 --- a/packages/gui-extensions/src/updater/beta-dialog.tsx +++ b/packages/gui-extensions/src/updater/beta-dialog.tsx @@ -1,4 +1,4 @@ -import { createSignal, For, onMount, Show } from "solid-js" +import { createSignal, For, onCleanup, onMount, Show } from "solid-js" import { createResizeObserver } from "@solid-primitives/resize-observer" import { Option } from "effect" import { Button } from "@opencode/ui/button" @@ -38,18 +38,23 @@ export function showBetaNotes( previous={input.previous} dialog={dialog} open={(href) => ctx.system.openExternal(href)} + viewAll={() => { + dialog.close() + ctx.layout.settings("whats-new") + }} /> ), { replace: true }, ) } -/** What's new in beta: the listed features and fixes, and a link to the whole range when some are not listed. */ +/** What's new in beta: the listed features and fixes, and the What's new page when some are not listed. */ export function DialogBetaNotes(props: { notes: BetaNotes previous: string dialog: DialogHandle open: (href: string) => void + viewAll: () => void }) { const ctx = useExtension() const [fade, setFade] = createSignal({ top: 0, bottom: 0 }) @@ -93,11 +98,9 @@ export function DialogBetaNotes(props: { - {(href) => ( - - )} + + + + +
+
+
+
+ + ) + }} + + ) +} diff --git a/packages/gui-extensions/src/updater/i18n/en.ts b/packages/gui-extensions/src/updater/i18n/en.ts index e331421e585..2bd2deda8e3 100644 --- a/packages/gui-extensions/src/updater/i18n/en.ts +++ b/packages/gui-extensions/src/updater/i18n/en.ts @@ -34,6 +34,8 @@ export default { "beta.since.other": "Since build {{build}} · {{count}} changes", "beta.added": "New", "beta.fixed": "Fixes", - "beta.viewAll": "View all on GitHub", + "beta.viewAll": "View all", + "beta.page.description": "Every change in this build.", + "beta.github": "History on GitHub", "beta.done": "Got it", } diff --git a/packages/gui-extensions/src/updater/renderer.tsx b/packages/gui-extensions/src/updater/renderer.tsx index d6aa4a6315e..6f9991f516f 100644 --- a/packages/gui-extensions/src/updater/renderer.tsx +++ b/packages/gui-extensions/src/updater/renderer.tsx @@ -6,6 +6,7 @@ import type definition from "./index" const setup: Setup = (ctx) => { whatsNew(ctx) + betaNotes(ctx) if (!ctx.desktop) return const updater = ctx.uses.updater @@ -86,6 +87,41 @@ const setup: Setup = (ctx) => { createKeyed(updater, (client) => void client.on("check", () => act("check"))) } +/** + * What's new in beta as a settings page and a command, so it is a keystroke away after its dialog closes. Only builds + * that embed the notes have it: beta builds and the desktop dev server. + */ +function betaNotes(ctx: SetupContext) { + if (!import.meta.env.OPENCODE_BETA_NOTES) return + + const Page = lazy(() => import("./beta-page")) + + ctx.add(SettingsPage, { + id: "whats-new", + icon: "flask", + get title() { + return ctx.t("beta.title") + }, + get entries() { + return [{ id: "whats-new", title: ctx.t("beta.title"), keywords: "changelog release notes beta" }] + }, + render: () => ( + + + + ), + }) + + ctx.add(Command, { + id: "whats-new", + get title() { + return ctx.t("beta.title") + }, + keywords: "changelog release notes beta", + run: () => ctx.layout.settings("whats-new"), + }) +} + /** * What's New after an update, on every platform: the release highlights since the version last seen, or on beta the * features and fixes of the beta builds since then. The first run and a disabled What's New only remember the version. diff --git a/packages/stories/mocks/gui-extensions/settings-page.tsx b/packages/stories/mocks/gui-extensions/settings-page.tsx new file mode 100644 index 00000000000..6a78bb5b4d4 --- /dev/null +++ b/packages/stories/mocks/gui-extensions/settings-page.tsx @@ -0,0 +1,7 @@ +import type { JSX } from "solid-js" +// Extension settings pages render inside the app's settings panel, which brings these styles and caps the width. +import "@/settings/settings.css" + +export function SettingsPanel(props: { children: JSX.Element }) { + return
{props.children}
+}