.prettierrc.json was missing "semi": false, so prettier wanted to add semicolons to a codebase written without them (763 semicolon-free statements vs. 150 with, in hand-written .ts; zero hand-written .svelte files use them at all). That's why prettier --check failed on 249 files — not because the code was unformatted, but because the config didn't match the actual house style. Added "semi": false; left printWidth/etc as configured (printWidth barely moves the failure count: 218/213/212 files at 100/120/140). Ran `prettier --write .` with the corrected config. Verified semantics-preserving before and after: - eslint: 142 problems both before and after, byte-identical - build passes, 38/38 tests pass - token-stream diff (whitespace/semicolons/quotes normalized) on all 218 changed files: only 52 had any remaining token change, all either trailing-comma removal (matching trailingComma: "none") or import/ ternary reflow — no semantic changes - live smoke test: Knowledge, Tasks, Fleet map, and a chat window (AgentTrace, markdown, Scope graph, activity rail) all render correctly, no console errors Most of the diff is shadcn/ui vendor files (lib/components/ui/) moving from the CLI's own style (double quotes, tabs, semicolons) to house style; re-running `shadcn-svelte add` on a component will need a follow-up format pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
124 lines
3.7 KiB
TypeScript
124 lines
3.7 KiB
TypeScript
import { PATTERNS, type BackgroundPatternId } from '$lib/desktop-patterns'
|
|
|
|
export interface BackgroundConfig {
|
|
pattern: BackgroundPatternId
|
|
color: string // pattern foreground color, hex
|
|
fillColor: string | null // fill behind the pattern (its "gaps"); null = transparent, i.e. the app's own theme background shows through
|
|
opacity: number // 0..1
|
|
fade: number // 0..1 — 0 disables the vignette mask entirely
|
|
scale: number // pattern tile-size multiplier; 1 = natural size
|
|
rotation: number // degrees, 0..359
|
|
}
|
|
|
|
const STORAGE_KEY = 'oikos-desktop-bg'
|
|
const VALID_IDS = new Set(PATTERNS.map((p) => p.id))
|
|
|
|
// Matches the previous hardcoded desktop background (bubbles, brand blue,
|
|
// tuned low so it reads as texture rather than noise) so this feature ships
|
|
// as "now configurable" rather than a visual regression on first load.
|
|
const DEFAULTS: BackgroundConfig = {
|
|
pattern: 'bubbles',
|
|
color: '#444cf7',
|
|
fillColor: null,
|
|
opacity: 0.08,
|
|
fade: 0,
|
|
scale: 1,
|
|
rotation: 0
|
|
}
|
|
|
|
function clamp01(v: unknown, fallback: number): number {
|
|
const n = typeof v === 'number' ? v : Number(v)
|
|
if (!Number.isFinite(n)) return fallback
|
|
return Math.min(1, Math.max(0, n))
|
|
}
|
|
|
|
function clampScale(v: unknown): number {
|
|
const n = typeof v === 'number' ? v : Number(v)
|
|
if (!Number.isFinite(n)) return DEFAULTS.scale
|
|
return Math.min(3, Math.max(0.4, n))
|
|
}
|
|
|
|
function clampRotation(v: unknown): number {
|
|
const n = typeof v === 'number' ? v : Number(v)
|
|
if (!Number.isFinite(n)) return DEFAULTS.rotation
|
|
return ((n % 360) + 360) % 360
|
|
}
|
|
|
|
function isHexColor(c: unknown): c is string {
|
|
return typeof c === 'string' && /^#[0-9a-fA-F]{6}$/.test(c)
|
|
}
|
|
|
|
function storedConfig(): BackgroundConfig {
|
|
if (typeof localStorage === 'undefined') return DEFAULTS
|
|
const raw = localStorage.getItem(STORAGE_KEY)
|
|
if (!raw) return DEFAULTS
|
|
try {
|
|
const parsed = JSON.parse(raw)
|
|
return {
|
|
pattern: VALID_IDS.has(parsed.pattern) ? parsed.pattern : DEFAULTS.pattern,
|
|
color: isHexColor(parsed.color) ? parsed.color : DEFAULTS.color,
|
|
fillColor:
|
|
parsed.fillColor === null
|
|
? null
|
|
: isHexColor(parsed.fillColor)
|
|
? parsed.fillColor
|
|
: DEFAULTS.fillColor,
|
|
opacity: clamp01(parsed.opacity, DEFAULTS.opacity),
|
|
fade: clamp01(parsed.fade, DEFAULTS.fade),
|
|
scale: clampScale(parsed.scale),
|
|
rotation: clampRotation(parsed.rotation)
|
|
}
|
|
} catch {
|
|
return DEFAULTS
|
|
}
|
|
}
|
|
|
|
let config: BackgroundConfig = $state(storedConfig())
|
|
|
|
function persist(): void {
|
|
if (typeof localStorage !== 'undefined') localStorage.setItem(STORAGE_KEY, JSON.stringify(config))
|
|
}
|
|
|
|
export function getBackground(): BackgroundConfig {
|
|
return config
|
|
}
|
|
|
|
export function setBackgroundPattern(pattern: BackgroundPatternId): void {
|
|
config = { ...config, pattern }
|
|
persist()
|
|
}
|
|
|
|
export function setPatternColor(color: string): void {
|
|
if (!isHexColor(color)) return
|
|
config = { ...config, color }
|
|
persist()
|
|
}
|
|
|
|
// null clears back to "auto" (transparent — the app's theme background
|
|
// shows through the pattern's gaps).
|
|
export function setFillColor(color: string | null): void {
|
|
if (color !== null && !isHexColor(color)) return
|
|
config = { ...config, fillColor: color }
|
|
persist()
|
|
}
|
|
|
|
export function setBackgroundOpacity(opacity: number): void {
|
|
config = { ...config, opacity: clamp01(opacity, DEFAULTS.opacity) }
|
|
persist()
|
|
}
|
|
|
|
export function setBackgroundFade(fade: number): void {
|
|
config = { ...config, fade: clamp01(fade, DEFAULTS.fade) }
|
|
persist()
|
|
}
|
|
|
|
export function setBackgroundScale(scale: number): void {
|
|
config = { ...config, scale: clampScale(scale) }
|
|
persist()
|
|
}
|
|
|
|
export function setBackgroundRotation(rotation: number): void {
|
|
config = { ...config, rotation: clampRotation(rotation) }
|
|
persist()
|
|
}
|