mirror of
https://github.com/anomalyco/opencode.git
synced 2026-11-13 11:39:41 +03:00
fix(desktop): tidy the What's new in beta page and drop its command (#58256)
This commit is contained in:
committed by
GitHub
Unverified
parent
bcd713e2b7
commit
e997d7b8a0
@@ -56,7 +56,7 @@ export default defineConfig(async ({ command }) => {
|
||||
const nodePtyPkg = nodePtyPackage()
|
||||
|
||||
// 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.
|
||||
// them without asking GitHub, so the page can be tried; other channels none.
|
||||
const betaNotes =
|
||||
channel === "beta"
|
||||
? await BetaNotes.enrich(BetaNotes.embed(), { token: process.env.GITHUB_TOKEN })
|
||||
|
||||
@@ -195,7 +195,7 @@ export const PreviousBuildUnknown = {
|
||||
render: () => <Fixture picked={commits} previous="0.0.0-beta-20500" previousCommit={sha(5000)} />,
|
||||
}
|
||||
|
||||
/** Settings > What's new in beta: every change the build carries, from ⌘K, Settings or the dialog's View all. */
|
||||
/** Settings > What's new in beta: every change the build carries, from Settings or the dialog's View all. */
|
||||
export const Page = {
|
||||
render: () => (
|
||||
<SettingsPanel>
|
||||
|
||||
@@ -115,7 +115,7 @@ const FADE = 24
|
||||
|
||||
// Every entry starts on the heading's edge; a visual sits small at the row's end, so rows keep one rhythm with or
|
||||
// without one.
|
||||
export function Section(props: { title: string; notes: readonly BetaNote[]; open: (href: string) => void }) {
|
||||
function Section(props: { title: string; notes: readonly BetaNote[]; open: (href: string) => void }) {
|
||||
return (
|
||||
<Show when={props.notes.length > 0}>
|
||||
<section class="flex flex-col gap-3">
|
||||
@@ -141,7 +141,7 @@ export function Section(props: { title: string; notes: readonly BetaNote[]; open
|
||||
}
|
||||
|
||||
/** What changed, drawn: the pull request's looping SVG at half size, only ever as an image, in the icon colour. */
|
||||
function Visual(props: { visual: NonNullable<BetaNote["visual"]>; label?: string }) {
|
||||
export function Visual(props: { visual: NonNullable<BetaNote["visual"]>; label?: string }) {
|
||||
const [color, setColor] = createSignal<string>()
|
||||
let frame!: HTMLSpanElement
|
||||
|
||||
@@ -173,11 +173,13 @@ function Visual(props: { visual: NonNullable<BetaNote["visual"]>; label?: string
|
||||
)
|
||||
}
|
||||
|
||||
/** Who made it and its pull request, quietly. */
|
||||
function Meta(props: { note: BetaNote; open: (href: string) => void }) {
|
||||
/** Who made it and its pull request, quietly; `class` sets the type, the dialog's own scale by default. */
|
||||
export function Meta(props: { note: BetaNote; open: (href: string) => void; class?: string }) {
|
||||
return (
|
||||
<Show when={props.note.author || props.note.pr}>
|
||||
<div class="flex min-w-0 flex-wrap items-center gap-x-1.5 text-[12px] leading-4 font-[400] text-v2-text-text-faint">
|
||||
<div
|
||||
class={`flex min-w-0 flex-wrap items-center gap-x-1.5 ${props.class ?? "text-[12px] leading-4 font-[400] text-v2-text-text-faint"}`}
|
||||
>
|
||||
<Show when={props.note.author}>
|
||||
{(author) => (
|
||||
<span class="flex min-w-0 items-center gap-1.5">
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import { Show } from "solid-js"
|
||||
import { createSignal, For, onCleanup, onMount, Show } from "solid-js"
|
||||
import { createMediaQuery } from "@solid-primitives/media"
|
||||
import { Option } from "effect"
|
||||
import { Button } from "@opencode/ui/button"
|
||||
import { useExtension } from "../sdk"
|
||||
import { Section } from "./beta-dialog"
|
||||
import { allBetaNotes, betaHistory, decodeEmbeddedNotes, type EmbeddedNotes } from "./beta-notes"
|
||||
import { Meta, Visual } from "./beta-dialog"
|
||||
import { allBetaNotes, betaHistory, decodeEmbeddedNotes, type BetaNote, type EmbeddedNotes } from "./beta-notes"
|
||||
import type definition from "./index"
|
||||
|
||||
/** Settings > What's new in beta: every change this build carries, as the dialog shows them. */
|
||||
/** Settings > What's new in beta: every change this build carries, in the host's settings list markup. */
|
||||
export default function BetaNotesPage() {
|
||||
const ctx = useExtension<typeof definition>()
|
||||
|
||||
@@ -20,6 +21,9 @@ export default function BetaNotesPage() {
|
||||
|
||||
export function BetaNotesView(props: { embedded: EmbeddedNotes | undefined; open: (href: string) => void }) {
|
||||
const ctx = useExtension<typeof definition>()
|
||||
// Below 640px a row's control slot wraps under its title, so narrow windows leave the visuals to the dialog.
|
||||
const wide = createMediaQuery("(min-width: 640px)")
|
||||
const watch = createWatch()
|
||||
|
||||
return (
|
||||
<Show when={props.embedded}>
|
||||
@@ -30,15 +34,15 @@ export function BetaNotesView(props: { embedded: EmbeddedNotes | undefined; open
|
||||
<>
|
||||
<div class="settings-tab-header">
|
||||
<div class="settings-tab-header-row">
|
||||
<div class="flex flex-col gap-1">
|
||||
<div class="flex min-w-0 flex-col gap-1">
|
||||
<h2 class="settings-tab-title">{ctx.t("beta.title")}</h2>
|
||||
<span class="text-11-regular text-v2-text-text-muted">{ctx.t("beta.page.description")}</span>
|
||||
</div>
|
||||
<div class="settings-tab-header-actions">
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
class="-me-2.5"
|
||||
variant="ghost-muted"
|
||||
class="whitespace-nowrap"
|
||||
onClick={() => props.open(betaHistory(embedded()))}
|
||||
>
|
||||
{ctx.t("beta.github")}
|
||||
@@ -47,8 +51,8 @@ export function BetaNotesView(props: { embedded: EmbeddedNotes | undefined; open
|
||||
</div>
|
||||
</div>
|
||||
<div class="settings-tab-body settings-tab-body--sectioned">
|
||||
<Section title={ctx.t("beta.added")} notes={notes.added} open={props.open} />
|
||||
<Section title={ctx.t("beta.fixed")} notes={notes.fixed} open={props.open} />
|
||||
<Notes title={ctx.t("beta.added")} notes={notes.added} open={props.open} wide={wide()} watch={watch} />
|
||||
<Notes title={ctx.t("beta.fixed")} notes={notes.fixed} open={props.open} wide={wide()} watch={watch} />
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
@@ -56,3 +60,81 @@ export function BetaNotesView(props: { embedded: EmbeddedNotes | undefined; open
|
||||
</Show>
|
||||
)
|
||||
}
|
||||
|
||||
type Watch = (element: Element, inView: (value: boolean) => void) => () => void
|
||||
|
||||
function Notes(props: {
|
||||
title: string
|
||||
notes: readonly BetaNote[]
|
||||
open: (href: string) => void
|
||||
wide: boolean
|
||||
watch: Watch
|
||||
}) {
|
||||
return (
|
||||
<Show when={props.notes.length > 0}>
|
||||
<section class="settings-section" aria-label={props.title}>
|
||||
<h3 class="settings-section-title">{props.title}</h3>
|
||||
<div data-component="settings-list" data-variant="catalog">
|
||||
<For each={props.notes}>
|
||||
{(note) => (
|
||||
<div data-component="settings-row">
|
||||
<div data-slot="settings-row-copy">
|
||||
<div data-slot="settings-row-title" class="[overflow-wrap:anywhere]">
|
||||
{note.summary}
|
||||
</div>
|
||||
<Show when={note.author || note.pr}>
|
||||
<div data-slot="settings-row-description">
|
||||
<Meta note={note} open={props.open} class="" />
|
||||
</div>
|
||||
</Show>
|
||||
</div>
|
||||
<Show when={props.wide && note.visual}>
|
||||
{(visual) => <RowVisual visual={visual()} label={note.description} watch={props.watch} />}
|
||||
</Show>
|
||||
</div>
|
||||
)}
|
||||
</For>
|
||||
</div>
|
||||
</section>
|
||||
</Show>
|
||||
)
|
||||
}
|
||||
|
||||
/** A row's visual in its control slot, mounted only while the row is near the viewport so off-screen loops stop. */
|
||||
function RowVisual(props: { visual: NonNullable<BetaNote["visual"]>; label?: string; watch: Watch }) {
|
||||
const [visible, setVisible] = createSignal(false)
|
||||
let slot!: HTMLDivElement
|
||||
|
||||
onMount(() => onCleanup(props.watch(slot, setVisible)))
|
||||
|
||||
return (
|
||||
// Inline, so the host's unlayered control-slot width cannot collapse the space before the visual mounts.
|
||||
<div ref={slot} data-slot="settings-row-control" style={{ width: "120px", height: "36px" }}>
|
||||
<Show when={visible()}>
|
||||
<Visual visual={props.visual} label={props.label} />
|
||||
</Show>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
/** One IntersectionObserver for every row on the page; each watched element hears when it nears or leaves the viewport. */
|
||||
function createWatch(): Watch {
|
||||
const rows = new Map<Element, (value: boolean) => void>()
|
||||
|
||||
const observer = new IntersectionObserver(
|
||||
(entries) => entries.forEach((entry) => rows.get(entry.target)?.(entry.isIntersecting)),
|
||||
{ rootMargin: "200px 0px" },
|
||||
)
|
||||
|
||||
onCleanup(() => observer.disconnect())
|
||||
|
||||
return (element, inView) => {
|
||||
rows.set(element, inView)
|
||||
observer.observe(element)
|
||||
|
||||
return () => {
|
||||
rows.delete(element)
|
||||
observer.unobserve(element)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,8 +88,8 @@ const setup: Setup<typeof definition> = (ctx) => {
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* What's new in beta as a settings page, to come back to after its dialog closes. Only builds that embed the notes
|
||||
* have it: beta builds and the desktop dev server.
|
||||
*/
|
||||
function betaNotes(ctx: SetupContext<typeof definition>) {
|
||||
if (!import.meta.env.OPENCODE_BETA_NOTES) return
|
||||
@@ -111,15 +111,6 @@ function betaNotes(ctx: SetupContext<typeof definition>) {
|
||||
</Suspense>
|
||||
),
|
||||
})
|
||||
|
||||
ctx.add(Command, {
|
||||
id: "whats-new",
|
||||
get title() {
|
||||
return ctx.t("beta.title")
|
||||
},
|
||||
keywords: "changelog release notes beta",
|
||||
run: () => ctx.layout.settings("whats-new"),
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user