Files
orca/src/shared/plugins/artifacts/plugin-theme-background.ts
T
Neil f79b478c07 feat(themes): show the theme background image through native chat (#36796)
* feat(themes): show the theme background image through native chat

* fix(themes): keep unfocused chat splits undimmed over a theme image
2026-11-01 01:19:55 -08:00

85 lines
2.8 KiB
TypeScript

import { z } from 'zod'
import { pluginRelativePathSchema } from '../manifest/plugin-manifest-fields'
import {
PLUGIN_MANIFEST_V2_SINCE,
pluginFieldDoc,
pluginPathFieldDoc
} from '../manifest/plugin-schema-field-docs'
import { PLUGIN_THEME_BACKGROUND_RULE_TEXT } from '../images/plugin-image-fields'
import { parsePluginImageUrl } from '../images/plugin-image-url'
const NEXT = PLUGIN_MANIFEST_V2_SINCE
const backgroundStyleFields = {
opacity: z
.number()
.min(0)
.max(1)
.optional()
.meta(
pluginFieldDoc(
'How strongly the image shows over the theme background, 0 to 1. Defaults to 1.',
NEXT,
[0.35]
)
),
size: z
.enum(['cover', 'contain', 'tile'])
.optional()
.meta(pluginFieldDoc('How the image fills the window. Defaults to `cover`.', NEXT)),
position: z
.enum(['center', 'top', 'bottom', 'left', 'right'])
.optional()
.meta(pluginFieldDoc('Which edge the image anchors to. Defaults to `center`.', NEXT))
}
const backgroundFields = {
path: pluginRelativePathSchema.meta(
pluginPathFieldDoc(`Image file in the plugin. ${PLUGIN_THEME_BACKGROUND_RULE_TEXT}`, NEXT, [
'images/dusk.webp'
])
),
...backgroundStyleFields
}
/** `validate:plugin` shape: unknown keys fail authoring. */
export const pluginThemeBackgroundSchema = z
.strictObject(backgroundFields)
.meta(
pluginFieldDoc(
'One image painted under translucent terminals and chats (Background Opacity below 1). Static; never blurred.',
NEXT
)
)
export type PluginThemeBackgroundArtifact = z.infer<typeof pluginThemeBackgroundSchema>
// Why per-field catch: a style value added later falls back to its default instead of dropping the image.
const tolerantBackgroundSchema = z.object({
path: backgroundFields.path,
opacity: backgroundStyleFields.opacity.catch(undefined),
size: backgroundStyleFields.size.catch(undefined),
position: backgroundStyleFields.position.catch(undefined)
})
/** Runtime keeps a theme whose background is malformed or newer; at worst it paints no image. */
export function tolerantPluginThemeBackground(
value: unknown
): PluginThemeBackgroundArtifact | undefined {
const parsed = tolerantBackgroundSchema.safeParse(value)
return parsed.success ? parsed.data : undefined
}
/** What main hands the renderer: an `orca-plugin-image` URL main serves only after checking the bytes. */
export const pluginThemeBackgroundRegistrationSchema = z.object({
url: z.string().refine((url) => parsePluginImageUrl(url)?.kind === 'installed'),
...backgroundStyleFields
})
/** Registration field: an unreadable background drops the image, never the theme. */
export const tolerantPluginThemeBackgroundRegistration = pluginThemeBackgroundRegistrationSchema
.optional()
.catch(undefined)
export type PluginThemeBackground = z.infer<typeof pluginThemeBackgroundRegistrationSchema>