Files
oikos/web/src/lib/stores/background.svelte.ts
dtoro 873b00ac42
Some checks failed
ci / build-test (push) Has been cancelled
ci / docker-build (push) Has been cancelled
ci / web (push) Has been cancelled
Desktop App / Build Linux (amd64) (push) Has been cancelled
Desktop App / Attach to Release (push) Has been cancelled
style(web): fix prettier config, format entire web/ tree
.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>
2026-07-27 12:56:07 +02:00

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()
}