summaryrefslogtreecommitdiffhomepage
path: root/packages/app/src/context
diff options
context:
space:
mode:
authoradamelmore <[email protected]>2026-01-26 15:34:59 -0600
committeradamelmore <[email protected]>2026-01-26 15:36:59 -0600
commitccc7aa49c33dc2247ceee0a51e7816ea5803c404 (patch)
treecda50d5e5ef48ef1e49a2bff5f5f5aacb903570b /packages/app/src/context
parent53ac394c685780985d776c4f861c283714615999 (diff)
downloadopencode-ccc7aa49c33dc2247ceee0a51e7816ea5803c404.tar.gz
opencode-ccc7aa49c33dc2247ceee0a51e7816ea5803c404.zip
wip: highlights
Diffstat (limited to 'packages/app/src/context')
-rw-r--r--packages/app/src/context/highlights.tsx54
-rw-r--r--packages/app/src/context/settings.tsx6
2 files changed, 43 insertions, 17 deletions
diff --git a/packages/app/src/context/highlights.tsx b/packages/app/src/context/highlights.tsx
index 2d20660d7..e55bca675 100644
--- a/packages/app/src/context/highlights.tsx
+++ b/packages/app/src/context/highlights.tsx
@@ -3,10 +3,11 @@ import { createStore } from "solid-js/store"
import { createSimpleContext } from "@opencode-ai/ui/context"
import { useDialog } from "@opencode-ai/ui/context/dialog"
import { usePlatform } from "@/context/platform"
+import { useSettings } from "@/context/settings"
import { persisted } from "@/utils/persist"
import { DialogReleaseNotes, type Highlight } from "@/components/dialog-release-notes"
-const CHANGELOG_URL = "https://dev.opencode.ai/changelog.json"
+const CHANGELOG_URL = "https://opencode.ai/changelog.json"
type Store = {
version?: string
@@ -18,7 +19,7 @@ type ParsedRelease = {
}
function isRecord(value: unknown): value is Record<string, unknown> {
- return typeof value === "object" && value !== null
+ return typeof value === "object" && value !== null && !Array.isArray(value)
}
function getText(value: unknown): string | undefined {
@@ -40,14 +41,14 @@ function normalizeVersion(value: string | undefined) {
function parseMedia(value: unknown, alt: string): Highlight["media"] | undefined {
if (!isRecord(value)) return
const type = getText(value.type)?.toLowerCase()
- const src = getText(value.src)
+ const src = getText(value.src) ?? getText(value.url)
if (!src) return
if (type !== "image" && type !== "video") return
return { type, src, alt }
}
-function parseHighlight(value: unknown, tag: string | undefined): Highlight | undefined {
+function parseHighlight(value: unknown): Highlight | undefined {
if (!isRecord(value)) return
const title = getText(value.title)
@@ -57,7 +58,7 @@ function parseHighlight(value: unknown, tag: string | undefined): Highlight | un
if (!description) return
const media = parseMedia(value.media, title)
- return { title, description, tag, media }
+ return { title, description, media }
}
function parseRelease(value: unknown): ParsedRelease | undefined {
@@ -70,11 +71,18 @@ function parseRelease(value: unknown): ParsedRelease | undefined {
const highlights = value.highlights.flatMap((group) => {
if (!isRecord(group)) return []
- if (!Array.isArray(group.items)) return []
+
const source = getText(group.source)
- return group.items
- .map((item) => parseHighlight(item, source))
- .filter((item): item is Highlight => item !== undefined)
+ if (!source) return []
+ if (!source.toLowerCase().includes("desktop")) return []
+
+ if (Array.isArray(group.items)) {
+ return group.items.map((item) => parseHighlight(item)).filter((item): item is Highlight => item !== undefined)
+ }
+
+ const item = parseHighlight(group)
+ if (!item) return []
+ return [item]
})
return { tag, highlights }
@@ -108,10 +116,17 @@ function sliceHighlights(input: { releases: ParsedRelease[]; current?: string; p
return index === -1 ? releases.length : index
})()
- return releases
- .slice(start, end)
- .flatMap((release) => release.highlights)
- .slice(0, 3)
+ const highlights = releases.slice(start, end).flatMap((release) => release.highlights)
+ const seen = new Set<string>()
+ const unique = highlights.filter((highlight) => {
+ const key = [highlight.title, highlight.description, highlight.media?.type ?? "", highlight.media?.src ?? ""].join(
+ "\n",
+ )
+ if (seen.has(key)) return false
+ seen.add(key)
+ return true
+ })
+ return unique.slice(0, 3)
}
export const { use: useHighlights, provider: HighlightsProvider } = createSimpleContext({
@@ -120,6 +135,7 @@ export const { use: useHighlights, provider: HighlightsProvider } = createSimple
init: () => {
const platform = usePlatform()
const dialog = useDialog()
+ const settings = useSettings()
const [store, setStore, _, ready] = persisted("highlights.v1", createStore<Store>({ version: undefined }))
const [from, setFrom] = createSignal<string | undefined>(undefined)
@@ -135,6 +151,7 @@ export const { use: useHighlights, provider: HighlightsProvider } = createSimple
createEffect(() => {
if (state.started) return
if (!ready()) return
+ if (!settings.ready()) return
if (!platform.version) return
state.started = true
@@ -149,6 +166,11 @@ export const { use: useHighlights, provider: HighlightsProvider } = createSimple
setFrom(previous)
setTo(platform.version)
+ if (!settings.general.releaseNotes()) {
+ markSeen()
+ return
+ }
+
const fetcher = platform.fetch ?? fetch
const controller = new AbortController()
onCleanup(() => {
@@ -182,10 +204,8 @@ export const { use: useHighlights, provider: HighlightsProvider } = createSimple
}
const timer = setTimeout(() => {
- dialog.show(
- () => <DialogReleaseNotes highlights={highlights} />,
- () => markSeen(),
- )
+ markSeen()
+ dialog.show(() => <DialogReleaseNotes highlights={highlights} />)
}, 500)
setTimer(timer)
})
diff --git a/packages/app/src/context/settings.tsx b/packages/app/src/context/settings.tsx
index d976cbc49..67e907a63 100644
--- a/packages/app/src/context/settings.tsx
+++ b/packages/app/src/context/settings.tsx
@@ -18,6 +18,7 @@ export interface SoundSettings {
export interface Settings {
general: {
autoSave: boolean
+ releaseNotes: boolean
}
appearance: {
fontSize: number
@@ -34,6 +35,7 @@ export interface Settings {
const defaultSettings: Settings = {
general: {
autoSave: true,
+ releaseNotes: true,
},
appearance: {
fontSize: 14,
@@ -97,6 +99,10 @@ export const { use: useSettings, provider: SettingsProvider } = createSimpleCont
setAutoSave(value: boolean) {
setStore("general", "autoSave", value)
},
+ releaseNotes: createMemo(() => store.general?.releaseNotes ?? defaultSettings.general.releaseNotes),
+ setReleaseNotes(value: boolean) {
+ setStore("general", "releaseNotes", value)
+ },
},
appearance: {
fontSize: createMemo(() => store.appearance?.fontSize ?? defaultSettings.appearance.fontSize),