@kyush/shared workspace package
- Add shared workspace with package.json + tsconfig + index/schemas/types
- Schemas cover the major input shapes (CreateUser, UpdateBackend,
CreateScriptInput as a discriminated union, AdminLoginInput, ScriptTestInput,
v1 chat completion request/response, etc.)
- Loose chat completion schemas for the conversation timeline
- Both server and client now depend on the workspace package; client/src/types
is now a thin re-export of @kyush/shared
Server route validation
- All admin/auth + admin + scripts mutating routes now use @hono/zod-validator
with the shared input schemas, eliminating dozens of manual `if (!field)` checks
- server/src/schemas/{common,v1}.ts re-export the shared schemas with .openapi()
metadata so the OpenAPI doc still describes everything
Single-source process.env
- New server/src/config/env.ts is the only file that touches process.env
- admin-auth, db-paths, time, ModelCatalogService, index, main all read from env
- Each value is parsed/normalised exactly once with sensible fallbacks
Production-ready auth (TanStack Query)
- @tanstack/solid-query is the new data layer for the session
- AuthProvider wraps QueryClientProvider, exposes session/loading/login/logout
via a useAuth() context that mirrors the previous shape
- 401 responses collapse the cached session via setQueryData; CSRF is mirrored
into the api client through a tracked createEffect
API client (ky + es-toolkit + shared types)
- client/src/api/client.ts now imports CreateUserInput, CreateScriptInput, etc.
from @kyush/shared
- compactSearchParams() uses es-toolkit's omitBy to drop undefined query params
- buildUrl() retained for OIDC redirects through window.location.href
Lazy imports + default exports
- Every lazy-loaded route file (Dashboard, Users, Backends, Analytics,
DetailLogs, Models, Scripts) now has a default export
- App.tsx + Scripts.tsx use the cleaner `lazy(() => import('./...'))` pattern
with no `.then((m) => ({ default: m.X }))` indirection
- ScriptEditor moved to client/src/components/script-editor.tsx (kebab-case)
with default export; LoginGate similarly moved to login-gate.tsx
UI primitive cleanup
- KCheckbox/KSelect/KDialog/KDropdownMenu/KPopover/KSwitch/KTabs/KTextField/
KToast/KTooltip → CheckboxPrimitive/SelectPrimitive/DialogPrimitive/...
- Select.tsx and Checkbox.tsx now use lucide-solid icons (Check, ChevronDown)
instead of '✓' / '▾' literals
- Select drops its createMemo over a tiny option list — inline derivation is
cheaper than the memo bookkeeping
ConversationTimeline refactor (the prior hacky parser)
- Drives entirely off the new LooseChatCompletionRequestSchema /
LooseChatCompletionResponseSchema
- All the inlined `(message as Record<string, unknown>).reasoning_content`
type-narrowing chains are gone; helpers extract messages cleanly
- createMemo wrappers replaced by inline reactive derivations
ts-reset + Tailwind v4 + es-toolkit + Node 24 cleanups
- Add @total-typescript/ts-reset to client and server (reset.d.ts in both)
- @tailwindcss/vite plugin wired into client/vite.config.ts; styles.css now
imports tailwindcss and bridges existing tokens via @theme inline
- es-toolkit added to client + server
- Replace `path.dirname(fileURLToPath(import.meta.url))` with the native
`import.meta.dirname` (Node 20.11+) across server config files + tests
Type-guard / no-`as` cleanup pass
- isPragmaColumnRow type guard replaces `as Array<{ name: string }>` casts in
database.ts and request-logs-db.ts
- Lazy DB singletons rewrite: `let db: Database | undefined` + `db ??= openDb()`
removes the `undefined as unknown as Database` reset hack
- Scripts.tsx splits the save payload through buildCreatePayload /
buildUpdatePayload helpers that use a switch on script_type so the
discriminated union picks the correct variant — no payload casts
Co-Authored-By: Claude <noreply@anthropic.com>