mirror of
https://github.com/anomalyco/opencode.git
synced 2026-04-27 08:15:31 +00:00
chore(app): refactor for better solidjs hygiene (#13344)
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { createStore } from "solid-js/store"
|
||||
import { createEffect, createMemo, onCleanup } from "solid-js"
|
||||
import { createStore, reconcile } from "solid-js/store"
|
||||
import { batch, createEffect, createMemo, onCleanup } from "solid-js"
|
||||
import { useParams } from "@solidjs/router"
|
||||
import { createSimpleContext } from "@opencode-ai/ui/context"
|
||||
import { useGlobalSDK } from "./global-sdk"
|
||||
@@ -13,7 +13,6 @@ import { decode64 } from "@/utils/base64"
|
||||
import { EventSessionError } from "@opencode-ai/sdk/v2"
|
||||
import { Persist, persisted } from "@/utils/persist"
|
||||
import { playSound, soundSrc } from "@/utils/sound"
|
||||
import { buildNotificationIndex } from "./notification-index"
|
||||
|
||||
type NotificationBase = {
|
||||
directory?: string
|
||||
@@ -34,6 +33,21 @@ type ErrorNotification = NotificationBase & {
|
||||
|
||||
export type Notification = TurnCompleteNotification | ErrorNotification
|
||||
|
||||
type NotificationIndex = {
|
||||
session: {
|
||||
all: Record<string, Notification[]>
|
||||
unseen: Record<string, Notification[]>
|
||||
unseenCount: Record<string, number>
|
||||
unseenHasError: Record<string, boolean>
|
||||
}
|
||||
project: {
|
||||
all: Record<string, Notification[]>
|
||||
unseen: Record<string, Notification[]>
|
||||
unseenCount: Record<string, number>
|
||||
unseenHasError: Record<string, boolean>
|
||||
}
|
||||
}
|
||||
|
||||
const MAX_NOTIFICATIONS = 500
|
||||
const NOTIFICATION_TTL_MS = 1000 * 60 * 60 * 24 * 30
|
||||
|
||||
@@ -44,6 +58,53 @@ function pruneNotifications(list: Notification[]) {
|
||||
return pruned.slice(pruned.length - MAX_NOTIFICATIONS)
|
||||
}
|
||||
|
||||
function createNotificationIndex(): NotificationIndex {
|
||||
return {
|
||||
session: {
|
||||
all: {},
|
||||
unseen: {},
|
||||
unseenCount: {},
|
||||
unseenHasError: {},
|
||||
},
|
||||
project: {
|
||||
all: {},
|
||||
unseen: {},
|
||||
unseenCount: {},
|
||||
unseenHasError: {},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
function buildNotificationIndex(list: Notification[]) {
|
||||
const index = createNotificationIndex()
|
||||
|
||||
list.forEach((notification) => {
|
||||
if (notification.session) {
|
||||
const all = index.session.all[notification.session] ?? []
|
||||
index.session.all[notification.session] = [...all, notification]
|
||||
if (!notification.viewed) {
|
||||
const unseen = index.session.unseen[notification.session] ?? []
|
||||
index.session.unseen[notification.session] = [...unseen, notification]
|
||||
index.session.unseenCount[notification.session] = unseen.length + 1
|
||||
if (notification.type === "error") index.session.unseenHasError[notification.session] = true
|
||||
}
|
||||
}
|
||||
|
||||
if (notification.directory) {
|
||||
const all = index.project.all[notification.directory] ?? []
|
||||
index.project.all[notification.directory] = [...all, notification]
|
||||
if (!notification.viewed) {
|
||||
const unseen = index.project.unseen[notification.directory] ?? []
|
||||
index.project.unseen[notification.directory] = [...unseen, notification]
|
||||
index.project.unseenCount[notification.directory] = unseen.length + 1
|
||||
if (notification.type === "error") index.project.unseenHasError[notification.directory] = true
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return index
|
||||
}
|
||||
|
||||
export const { use: useNotification, provider: NotificationProvider } = createSimpleContext({
|
||||
name: "Notification",
|
||||
init: () => {
|
||||
@@ -68,21 +129,81 @@ export const { use: useNotification, provider: NotificationProvider } = createSi
|
||||
list: [] as Notification[],
|
||||
}),
|
||||
)
|
||||
const [index, setIndex] = createStore<NotificationIndex>(buildNotificationIndex(store.list))
|
||||
|
||||
const meta = { pruned: false, disposed: false }
|
||||
|
||||
const updateUnseen = (scope: "session" | "project", key: string, unseen: Notification[]) => {
|
||||
setIndex(scope, "unseen", key, unseen)
|
||||
setIndex(scope, "unseenCount", key, unseen.length)
|
||||
setIndex(
|
||||
scope,
|
||||
"unseenHasError",
|
||||
key,
|
||||
unseen.some((notification) => notification.type === "error"),
|
||||
)
|
||||
}
|
||||
|
||||
const appendToIndex = (notification: Notification) => {
|
||||
if (notification.session) {
|
||||
setIndex("session", "all", notification.session, (all = []) => [...all, notification])
|
||||
if (!notification.viewed) {
|
||||
setIndex("session", "unseen", notification.session, (unseen = []) => [...unseen, notification])
|
||||
setIndex("session", "unseenCount", notification.session, (count = 0) => count + 1)
|
||||
if (notification.type === "error") setIndex("session", "unseenHasError", notification.session, true)
|
||||
}
|
||||
}
|
||||
|
||||
if (notification.directory) {
|
||||
setIndex("project", "all", notification.directory, (all = []) => [...all, notification])
|
||||
if (!notification.viewed) {
|
||||
setIndex("project", "unseen", notification.directory, (unseen = []) => [...unseen, notification])
|
||||
setIndex("project", "unseenCount", notification.directory, (count = 0) => count + 1)
|
||||
if (notification.type === "error") setIndex("project", "unseenHasError", notification.directory, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const removeFromIndex = (notification: Notification) => {
|
||||
if (notification.session) {
|
||||
setIndex("session", "all", notification.session, (all = []) => all.filter((n) => n !== notification))
|
||||
if (!notification.viewed) {
|
||||
const unseen = (index.session.unseen[notification.session] ?? empty).filter((n) => n !== notification)
|
||||
updateUnseen("session", notification.session, unseen)
|
||||
}
|
||||
}
|
||||
|
||||
if (notification.directory) {
|
||||
setIndex("project", "all", notification.directory, (all = []) => all.filter((n) => n !== notification))
|
||||
if (!notification.viewed) {
|
||||
const unseen = (index.project.unseen[notification.directory] ?? empty).filter((n) => n !== notification)
|
||||
updateUnseen("project", notification.directory, unseen)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
createEffect(() => {
|
||||
if (!ready()) return
|
||||
if (meta.pruned) return
|
||||
meta.pruned = true
|
||||
setStore("list", pruneNotifications(store.list))
|
||||
const list = pruneNotifications(store.list)
|
||||
batch(() => {
|
||||
setStore("list", list)
|
||||
setIndex(reconcile(buildNotificationIndex(list), { merge: false }))
|
||||
})
|
||||
})
|
||||
|
||||
const append = (notification: Notification) => {
|
||||
setStore("list", (list) => pruneNotifications([...list, notification]))
|
||||
}
|
||||
const list = pruneNotifications([...store.list, notification])
|
||||
const keep = new Set(list)
|
||||
const removed = store.list.filter((n) => !keep.has(n))
|
||||
|
||||
const index = createMemo(() => buildNotificationIndex(store.list))
|
||||
batch(() => {
|
||||
if (keep.has(notification)) appendToIndex(notification)
|
||||
removed.forEach((n) => removeFromIndex(n))
|
||||
setStore("list", list)
|
||||
})
|
||||
}
|
||||
|
||||
const lookup = async (directory: string, sessionID?: string) => {
|
||||
if (!sessionID) return undefined
|
||||
@@ -181,36 +302,66 @@ export const { use: useNotification, provider: NotificationProvider } = createSi
|
||||
ready,
|
||||
session: {
|
||||
all(session: string) {
|
||||
return index().session.all.get(session) ?? empty
|
||||
return index.session.all[session] ?? empty
|
||||
},
|
||||
unseen(session: string) {
|
||||
return index().session.unseen.get(session) ?? empty
|
||||
return index.session.unseen[session] ?? empty
|
||||
},
|
||||
unseenCount(session: string) {
|
||||
return index().session.unseenCount.get(session) ?? 0
|
||||
return index.session.unseenCount[session] ?? 0
|
||||
},
|
||||
unseenHasError(session: string) {
|
||||
return index().session.unseenHasError.get(session) ?? false
|
||||
return index.session.unseenHasError[session] ?? false
|
||||
},
|
||||
markViewed(session: string) {
|
||||
setStore("list", (n) => n.session === session, "viewed", true)
|
||||
const unseen = index.session.unseen[session] ?? empty
|
||||
if (!unseen.length) return
|
||||
|
||||
const projects = [
|
||||
...new Set(unseen.flatMap((notification) => (notification.directory ? [notification.directory] : []))),
|
||||
]
|
||||
batch(() => {
|
||||
setStore("list", (n) => n.session === session && !n.viewed, "viewed", true)
|
||||
updateUnseen("session", session, [])
|
||||
projects.forEach((directory) => {
|
||||
const next = (index.project.unseen[directory] ?? empty).filter(
|
||||
(notification) => notification.session !== session,
|
||||
)
|
||||
updateUnseen("project", directory, next)
|
||||
})
|
||||
})
|
||||
},
|
||||
},
|
||||
project: {
|
||||
all(directory: string) {
|
||||
return index().project.all.get(directory) ?? empty
|
||||
return index.project.all[directory] ?? empty
|
||||
},
|
||||
unseen(directory: string) {
|
||||
return index().project.unseen.get(directory) ?? empty
|
||||
return index.project.unseen[directory] ?? empty
|
||||
},
|
||||
unseenCount(directory: string) {
|
||||
return index().project.unseenCount.get(directory) ?? 0
|
||||
return index.project.unseenCount[directory] ?? 0
|
||||
},
|
||||
unseenHasError(directory: string) {
|
||||
return index().project.unseenHasError.get(directory) ?? false
|
||||
return index.project.unseenHasError[directory] ?? false
|
||||
},
|
||||
markViewed(directory: string) {
|
||||
setStore("list", (n) => n.directory === directory, "viewed", true)
|
||||
const unseen = index.project.unseen[directory] ?? empty
|
||||
if (!unseen.length) return
|
||||
|
||||
const sessions = [
|
||||
...new Set(unseen.flatMap((notification) => (notification.session ? [notification.session] : []))),
|
||||
]
|
||||
batch(() => {
|
||||
setStore("list", (n) => n.directory === directory && !n.viewed, "viewed", true)
|
||||
updateUnseen("project", directory, [])
|
||||
sessions.forEach((session) => {
|
||||
const next = (index.session.unseen[session] ?? empty).filter(
|
||||
(notification) => notification.directory !== directory,
|
||||
)
|
||||
updateUnseen("session", session, next)
|
||||
})
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user