mirror of
https://github.com/anomalyco/opencode.git
synced 2026-09-20 15:47:38 +00:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
169ababd60 |
@@ -3,7 +3,19 @@ import { checksum } from "@opencode/util/encode"
|
||||
import { SessionMessage } from "@opencode/schema/session-message"
|
||||
import { Skill } from "@opencode/schema/skill"
|
||||
import { Persistence } from "@/runtime/persistence/schema"
|
||||
import { FileSelection, SelectedLineRange } from "@/workspaces/files/types"
|
||||
// Effect twins of workspaces/files/types until this module moves to plain codecs.
|
||||
const SelectedLineRange = Persistence.struct({
|
||||
start: Schema.Number,
|
||||
end: Schema.Number,
|
||||
side: Persistence.optional(Schema.Literals(["additions", "deletions"])),
|
||||
endSide: Persistence.optional(Schema.Literals(["additions", "deletions"])),
|
||||
})
|
||||
const FileSelection = Persistence.struct({
|
||||
startLine: Schema.Number,
|
||||
startChar: Schema.Number,
|
||||
endLine: Schema.Number,
|
||||
endChar: Schema.Number,
|
||||
})
|
||||
|
||||
const PartBase = {
|
||||
content: Schema.String,
|
||||
@@ -228,3 +240,5 @@ export const PromptHistoryEntry = Schema.Union([HistoryEntry, HistoryPrompt]).pi
|
||||
export type PromptHistoryEntry = typeof PromptHistoryEntry.Type
|
||||
|
||||
export const PromptHistoryState = Persistence.struct({ entries: Persistence.array(PromptHistoryEntry) })
|
||||
|
||||
|
||||
|
||||
@@ -11,15 +11,15 @@ import { Persist, persisted } from "@/runtime/persistence/storage"
|
||||
import { showToast } from "@/shell/notifications/toast"
|
||||
import { useDialog } from "@opencode/ui/context/dialog"
|
||||
import { createResource } from "solid-js"
|
||||
import { Schema } from "effect"
|
||||
import { Persistence } from "@/runtime/persistence/schema"
|
||||
import { Codec } from "@/runtime/persistence/codec"
|
||||
|
||||
import type { HomeController } from "../model"
|
||||
import { useGlobal } from "@/runtime/server/runtime"
|
||||
import { SessionTransfer } from "@opencode/schema/session-transfer"
|
||||
|
||||
import { useSshAuthenticate } from "@/servers/ssh/authenticate"
|
||||
|
||||
export const HomeServersSchema = Schema.Struct({
|
||||
collapsed: Persistence.record(Persistence.fallback(Schema.Boolean, () => false)),
|
||||
export const HomeServersSchema = Codec.struct({
|
||||
collapsed: Codec.lenientRecord(Codec.fallback(Codec.boolean, () => false)),
|
||||
})
|
||||
|
||||
export function createHomeProjectsController(home: HomeController) {
|
||||
@@ -114,8 +114,13 @@ export function createHomeProjectsController(home: HomeController) {
|
||||
extensions: ["json"],
|
||||
},
|
||||
async (file) => {
|
||||
const data = await Schema.decodeUnknownPromise(Schema.fromJsonString(SessionTransfer.Data))(
|
||||
await file.text(),
|
||||
// Validating an imported file is the one place the shared Effect schema is needed here.
|
||||
const [{ Schema }, { SessionTransfer }] = await Promise.all([
|
||||
import("effect"),
|
||||
import("@opencode/schema/session-transfer"),
|
||||
])
|
||||
const data = await Schema.decodeUnknownPromise(Schema.fromJsonString(SessionTransfer.Data))(
|
||||
await file.text(),
|
||||
)
|
||||
const api = home.server.context(conn).sdk.api.session
|
||||
const imported = await api.import({
|
||||
@@ -184,3 +189,4 @@ export function createHomeProjectsController(home: HomeController) {
|
||||
}
|
||||
|
||||
export type HomeProjectsController = ReturnType<typeof createHomeProjectsController>
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import { Show, Suspense, createMemo, createSignal, lazy, onMount } from "solid-j
|
||||
import { createStore } from "solid-js/store"
|
||||
import { makeEventListener } from "@solid-primitives/event-listener"
|
||||
import { debounce } from "@solid-primitives/scheduled"
|
||||
import { Schema } from "effect"
|
||||
import { Codec } from "@/runtime/persistence/codec"
|
||||
import createPresence from "solid-presence"
|
||||
import { Composer } from "@/composer/composer"
|
||||
import { ComposerDropzone } from "@/composer/dropzone"
|
||||
@@ -21,7 +21,6 @@ import { useWorkspaceLocation } from "@/workspaces/location"
|
||||
import { useProviders } from "@/providers/catalog/providers"
|
||||
import { NEW_SESSION_CONTENT_WIDTH } from "@/new-session/layout"
|
||||
import { Persist, persisted } from "@/runtime/persistence/storage"
|
||||
import { Persistence } from "@/runtime/persistence/schema"
|
||||
import type { NewSessionWorkspaceController } from "./workspace/controller"
|
||||
import { NewSessionWordmark } from "./wordmark"
|
||||
import { SummaryPopover } from "@/session/summary/popover"
|
||||
@@ -34,12 +33,12 @@ const NewSessionSummary = lazy(async () => {
|
||||
|
||||
const providerTipDismissalDuration = 30 * 24 * 60 * 60 * 1000
|
||||
|
||||
export const WorkspaceOnboardingSchema = Persistence.struct({
|
||||
used: Schema.Boolean,
|
||||
export const WorkspaceOnboardingSchema = Codec.struct({
|
||||
used: Codec.boolean,
|
||||
})
|
||||
|
||||
export const ProviderTipSchema = Persistence.struct({
|
||||
dismissedAt: Schema.Finite,
|
||||
export const ProviderTipSchema = Codec.struct({
|
||||
dismissedAt: Codec.number,
|
||||
})
|
||||
|
||||
export const WorkspaceTipSchema = ProviderTipSchema
|
||||
@@ -267,3 +266,4 @@ function NewSessionTips(props: { workspaceEligible: boolean; onWorkspace: () =>
|
||||
</Show>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ import { Schema } from "effect"
|
||||
import { WorkspaceOnboardingSchema, ProviderTipSchema, WorkspaceTipSchema } from "@/new-session/view"
|
||||
import { ModelSelectionSchema } from "@/providers/models/selection"
|
||||
import { Persistence } from "@/runtime/persistence/schema"
|
||||
import { Codec } from "@/runtime/persistence/codec"
|
||||
import { FileViewsSchema } from "@/workspaces/files/view-cache"
|
||||
import { languageSchema } from "@/runtime/i18n/language"
|
||||
import { HomeServersSchema } from "@/home/projects/controller"
|
||||
@@ -10,9 +11,9 @@ import { ModelProvidersSchema } from "@/settings/models/models"
|
||||
|
||||
describe("persisted consumer schemas", () => {
|
||||
test("onboarding and provider tip retain defaults and validate stored values", () => {
|
||||
const onboarding = Schema.decodeUnknownSync(Persistence.withInitial(WorkspaceOnboardingSchema, { used: false }))
|
||||
const tip = Schema.decodeUnknownSync(Persistence.withInitial(ProviderTipSchema, { dismissedAt: 0 }))
|
||||
const workspaceTip = Schema.decodeUnknownSync(Persistence.withInitial(WorkspaceTipSchema, { dismissedAt: 0 }))
|
||||
const onboarding = ((input: unknown) => Codec.decodeOrThrow(Codec.withInitial(WorkspaceOnboardingSchema, { used: false }), input))
|
||||
const tip = ((input: unknown) => Codec.decodeOrThrow(Codec.withInitial(ProviderTipSchema, { dismissedAt: 0 }), input))
|
||||
const workspaceTip = ((input: unknown) => Codec.decodeOrThrow(Codec.withInitial(WorkspaceTipSchema, { dismissedAt: 0 }), input))
|
||||
expect(onboarding({})).toEqual({ used: false })
|
||||
expect(onboarding({ used: "true" })).toEqual({ used: false })
|
||||
expect(onboarding({ used: true })).toEqual({ used: true })
|
||||
@@ -25,7 +26,7 @@ describe("persisted consumer schemas", () => {
|
||||
|
||||
test("collapse records recover malformed entries without losing valid siblings", () => {
|
||||
for (const schema of [HomeServersSchema, ModelProvidersSchema]) {
|
||||
const decode = Schema.decodeUnknownSync(Persistence.withInitial(schema, { collapsed: {} }))
|
||||
const decode = (input: unknown) => Codec.decodeOrThrow(Codec.withInitial(schema, { collapsed: {} }), input)
|
||||
expect(decode({})).toEqual({ collapsed: {} })
|
||||
expect(decode({ collapsed: [] })).toEqual({ collapsed: {} })
|
||||
expect(decode({ collapsed: { open: false, closed: true, invalid: "false" } })).toEqual({
|
||||
@@ -76,7 +77,7 @@ describe("persisted consumer schemas", () => {
|
||||
})
|
||||
|
||||
test("file views validate scroll positions and line sides independently", () => {
|
||||
const decode = Schema.decodeUnknownSync(Persistence.withInitial(FileViewsSchema, { file: {} }))
|
||||
const decode = ((input: unknown) => Codec.decodeOrThrow(Codec.withInitial(FileViewsSchema, { file: {} }), input))
|
||||
expect(decode({})).toEqual({ file: {} })
|
||||
const state = decode({
|
||||
file: {
|
||||
@@ -111,3 +112,4 @@ describe("persisted consumer schemas", () => {
|
||||
expect(decode({ locale: "ar" })).toEqual({ locale: "ar" })
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -5,8 +5,7 @@ import { Icon } from "@opencode/ui/icon"
|
||||
import { IconButton } from "@opencode/ui/icon-button"
|
||||
import { TextInput } from "@opencode/ui/text-input"
|
||||
import { type Component, createEffect, For, on, onCleanup, Show } from "solid-js"
|
||||
import { Schema } from "effect"
|
||||
import { Persistence } from "@/runtime/persistence/schema"
|
||||
import { Codec } from "@/runtime/persistence/codec"
|
||||
import { useLanguage } from "@/runtime/i18n/language"
|
||||
import { useModels } from "@/providers/models/models"
|
||||
import { useServerSDK } from "@/runtime/server/client"
|
||||
@@ -20,8 +19,8 @@ type ModelItem = ReturnType<ReturnType<typeof useModels>["list"]>[number]
|
||||
|
||||
const PROVIDER_ICON_SIZE = 16
|
||||
|
||||
export const ModelProvidersSchema = Schema.Struct({
|
||||
collapsed: Persistence.record(Persistence.fallback(Schema.Boolean, () => false)),
|
||||
export const ModelProvidersSchema = Codec.struct({
|
||||
collapsed: Codec.lenientRecord(Codec.fallback(Codec.boolean, () => false)),
|
||||
})
|
||||
|
||||
export const SettingsModels: Component<{ active?: boolean; autofocus?: boolean }> = (props) => {
|
||||
@@ -211,3 +210,4 @@ export const SettingsModels: Component<{ active?: boolean; autofocus?: boolean }
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { describe, expect, test } from "bun:test"
|
||||
import { Schema } from "effect"
|
||||
import { Codec } from "@/runtime/persistence/codec"
|
||||
import {
|
||||
activeCommandRegistrations,
|
||||
addCommandRegistration,
|
||||
@@ -10,12 +10,12 @@ import {
|
||||
} from "./command"
|
||||
|
||||
test("command catalog persistence validates metadata and omits executable fields", () => {
|
||||
const decode = Schema.decodeUnknownSync(CommandCatalog)
|
||||
const decode = ((input: unknown) => Codec.decodeOrThrow(CommandCatalog, input))
|
||||
const catalog = decode({ open: { title: "Open", keybind: "mod+o", hidden: false, onSelect: "invalid" } })
|
||||
expect(catalog).toEqual({ open: { title: "Open", keybind: "mod+o", hidden: false } })
|
||||
expect(decode({})).toEqual({})
|
||||
expect(() => decode({ open: { title: 1 } })).toThrow()
|
||||
expect(decode(Schema.encodeSync(CommandCatalog)(catalog))).toEqual(catalog)
|
||||
expect(decode(CommandCatalog.encode(catalog))).toEqual(catalog)
|
||||
})
|
||||
|
||||
const paletteOptions: CommandOption[] = [
|
||||
@@ -79,3 +79,4 @@ describe("resolveKeybindOption", () => {
|
||||
expect(resolveKeybindOption([fallback, contextual], new KeyboardEvent("keydown"))).toBe(fallback)
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@ import { createSimpleContext } from "@opencode/ui/context"
|
||||
import { useDialog } from "@opencode/ui/context/dialog"
|
||||
import { type Accessor, batch, createEffect, createMemo, onCleanup, onMount } from "solid-js"
|
||||
import { createStore, reconcile } from "solid-js/store"
|
||||
import { Schema } from "effect"
|
||||
import { Persistence } from "@/runtime/persistence/schema"
|
||||
import { Codec } from "@/runtime/persistence/codec"
|
||||
|
||||
import { makeEventListener } from "@solid-primitives/event-listener"
|
||||
import { useLanguage } from "@/runtime/i18n/language"
|
||||
import { useSettings } from "@/settings/model"
|
||||
@@ -109,16 +109,16 @@ export function resolveKeybindOption(candidates: CommandOption[] | undefined, ev
|
||||
|
||||
type CommandSource = "palette" | "keybind" | "slash"
|
||||
|
||||
export const CommandCatalogItem = Persistence.struct({
|
||||
title: Schema.String,
|
||||
description: Schema.optional(Schema.String),
|
||||
category: Schema.optional(Schema.String),
|
||||
keybind: Schema.optional(Schema.String),
|
||||
slash: Schema.optional(Schema.String),
|
||||
hidden: Schema.optional(Schema.Boolean),
|
||||
export const CommandCatalogItem = Codec.struct({
|
||||
title: Codec.string,
|
||||
description: Codec.optional(Codec.string),
|
||||
category: Codec.optional(Codec.string),
|
||||
keybind: Codec.optional(Codec.string),
|
||||
slash: Codec.optional(Codec.string),
|
||||
hidden: Codec.optional(Codec.boolean),
|
||||
})
|
||||
export type CommandCatalogItem = typeof CommandCatalogItem.Type
|
||||
export const CommandCatalog = Schema.Record(Schema.String, Schema.mutableKey(CommandCatalogItem))
|
||||
export const CommandCatalog = Codec.record(CommandCatalogItem)
|
||||
export type CommandCatalog = typeof CommandCatalog.Type
|
||||
|
||||
export type CommandRegistration = {
|
||||
@@ -480,3 +480,4 @@ export const { use: useCommand, provider: CommandProvider } = createSimpleContex
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
@@ -1,23 +1,22 @@
|
||||
import { expect, test } from "bun:test"
|
||||
import { Schema } from "effect"
|
||||
import type { ServerConnection } from "@/runtime/server/registry"
|
||||
import type { Tab } from "@/shell/tabs/tabs"
|
||||
import { NotificationStore, openNotificationSession, type Notification } from "./notification"
|
||||
import { Persistence } from "@/runtime/persistence/schema"
|
||||
import { Codec } from "@/runtime/persistence/codec"
|
||||
|
||||
test("notification persistence validates and salvages individual notifications", () => {
|
||||
const valid: Notification[] = [
|
||||
{ type: "turn-complete", time: 123, viewed: false, session: "session-1" },
|
||||
{ type: "error", time: 124, viewed: true, error: { type: "api", message: "failed", status: 500 } },
|
||||
]
|
||||
const decode = Schema.decodeUnknownSync(Persistence.withInitial(NotificationStore, { list: [] }))
|
||||
const decode = ((input: unknown) => Codec.decodeOrThrow(Codec.withInitial(NotificationStore, { list: [] }), input))
|
||||
const store = decode({
|
||||
list: [valid[0], null, { type: "unknown", time: 123, viewed: false }, { ...valid[1], error: "invalid" }, valid[1]],
|
||||
})
|
||||
expect(store.list).toEqual(valid)
|
||||
expect(decode({})).toEqual({ list: [] })
|
||||
expect(decode({ list: {} })).toEqual({ list: [] })
|
||||
expect(decode(Schema.encodeSync(NotificationStore)(store))).toEqual(store)
|
||||
expect(decode(NotificationStore.encode(store))).toEqual(store)
|
||||
})
|
||||
|
||||
test("opens notification sessions through the tab router", () => {
|
||||
@@ -41,3 +40,4 @@ test("opens notification sessions through the tab router", () => {
|
||||
|
||||
expect(calls).toEqual(["add:session-1", "route:session-1", "select:session-1"])
|
||||
})
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { createStore, reconcile } from "solid-js/store"
|
||||
import { Schema } from "effect"
|
||||
import { SessionError } from "@opencode/schema/session-error"
|
||||
import { Codec } from "@/runtime/persistence/codec"
|
||||
import type { SessionError } from "@opencode/schema/session-error"
|
||||
import { type Accessor, batch, createEffect, createMemo, createRoot, getOwner, onCleanup } from "solid-js"
|
||||
import { createSimpleContext } from "@opencode/ui/context"
|
||||
import type { ServerSDK } from "@/runtime/server/client"
|
||||
@@ -10,7 +10,7 @@ import { useLanguage } from "@/runtime/i18n/language"
|
||||
import { useSettings } from "@/settings/model"
|
||||
import { decode64 } from "@/runtime/persistence/base64"
|
||||
import { Persist, persisted } from "@/runtime/persistence/storage"
|
||||
import { Persistence } from "@/runtime/persistence/schema"
|
||||
|
||||
import { playSoundById } from "@/shell/notifications/sound"
|
||||
import type { createNotificationCoordinator } from "@/shell/notifications/coordinator"
|
||||
import { useGlobal } from "@/runtime/server/runtime"
|
||||
@@ -20,19 +20,28 @@ import { requireServerKey, sessionHref } from "@/shell/routes/session"
|
||||
import type { ServerScope } from "@/runtime/server/scope"
|
||||
import { useServer } from "@/runtime/server/current"
|
||||
|
||||
const NotificationBase = {
|
||||
directory: Schema.optional(Schema.String),
|
||||
session: Schema.optional(Schema.String),
|
||||
metadata: Schema.optional(Schema.Unknown),
|
||||
time: Schema.Finite,
|
||||
viewed: Schema.Boolean,
|
||||
}
|
||||
export const Notification = Schema.Union([
|
||||
Persistence.struct({ ...NotificationBase, type: Schema.Literal("turn-complete") }),
|
||||
Persistence.struct({ ...NotificationBase, type: Schema.Literal("error"), error: SessionError.Error }),
|
||||
const NotificationBase = {
|
||||
directory: Codec.optional(Codec.string),
|
||||
session: Codec.optional(Codec.string),
|
||||
metadata: Codec.optional(Codec.unknown),
|
||||
time: Codec.number,
|
||||
viewed: Codec.boolean,
|
||||
}
|
||||
// The error payload is whatever the server reported; shape-checking it here would load the shared
|
||||
// Effect schema into the renderer's startup path for a value the server already validated.
|
||||
const StoredSessionError = Codec.make<SessionError.Error, unknown>(
|
||||
(value) =>
|
||||
typeof value === "object" && value !== null && "name" in value && "message" in value
|
||||
? (value as unknown as SessionError.Error)
|
||||
: Codec.INVALID,
|
||||
(value) => value,
|
||||
)
|
||||
export const Notification = Codec.union([
|
||||
Codec.struct({ ...NotificationBase, type: Codec.literal("turn-complete") }),
|
||||
Codec.struct({ ...NotificationBase, type: Codec.literal("error"), error: StoredSessionError }),
|
||||
])
|
||||
export type Notification = typeof Notification.Type
|
||||
export const NotificationStore = Persistence.struct({ list: Persistence.array(Notification) })
|
||||
export const NotificationStore = Codec.struct({ list: Codec.lenientArray(Notification) })
|
||||
|
||||
type NotificationIndex = {
|
||||
session: {
|
||||
@@ -368,3 +377,5 @@ export const useNotification = () => {
|
||||
const server = useServer()
|
||||
return server.ctx.notification
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { expect, test } from "bun:test"
|
||||
import { Schema } from "effect"
|
||||
import { HighlightsStore } from "./highlights"
|
||||
import { Persistence } from "@/runtime/persistence/schema"
|
||||
import { Codec } from "@/runtime/persistence/codec"
|
||||
|
||||
test("highlight persistence defaults missing or invalid versions and round-trips valid versions", () => {
|
||||
const decode = Schema.decodeUnknownSync(Persistence.withInitial(HighlightsStore, { version: undefined }))
|
||||
const decode = ((input: unknown) => Codec.decodeOrThrow(Codec.withInitial(HighlightsStore, { version: undefined }), input))
|
||||
expect(decode({})).toEqual({ version: undefined })
|
||||
expect(decode({ version: null })).toEqual({ version: undefined })
|
||||
const value = decode({ version: "1.2.3", legacy: true })
|
||||
expect(value).toEqual({ version: "1.2.3" })
|
||||
expect(Schema.encodeSync(HighlightsStore)(value)).toEqual(value)
|
||||
expect(HighlightsStore.encode(value)).toEqual(value)
|
||||
})
|
||||
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
import { createEffect, onCleanup } from "solid-js"
|
||||
import { createStore } from "solid-js/store"
|
||||
import { Schema } from "effect"
|
||||
import { Codec } from "@/runtime/persistence/codec"
|
||||
import { createSimpleContext } from "@opencode/ui/context"
|
||||
import { useDialog } from "@opencode/ui/context/dialog"
|
||||
import { usePlatform } from "@/runtime/platform/platform"
|
||||
import { useSettings } from "@/settings/model"
|
||||
import { persisted } from "@/runtime/persistence/storage"
|
||||
import { Persistence } from "@/runtime/persistence/schema"
|
||||
|
||||
import { DialogReleaseNotes, type Highlight } from "@/shell/updates/release-notes"
|
||||
|
||||
const CHANGELOG_URL = "https://opencode.ai/changelog.json"
|
||||
|
||||
export const HighlightsStore = Persistence.struct({
|
||||
version: Schema.UndefinedOr(Schema.String),
|
||||
export const HighlightsStore = Codec.struct({
|
||||
version: Codec.undefinedOr(Codec.string),
|
||||
})
|
||||
|
||||
type ParsedRelease = {
|
||||
@@ -233,3 +233,4 @@ export const { use: useHighlights, provider: HighlightsProvider } = createSimple
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
@@ -1,20 +1,19 @@
|
||||
import type { FileContent } from "@/runtime/server/types"
|
||||
import { Schema } from "effect"
|
||||
import { Persistence } from "@/runtime/persistence/schema"
|
||||
import { Codec } from "@/runtime/persistence/codec"
|
||||
|
||||
export const FileSelection = Persistence.struct({
|
||||
startLine: Schema.Number,
|
||||
startChar: Schema.Number,
|
||||
endLine: Schema.Number,
|
||||
endChar: Schema.Number,
|
||||
export const FileSelection = Codec.struct({
|
||||
startLine: Codec.number,
|
||||
startChar: Codec.number,
|
||||
endLine: Codec.number,
|
||||
endChar: Codec.number,
|
||||
})
|
||||
export type FileSelection = typeof FileSelection.Type
|
||||
|
||||
export const SelectedLineRange = Persistence.struct({
|
||||
start: Schema.Number,
|
||||
end: Schema.Number,
|
||||
side: Persistence.optional(Schema.Literals(["additions", "deletions"])),
|
||||
endSide: Persistence.optional(Schema.Literals(["additions", "deletions"])),
|
||||
export const SelectedLineRange = Codec.struct({
|
||||
start: Codec.number,
|
||||
end: Codec.number,
|
||||
side: Codec.lenientOptional(Codec.literals(["additions", "deletions"])),
|
||||
endSide: Codec.lenientOptional(Codec.literals(["additions", "deletions"])),
|
||||
})
|
||||
export type SelectedLineRange = typeof SelectedLineRange.Type
|
||||
|
||||
@@ -44,3 +43,4 @@ export function selectionFromLines(range: SelectedLineRange): FileSelection {
|
||||
endChar: 0,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { createEffect, createRoot } from "solid-js"
|
||||
import { produce } from "solid-js/store"
|
||||
import { Schema } from "effect"
|
||||
import { Codec } from "@/runtime/persistence/codec"
|
||||
import { Persist, persisted } from "@/runtime/persistence/storage"
|
||||
import { Persistence } from "@/runtime/persistence/schema"
|
||||
import { createScopedCache } from "@/runtime/server/scoped-cache"
|
||||
import { SelectedLineRange } from "./types"
|
||||
import type { ServerScope } from "@/runtime/server/scope"
|
||||
@@ -11,14 +10,14 @@ const WORKSPACE_KEY = "__workspace__"
|
||||
const MAX_FILE_VIEW_SESSIONS = 20
|
||||
const MAX_VIEW_FILES = 500
|
||||
|
||||
const FileViewSchema = Persistence.struct({
|
||||
scrollTop: Persistence.optional(Schema.Finite),
|
||||
scrollLeft: Persistence.optional(Schema.Finite),
|
||||
selectedLines: Persistence.optional(Schema.NullOr(SelectedLineRange)),
|
||||
const FileViewSchema = Codec.struct({
|
||||
scrollTop: Codec.lenientOptional(Codec.number),
|
||||
scrollLeft: Codec.lenientOptional(Codec.number),
|
||||
selectedLines: Codec.lenientOptional(Codec.nullOr(SelectedLineRange)),
|
||||
})
|
||||
|
||||
export const FileViewsSchema = Schema.Struct({
|
||||
file: Persistence.record(Persistence.fallback(FileViewSchema, () => ({}))),
|
||||
export const FileViewsSchema = Codec.struct({
|
||||
file: Codec.lenientRecord(Codec.fallback(FileViewSchema, () => ({}))),
|
||||
})
|
||||
|
||||
function normalizeSelectedLines(range: SelectedLineRange): SelectedLineRange {
|
||||
@@ -150,3 +149,4 @@ export function createFileViewCache(scope: ServerScope) {
|
||||
clear: () => cache.clear(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user