Roadmap

What's done, what's next, and where Bosia is headed.

Track what's done, what's next, and where we're headed. Current version: 0.6.0


Severity: πŸ”΄ Critical Β· 🟠 Major Β· 🟑 Minor Β· βšͺ Trivial


Completed (v0.0.1 – v0.1.26)

Click to expand completed items

Core Framework

  • πŸ”΄ SSR with Svelte 5 Runes ($props, $state)
  • πŸ”΄ File-based routing (+page.svelte, +layout.svelte, +server.ts)
  • 🟠 Dynamic routes ([param]) and catch-all routes ([...rest])
  • 🟑 Route groups ((group)) for layout grouping
  • 🟠 API routes β€” +server.ts with HTTP verb exports
  • 🟠 Error pages β€” +error.svelte

Data Loading

  • πŸ”΄ Plain export async function load() pattern (no wrapper)
  • 🟠 $types codegen β€” auto-generated PageData, PageProps, LayoutData, LayoutProps
  • 🟠 parent() data threading in layouts
  • 🟠 Streaming SSR for metadata (non-blocking load())
  • 🟠 Form actions (SvelteKit-style)

Server

  • πŸ”΄ ElysiaJS HTTP server
  • 🟑 Gzip compression
  • 🟑 Static file caching (Cache-Control headers)
  • 🟑 /_health endpoint
  • 🟠 Cookie support (cookies.get, cookies.set, cookies.delete)
  • 🟠 Security headers (X-Content-Type-Options, X-Frame-Options, etc.)
  • 🟑 DISABLE_X_FRAME_OPTIONS=true env var to omit X-Frame-Options for intentional cross-origin iframe embedding
  • 🟠 Graceful shutdown handler (SIGTERM/SIGINT)
  • 🟠 .env file support with $env virtual module
  • 🟑 CORS configuration (framework-level)
  • 🟠 Session-aware fetch (cookies forwarded in internal API calls)
  • 🟑 Request timeouts on load() and metadata() functions
  • 🟠 Route PUT/PATCH/DELETE through handleRequest() β€” consistent CSRF, CORS, security headers, and cookie handling
  • 🟠 Graceful shutdown drain β€” drain in-flight requests before stopping; return 503 from health check during shutdown
  • 🟑 Concurrent build guard in dev β€” prevent overlapping builds when rapid file changes trigger buildAndRestart() while a build is already running
  • 🟑 Clean dev server shutdown β€” release Bun.serve, file watchers, and timers on SIGINT so the event loop drains naturally; outer bun run reports exit 0 instead of 130
  • 🟠 Dev watcher safety net β€” 5s mtime poll of src/ complements fs.watch so atomic-write edits (temp + rename) that macOS drops still trigger rebuilds
  • 🟠 Dev crash backoff β€” replace the "stop after 3 crashes" silent-stop with exponential backoff (500ms β†’ 5s) that never gives up, so a transient error or fixed source change brings the app back without manual restart

Security

  • πŸ”΄ XSS escaping in HTML templates β€” sanitize JSON.stringify() output in <script> tags
  • πŸ”΄ SSRF validation on /__bosia/data/ β€” validate route path segment
  • πŸ”΄ CSRF protection β€” Origin/Referer header validation for state-changing requests
  • 🟠 Strip stack traces from error responses in production
  • 🟠 Request body size limits
  • πŸ”΄ Path traversal protection β€” validate static/prerendered file paths stay within allowed directories
  • 🟑 Cookie parsing error recovery β€” wrap decodeURIComponent() in try-catch
  • 🟑 Cookie option validation β€” whitelist/validate domain, path, sameSite values
  • 🟠 PUBLIC_ env scoping β€” only expose vars declared in .env files
  • 🟠 Streaming error safety β€” validate route match before creating stream
  • 🟑 safeJsonStringify crash guard β€” try-catch for circular reference protection
  • 🟠 Open redirect validation on redirect()
  • 🟑 Cookie RFC 6265 validation β€” validate names against HTTP token spec; use encodeURIComponent only for values

Client

  • πŸ”΄ Client-side hydration
  • πŸ”΄ SPA router (client-side navigation)
  • 🟑 Navigation progress bar
  • 🟠 HMR via SSE in dev mode
  • 🟑 Per-page CSR opt-out (export const csr = false)
  • 🟑 Link prefetching β€” data-bosia-preload attribute for hover/viewport prefetch
  • 🟠 Fix client-side navigation with query strings/hashes
  • 🟑 Use insertAdjacentHTML for head injection β€” prevents re-parsing <head>, avoiding duplicate stylesheets and script re-execution

Build & Tooling

  • πŸ”΄ Bun build pipeline (client + server bundles)
  • 🟠 Manifest generation (dist/manifest.json)
  • 🟠 Static route prerendering (export const prerender = true)
  • 🟠 Tailwind CSS v4 integration
  • 🟠 $lib alias β†’ src/lib/*
  • 🟑 bosia:routes virtual module
  • 🟑 Validate Tailwind CSS binary exists before build
  • 🟑 Prerender fetch timeout
  • 🟑 Fix withTimeout timer leak
  • βšͺ Remove duplicate static file serving
  • 🟠 Static site output β€” merge prerendered HTML + client assets + public into dist/static/ for static hosting
  • 🟑 Validate .env variable names β€” reject invalid identifiers that break codegen
  • 🟑 .env parser escape sequence support β€” handle \n, \", etc. in quoted values

Routing

  • 🟠 Dynamic route prerendering with entries() export β€” enumerate dynamic route params for static prerendering

CLI

  • πŸ”΄ bosia dev β€” dev server with file watching
  • πŸ”΄ bosia build β€” production build
  • πŸ”΄ bosia start β€” production server
  • 🟠 bosia create β€” scaffold new project (with --template flag and interactive picker)
  • 🟠 bosia add β€” registry-based UI component installation
  • 🟠 bosia feat β€” registry-based feature scaffolding
  • 🟑 bosia add index-based path resolution β€” resolves component names from index.json instead of blindly prefixing ui/
  • 🟑 bosia feat nested feature dependencies β€” features field in meta.json for recursive installation
  • 🟑 bosia feat overwrite prompt β€” asks before replacing existing files
  • 🟑 bosia add multi-component install β€” bosia add button card input installs all in one call
  • 🟑 bosia add -y / --yes flag β€” auto-confirm overwrite prompts for CI / scripts

Templates & Features

  • 🟠 todo template (formerly drizzle) β€” PostgreSQL + Drizzle ORM with full CRUD todo demo
  • 🟠 drizzle feature β€” bosia feat drizzle scaffolds DB connection, schema aggregator, migrations dir, seed runner
  • 🟠 Multi-engine drizzle feature β€” adapter, drizzle.config.ts, and seed-runner branch on DATABASE_URL scheme (postgres, mysql, sqlite file, sqlite in-memory) over Bun's built-in drivers (no per-engine npm dep)
  • 🟠 bosia-brief-database skill + hook into bosia-brief-intake β€” captures DB engine + connection during brief intake, writes ## Database block to BRIEF.md
  • 🟠 todo feature β€” bosia feat todo scaffolds todo schema, repository, service, routes, components, and seed data
  • 🟑 todo component β€” bun x bosia@latest add todo installs todo-form, todo-item, todo-list components
  • 🟑 Registry as single source of truth β€” bosia create --template todo installs features from registry via template.json instead of duplicating files

Hooks & Middleware

  • 🟠 hooks.server.ts with Handle interface
  • 🟑 sequence() helper for composing middleware
  • 🟠 RequestEvent β€” request, params, url, cookies, locals

Docs & Ecosystem

  • 🟠 Documentation site (Astro Starlight) β€” 14 pages
  • 🟑 Indonesian (Bahasa Indonesia) translation with Starlight i18n
  • 🟑 Deployment guides (Docker, Railway, Fly.io)
  • 🟠 GitHub Actions for auto-publishing to npm and deploying docs
  • 🟑 Dev server auto-restart on crash
  • 🟑 Components documentation page with usage examples and prop tables
  • 🟑 Interactive component previews in docs β€” live Svelte demos (button, badge, input, separator, avatar, card, dropdown-menu)
  • 🟑 Nested registry structure for todo components β€” subfolder pattern matching ui/, with group install (bun x bosia@latest add todo) and individual install (bun x bosia@latest add todo/todo-form)
  • 🟑 Nested docs sidebar β€” UI and Todo as sub-groups under Components
  • 🟠 SEO infrastructure β€” Metadata type supports lang and link fields; dynamic <html lang>; <link> tag rendering in streaming SSR
  • 🟑 Docs SEO β€” OG tags, Twitter cards, canonical URLs, hreflang alternates on all pages
  • 🟑 robots.txt and sitemap.xml generation for docs site

v0.1.0

  • 🟑 Rename framework from bosbun to bosia
  • βšͺ Dead code cleanup (renderSSR, buildHtmlShell, unexported internals)
  • 🟑 splitCsvEnv helper for CSRF/CORS origin parsing

v0.2.0 β€” Production Hardening

Stability, security, and performance improvements for production workloads.

Security

Findings #1–#7 below come from the v0.4.5 security audit β€” see backup/SECURITY_ISSUE_1.md for full context, attack scenarios, and proposed diffs.

  • Cookie secure defaults β€” default HttpOnly; Secure; SameSite=Lax on cookies.set() with opt-out
  • Auto-detect Cache-Control on /__bosia/data/ β€” private, no-cache when cookies accessed; public, max-age=0, must-revalidate otherwise
  • πŸ”΄ load() fetch cookie scoping β€” makeFetch now forwards the Cookie header only to same-origin requests or origins in the INTERNAL_HOSTS allowlist; third-party hosts get no cookie. User-supplied init.headers.cookie is preserved
  • πŸ”΄ Audit #1 β€” allowExternal redirect validation β€” still validate against javascript:, data:, vbscript: schemes even when allowExternal: true (move DANGEROUS_SCHEMES check above the early return in errors.ts:32)
  • 🟠 Audit #4 β€” Trusted proxy configuration β€” TRUST_PROXY env to control when X-Forwarded-* headers are trusted in CSRF checks (csrf.ts:37-40)
  • 🟠 Audit #6 β€” CSP nonce infrastructure β€” per-request nonce generation, inject into all framework <script> tags, expose nonce in hooks for user scripts, opt-in CSP_DIRECTIVES env emits matching Content-Security-Policy header
  • 🟠 Audit #2 β€” CORS preflight validation β€” validate Access-Control-Request-Method / Access-Control-Request-Headers against allowed config in handlePreflight (cors.ts:53-69)
  • 🟠 Audit #3 β€” CORS Vary: Origin on all responses when CORS is configured β€” prevent CDN caching bugs on non-matching origins (set at server.ts request level, not only in getCorsHeaders)
  • 🟑 Audit #5 β€” Validate prerender entries() return values β€” reject /, \, .. in dynamic segment values before URL substitution (prerender.ts:44-50)
  • 🟑 Escape lang attribute in HTML shell β€” <html lang="${lang}"> injects lang raw; if a metadata() derives lang from URL/user input it can break out of the attribute
  • βšͺ Validate CORS_MAX_AGE env β€” reject non-numeric values instead of producing NaN header

Security test coverage (from audit)

  • 🟑 Test: allowExternal: true still rejects javascript: / data: / vbscript: URLs
  • 🟑 Test: handlePreflight rejects when Access-Control-Request-Method is not in allowedMethods
  • 🟑 Test: Vary: Origin is present on CORS-configured responses even when requesting origin doesn't match
  • 🟑 Test: dedicated safePath() unit test file (currently only covered indirectly via static file serving)
  • 🟑 Test: substituteParams() rejects malicious entry values containing path-traversal characters
  • 🟑 Test: TRUST_PROXY env gates X-Forwarded-* header trust in CSRF checks

Performance

  • 🟠 Parallelize client + server builds β€” run both Bun.build() calls with Promise.all() instead of sequentially (~500-1000ms savings)
  • 🟠 Parallelize Tailwind CSS with builds β€” run Tailwind CLI concurrently with client+server builds (~500-800ms savings); ensure output exists before manifest step
  • 🟑 Convert sequence() middleware recursion to loop β€” apply(i+1, e) pattern risks stack overflow with many handlers; use iterative approach

Server Reliability

  • 🟠 Stream backpressure handling β€” check controller.desiredSize to prevent memory buildup on slow/disconnected clients
  • 🟠 Streaming SSR error recovery β€” render proper error page instead of bare <p>Internal Server Error</p> when render() throws mid-stream
  • 🟠 renderPageWithFormData loader error handling β€” currently does not catch HttpError/Redirect thrown from loadRouteData() after a successful form action; let them surface as proper redirect/error responses instead of crashing the request
  • 🟑 Prerender process cleanup β€” proper signal handling, verified termination (await child.exited after kill()), use random port instead of hardcoded 13572
  • 🟑 Fix buildAndRestart recursive tail call β€” replace recursion with while loop to prevent stack growth under rapid file changes

Client

  • 🟑 Bound prefetch cache size β€” prefetchCache grows unbounded between navigations; add LRU eviction (max ~50 entries)
  • 🟑 Prefetch cache TTL β€” stale prefetch data served after long idle; discard entries older than 30s on consumePrefetch()
  • 🟠 Router click handler must respect modifier/middle clicks β€” router.svelte.ts currently SPA-navigates on Cmd/Ctrl/Shift/Alt+click and middle-click, breaking "open in new tab/window". Bail when e.button !== 0, any modifier key is held, e.defaultPrevented, or anchor has rel="external"

Build

  • 🟑 Fail build on tsconfig.json corruption β€” don't silently continue with degraded config
  • 🟑 compress() threshold uses character count not byte count β€” body.length on a UTF-8 string under-counts multi-byte content; switch to Buffer.byteLength or TextEncoder().encode(...).length before threshold check
  • 🟑 .env parser inline-comment stripping β€” KEY="value" # note currently keeps # note as part of the value; strip trailing comment after the closing quote
  • βšͺ Tune gzip compression threshold β€” raised to 2KB (GZIP_MIN_BYTES = 2048); small responses fit in single TCP packet, gzip overhead outweighs savings below this size

DX

  • 🟠 Audit #7 β€” Dev proxy must forward X-Forwarded-Host / X-Forwarded-Proto to the inner app server (dev.ts:208-220) β€” without them the inner CSRF check derives expectedOrigin = http://localhost:APP_PORT while the browser's Origin is http://localhost:DEV_PORT, causing same-origin POST/form actions to 403 in dev (audit rates 🟑 β€” DX-only, production unaffected β€” but keeping 🟠 per project policy)
  • 🟑 Stale env cleanup in dev β€” reset removed .env vars on hot-reload

v0.2.1 β€” Features & DX

New capabilities and developer experience improvements.

Data Loading

  • 🟠 depends() and invalidate() β€” selective data reloading
  • 🟑 Prefetch sends the loader cache mask β€” hover/viewport data-bosia-preload was warming the data endpoint with no mask, re-running every loader server-side; now it sends the same _invalidated bits as a real nav
  • 🟑 setHeaders() in load functions β€” set response headers from loaders
  • 🟠 beforeNavigate / afterNavigate lifecycle hooks
  • 🟠 Scroll restoration and snapshot support (export const snapshot)

Routing

  • 🟠 Layout reset (+layout@.svelte or +page@.svelte)
  • 🟠 Route-level +error.svelte β€” per-layout error boundaries instead of global-only
  • 🟑 Page option: ssr toggle (export const ssr = false)
  • 🟑 Page option: trailingSlash configuration

Forms

  • 🟠 use:enhance progressive enhancement β€” client-side fetch submission with automatic form state management (like SvelteKit)

Types

  • 🟠 Typed route params β€” generate { slug: string } from [slug] instead of Record<string, string>
  • 🟑 Error page types in generated $types.d.ts

Server

  • 🟑 Structured logging with request correlation IDs

DX

  • 🟑 Cache route scanning in dev mode β€” skip fs.readdirSync() re-scan when changed file is not a route file (+page/+layout/+server/+error)
  • 🟑 Remove hardcoded 200ms SSE delay β€” poll /_health instead of Bun.sleep(200) before broadcasting reload
  • 🟑 Smarter dev rebuild triggers β€” filter watcher by extension; skip rebuilds for .md, test files, and non-source changes

v0.2.2 β€” Ecosystem, Observability & Scale

Nice-to-haves for a growing framework and performance at scale.

  • 🟑 Production sourcemaps β€” external source maps for debuggable production errors

Performance (at scale)

  • 🟠 Request deduplication β€” deduplicate concurrent identical GET requests to same route; share in-flight loader promise instead of running twice. Scope dedup key by route+params (exclude user-specific loaders). Reworked in 0.3.1 around a folder convention: dedup ON by default keyed on URL only; per-user routes opt out by living under (private)
  • πŸ”΄ Dedup key cross-user data leak β€” replaced cookie-fingerprint identity with a folder convention. Routes under any (private) group folder skip dedup entirely and run per-request; all other routes are deduped on URL alone. Apps with per-user content must place routes under (private) (dashboards, carts, settings) or User B will receive User A's loader result. See docs/guides/request-deduplication.md for safety rules
  • 🟑 Trie-based route matcher β€” replace linear O(n) route scan with radix trie for O(k) matching (k = URL segments). Matters when route count exceeds ~100
  • 🟑 Compiled route regex β€” pre-compile route patterns to RegExp at startup instead of parsing on every match
  • 🟠 Concurrency / backpressure ceiling β€” Bun currently accepts unlimited concurrent connections (server.ts:812 only sets idleTimeout/maxRequestBodySize). Under load spike or slow-loris, memory + FD exhaustion is possible before idleTimeout kicks in β€” the most likely OOM vector for single-replica container deploys. Add a soft cap (env-gated, e.g. MAX_INFLIGHT) that reuses the existing in-flight counter (server.ts:633,696) and returns 503 when exceeded. Until shipped, deployments must front Bosia with a reverse proxy that enforces connection limits. Source: 2026-05-23 pre-prod audit. Shipped in v0.5.13 (server.ts:765-784, 638-646) β€” MAX_INFLIGHT env var, default Infinity (off, no behavior change); /_health exempt; cap-check runs before all work so the 503 is cheap. Docs + .env.example files updated
  • 🟑 Response cache + brotli β€” Bun.gzipSync() runs on every HTML response >2 KB in prod (html.ts:354-378) with no precompressed cache; brotli not implemented. (a) Add an LRU response cache keyed by (path, status, content-hash) for compressed bodies on routes with no per-user data; (b) add brotli via Bun.brotliCompressSync gated on Accept-Encoding: br. Source: 2026-05-23 pre-prod audit. Shipped in v0.6.0 β€” skip-render response cache (cache.ts) keyed on URL + identity hash (cookies/headers from CACHE_KEYS), per-route opt-out via export const cache = false, server-side invalidate(key) / invalidateAll(prefix) from bosia mirroring the client API, brotli + gzip pre-compressed per entry, CSP disables the cache. Follow-ups deferred to v0.7+: TTL expiry, layout-level cascade, multi-replica pub/sub invalidation, stale-while-revalidate, key-based invalidation for +server.ts endpoints
  • 🟑 Static-asset fallthrough cost β€” every static hit calls Bun.file().exists() up to 4Γ— across /dist/client/, /public/, /dist/, /dist/static/ (server.ts:299-335). Build a manifest at boot so prod lookups become a Map check; doc nginx/Caddy offload for high-traffic deploys. Source: 2026-05-23 pre-prod audit
  • 🟑 Collapse SSR render() calls β€” root App.svelte + error pages are rendered in separate Svelte render() invocations (renderer.ts:646,804,884,931). Profile under representative load before changing β€” error pages have different layouts so collapsing isn't trivial. Source: 2026-05-23 pre-prod audit

Server Reliability

  • 🟠 Process-level error handlers in prod β€” install process.on("uncaughtException") and process.on("unhandledRejection") outside the dev inspector path. Today only the dev inspector (plugins/inspector/index.ts:121-138) installs these; in prod an unhandled rejection from a background timer, plugin hook, or work outside the request lifecycle crashes the process with no log context. Handlers should emit a structured fatal line and process.exit(1) so the orchestrator restarts cleanly. Source: 2026-05-23 pre-prod audit. Shipped in v0.5.13 (server.ts:912-927) β€” gated on !isDev so the dev inspector keeps owning error display
  • 🟑 Structured logging β€” replace emoji-prefixed console.log/console.error throughout server.ts with a minimal level-based logger that emits JSON in prod (pretty in dev) and includes a request ID. Today's mixed format is awkward for Loki/Vector/journald and prod errors only emit .message (no stack). Source: 2026-05-23 pre-prod audit
  • βšͺ Tunable shutdown timers β€” server.ts:906 hardcodes the 2 s force-exit window and 10 s drain. Expose via SHUTDOWN_DRAIN_MS / SHUTDOWN_FORCE_MS for deploys with long-running streaming responses. Source: 2026-05-23 pre-prod audit
  • βšͺ Startup banner shows resolved hostname β€” server.ts:880-882 logs http://localhost:${PORT} even though Bun binds 0.0.0.0 by default. Cosmetic only (container is reachable). Source: 2026-05-23 pre-prod audit

v0.2.3 β€” CLI & Feature Installer

Per-file install strategies so features can safely contribute to shared files.

CLI / Feat

  • 🟠 bosia feat per-file strategies β€” meta.json files: FileEntry[] with strategy field: write (default), skip-if-exists, append-line, append-block, merge-json. Replaces all-or-nothing replace prompt for shared files like src/features/drizzle/schemas.ts
  • 🟑 Document meta.json schema and strategies in docs/ (CLI / bosia feat page)
  • 🟑 bosia feat <name> --dry-run β€” preview file actions (write/skip/append/merge) without touching disk
  • 🟑 Validation: error early when two installed features write to the same target with write strategy (force one to declare append-line/append-block)
  • 🟠 auth feature scaffold β€” uses append-block to register hooks in src/hooks.server.ts and routes barrel
  • 🟑 s3 / storage feature β€” bucket client + upload route using new strategies
  • 🟑 Track installed features per project (.bosia/installed.json) β€” enable bosia feat list and uninstall

v0.3.0 β€” Test Integration (Phase 1 + 2)

Built-in testing powered by bun test. See TEST_PLAN.md for full details.

DX

  • 🟑 Prettier formatting β€” root config + scripts (format, format:check); all 3 templates ship matching .prettierrc.json so scaffolded projects format-on-create. Pre-commit hook auto-formats staged files. No lint.

CLI

  • 🟠 bosia test command β€” wraps bun test with framework-aware defaults
  • 🟑 Auto-load .env.test (fallback .env) before running tests
  • 🟑 Set BOSIA_ENV=test automatically
  • 🟑 Pass through flags (--watch, --coverage, --bail, --timeout, etc.)
  • 🟑 Unit tests for core pure utilities (matcher, cookies, csrf, cors, errors, html, dedup, env)
  • 🟑 Unit tests for build/codegen helpers (scanner, routeTypes, envCodegen, hooks.sequence, paths.resolveBosiaBin, lib/utils.cn, cli/registry.mergePkgJson, prerender path/URL helpers)

Test Utilities (`bosia/testing`)

  • 🟠 createRequestEvent() β€” mock factory for testing +server.ts handlers and hooks
  • 🟠 createLoadEvent() β€” mock factory for testing load() functions
  • 🟑 createMetadataEvent() β€” mock factory for testing metadata() functions
  • 🟠 mockCookies() β€” in-memory cookie jar implementing Cookies interface
  • 🟑 mockFetch() β€” fetch interceptor for isolating loaders
  • 🟑 createFormData() β€” helper for building form action payloads

v0.3.1 β€” Route & API Integration Testing (Phase 3)

Test routes end-to-end without starting a real server.

  • 🟠 createTestApp() β€” build an in-process Elysia instance from the route manifest
  • 🟠 testRequest() β€” send HTTP requests to the test app, get standard Response back
  • 🟠 Support API routes, page routes (SSR HTML), and form actions
  • 🟑 Response assertion helpers: expectJson(), expectRedirect(), expectHtml()

v0.3.2 β€” Component Testing (Phase 4)

Render and assert on Svelte 5 components in tests.

  • 🟠 renderComponent(Component, { props }) β€” SSR render a component, return HTML
  • 🟠 renderPage(route, options?) β€” full SSR pipeline (loader β†’ layout β†’ page)
  • 🟑 Snapshot testing support (built into bun test)
  • 🟑 Investigate @testing-library/svelte compatibility with Bun

v0.4.0 β€” Plugin Core

First-party plugin system. Standardize OpenAPI / OpenTelemetry / server-timing as plugins; let third parties drop in any Elysia plugin. Full design in plans/plugin-feature.md.

Config & Types

  • πŸ”΄ bosia.config.ts loader β€” packages/bosia/src/core/config.ts; resolve from process.cwd(), compile via Bun.build({ target: "bun" }), cache, default to { plugins: [] }
  • πŸ”΄ Public types in packages/bosia/src/lib/index.ts β€” BosiaPlugin, BosiaConfig, BuildContext, DevContext, RenderContext, defineConfig helper

Elysia Hooks

  • πŸ”΄ backend.before / backend.after mount points in server.ts β€” before runs raw routes (e.g. /openapi.json) bypassing framework middleware; after receives RouteManifest for introspection

Build Hooks

  • 🟠 build.preBuild / build.postScan / build.postBuild in build.ts β€” call preBuild before loadEnv, postScan after scanRoutes(), postBuild after generateStaticSite()
  • 🟠 build.bunPlugins(target) merged into client + server Bun.build() plugin arrays

Render Hooks

  • 🟠 render.head fragments injected before </head> in buildMetadataChunk
  • 🟠 render.bodyEnd fragments injected before </body> in buildHtmlTail
  • 🟠 RenderContext (request, route, metadata) threaded from renderer.ts into html.ts builders

First-Party Plugin

  • 🟠 bosia/plugins/server-timing β€” exercises backend.before; adds Server-Timing: handler;dur=... header

Docs & Demo

  • 🟑 docs/content/docs/guides/plugins.md β€” usage guide
  • 🟑 apps/demo/bosia.config.ts β€” server-timing wired

v0.4.1 β€” OpenAPI Plugin

Auto-bridge file routes to OpenAPI spec.

  • 🟠 bosia/plugins/openapi first-party plugin
  • 🟠 build.postScan reads RouteManifest, emits dist/openapi.json
  • 🟠 Runtime mount via backend.before β€” GET /openapi.json, GET /docs (Scalar/Swagger UI)
  • 🟑 Optional schema export on +server.ts (TypeBox or Zod, decide later)
  • 🟑 Docs: OpenAPI usage page

v0.4.2 β€” OpenTelemetry Plugin

Tracing + metrics for production apps.

  • 🟠 bosia/plugins/opentelemetry first-party plugin
  • 🟠 OTLP exporter config via env vars (OTEL_EXPORTER_OTLP_ENDPOINT, etc.)
  • 🟠 Trace backend.before request β†’ response, load() calls, render time
  • 🟑 Verify dev parity β€” telemetry must work in bosia dev

v0.4.1 β€” Inspector Plugin βœ… (shipped 2026-05-06)

Click element in browser β†’ open exact source file:line in editor / hand off to AI agent. No Vite, no React-style fiber tree β€” does it via compile-time attribute injection.

Compile-Time

  • 🟠 bosia/plugins/inspector first-party plugin (dev-only)
  • 🟠 Contributes Bun plugin via build.bunPlugins() β€” runs before SveltePlugin() and replaces its .svelte onLoad with an injecting variant
  • 🟠 Parses .svelte source with svelte/compiler parse(), walks RegularElement nodes, injects data-bosia-loc="<relpath>:<line>:<col>" via magic-string (preserves source maps)
  • 🟑 Skips <svelte:*> and component (capitalized) tags
  • 🟑 Strips attribute from production builds (no-op when not dev)

Runtime Overlay

  • 🟠 Dev-only client overlay injected via render.bodyEnd β€” alt+hover highlights element, alt+click captures data-bosia-loc
  • 🟠 POST /__bosia/locate endpoint (mounted via backend.before) β€” receives { file, line, col }, opens editor (or POSTs to aiEndpoint with comment)
  • 🟑 Editor integration β€” code -g file:line (configurable via inspector({ editor: "code" | "cursor" | "zed" }))
  • 🟑 Toast feedback β€” overlay shows "opened :" on click

Docs

  • 🟑 docs/content/docs/guides/inspector.md β€” usage + AI-agent workflow

v0.4.2 β€” Template fixes βœ… (shipped 2026-05-07)

Make a freshly scaffolded project pass bun run check out of the box.

  • 🟠 Ship .gitignore with bun x bosia create β€” npm pack strips .gitignore, so templates store it as _gitignore and copyDir restores the dotfile name on copy
  • 🟑 Ignore generated Tailwind output public/bosia-tw.css in template .prettierignore and .gitignore (default, demo, todo) so bun run check succeeds on a clean scaffold
  • 🟑 bun run check:templates β€” packs via bun pm pack, extracts the tarball, and asserts each templates/* still has the expected files (no install, no scaffold) so this class of regression fails locally before publishing

v0.5.1 β€” Inspector default in all templates βœ… (shipped 2026-05-15)

Ship every scaffolding template with a minimal bosia.config.ts so freshly scaffolded projects get Alt+click-to-source out of the box.

  • 🟑 Add bosia.config.ts to packages/bosia/templates/{default,demo,todo}/ enabling inspector({ editor: "code" }). copyDir in cli/create.ts copies it as-is (not in the exclusion list); no template substitutions needed. Production-safe (plugin self-disables under NODE_ENV=production)
  • βšͺ Note preconfigured state in docs/content/docs/guides/inspector.md so existing-project users still find the manual setup steps

v0.5.5 β€” Dev/Build dist collision βœ… (shipped 2026-05-18)

Dev and build no longer share ./dist. Dev writes to .bosia/dev/; standalone bun run build keeps writing to ./dist/.

  • 🟠 Decouple URL namespace (/dist/client/...) from on-disk location via OUT_DIR in paths.ts (reads BOSIA_OUT_DIR, default ./dist)
  • 🟠 dev.ts hardcodes .bosia/dev and passes BOSIA_OUT_DIR to spawned build + app-server children; never reads the env itself
  • 🟠 build.ts, prerender.ts, html.ts, server.ts, cli/start.ts all read from OUT_DIR instead of hardcoded ./dist literals
  • 🟑 Verification path: BOSIA_OUT_DIR=.bosia/verify bun run build produces full artifacts (manifest, client, server, prerendered, static, route-manifest) without touching ./dist. Catches what tsc --noEmit + svelte-check miss (route scan, prerender child, server-entry compile). Verified at apps/demo

v0.5.6 β€” Build/dev `.bosia/` cleanup collision βœ… (shipped 2026-05-18)

Follow-up to v0.5.5. OUT_DIR was split, but build.ts still blanket-wiped ./.bosia at startup β€” clobbering a concurrently-running bosia dev whose compiled server lives at .bosia/dev/. Cleanup is now scoped.

  • πŸ”΄ build.ts cleanup is scoped to OUT_DIR (this build's artifacts) plus only the codegen files this build owns (.bosia/routes.ts, .bosia/routes.client.ts, .bosia/env.server.ts, .bosia/env.client.ts, .bosia/types). No more blanket .bosia/ rmSync. Fixes ENOENT reading .bosia/dev/server/+page-*.js mid-request when bun run build runs alongside bun run dev.

v0.5.7 β€” `params` as a top-level page/layout prop βœ… (shipped 2026-05-19)

Match SvelteKit: +page.svelte and +layout.svelte receive params as a sibling prop of data, not nested under data.params. Network protocol (data endpoint payload, SSR injection) is unchanged β€” params is stripped at the component boundary.

  • 🟠 App.svelte passes params as a separate prop on pages and layouts; SSR branch strips merged params off pageData via local helper
  • 🟠 hydrate.ts seeds appState.pageData without the merged params key (still seeds appState.routeParams from same payload)
  • 🟠 routeTypes.ts codegen: PageData / LayoutData no longer intersect { params: Params }; PageProps / LayoutProps declare params: Params as a sibling of data
  • 🟑 Update demo + template blog/[slug]/+page.svelte and docs (README.md, docs/content/docs/guides/routing.md) to consume params as a top-level prop
  • 🟑 Standardize default and todo starter templates on the (public)/ route group convention used by demo, so scaffolded projects are ready to add authenticated areas (e.g. (app)/, (admin)/) without restructuring later

Same-day addition (2026-05-19) β€” Inspector runtime error capture

Inspector now captures live client + server runtime errors and surfaces them in a passive badge inside the running app. Manual "Send to AI" per row reuses the existing alt-click β†’ aiEndpoint handoff. Live-only (no server buffer, no SSE replay), dev-only (production unaffected β€” plugin self-disables).

  • 🟠 Server capture: Elysia .onError() hook + uncaughtException / unhandledRejection process listeners installed lazily inside backend.before(). uncaughtException rethrows so dev.ts crash-recovery still triggers. 500ms dedup window on source:message:firstFrame prevents render-loop floods (packages/bosia/src/core/plugins/inspector/index.ts)
  • 🟠 SSE broadcaster at /__bosia/errors β€” module-scoped controller Set, event: bosia-error data frames, 25s :ping keepalive, abort-driven cleanup. No replay buffer (live-only contract)
  • 🟠 Reorder Elysia onError chain in server.ts: base 500 responder now registered after plugin.backend.before loop so plugin handlers fire first. Without this fix the inspector handler would never run because the base handler returned a truthy Response and short-circuited the chain
  • 🟠 Client capture in overlay.ts: window.error + unhandledrejection listeners + EventSource subscription to /__bosia/errors. Unified list, stable ids, UI dedup
  • 🟠 Floating badge UI bottom-right (● N errors) β†’ click β†’ expandable panel with per-row stack details, Dismiss, and AI-only "Send to AI" button. Badge hidden when list empty
  • 🟠 Sourcemap resolution dev-only β€” build.ts now emits sourcemap: "linked" in dev ("none" in production). New inspector/sourcemap.ts lazy-resolves compiled stack frames β†’ source (file, line, col) via @jridgewell/trace-mapping at POST time only for the error the user clicks "Send to AI" on. Per-process Map<path, TraceMap> cache; cache resets on app respawn so edits are never stale. Graceful degradation when .map is missing
  • 🟑 Last-interaction context: track the most recent data-bosia-loc the user clicked/keyed on and append Last user interaction: <file>:<line>:<col> to the comment payload. Helps the AI when the throw site is deep in framework code but the originating button/input is the relevant location
  • 🟑 errorsEnabled?: boolean (default true) config flag on InspectorOptions β€” opt out of the whole feature without removing the plugin
  • 🟑 AI-only action button β€” overlay still surfaces the badge for visibility without aiEndpoint, but the "Send to AI" button only renders when configured. Standalone bosia apps in editor-mode see display-only errors

v0.5.8 β€” `bind:*` shadow crash fix βœ… (shipped 2026-05-19)

Dev pages using <input bind:value={state}> (or any bind:* on writable state) crashed the browser with RangeError: Maximum call stack size exceeded on first render. Root cause was a name collision between Svelte 5's dev compile output and Bun's bundler β€” Svelte wraps the binding in a named function get() for $inspect stack traces; Bun rewrites $.get to a named import get; the function name then shadows the import and recurses into itself. Production was unaffected (anonymous arrow functions).

  • πŸ”΄ Post-process Svelte compile output in packages/bosia/src/core/plugins/inspector/bun-plugin.ts and packages/bosia/src/core/svelteCompiler.ts to rename the inner get / set to $$g / $$s (length-preserving so cached source-map columns stay accurate, names absent from svelte/internal/client exports). Dev-only β€” prod compile uses anonymous arrows so the shim is skipped.
  • πŸ”΄ Inject Inspector-extracted component CSS via a runtime <style> element instead of a loader: "css" virtual module. Bun's splitting: true names CSS chunks after the importing JS chunk's [name] (not the virtual module's uid), so when β‰₯2 routes share a styled .svelte component the bundler emits identical +page-<hash>.css chunks and fails with Multiple files share the same output path. Runtime injection sidesteps CSS chunking entirely. Dev-only β€” Inspector is disabled in prod.

v0.5.9 β€” `src/app.html` template βœ… (shipped 2026-05-20)

SvelteKit-style document shell customization. Users can create src/app.html with %bosia.head% and %bosia.body% placeholders to control HTML chrome (lang attribute, data attributes, favicon, analytics script placement). Immediate trigger: runtime lang mutation from metadata (honors cookie/header). Broader value: full chrome control without hardcoding.

  • 🟠 packages/bosia/src/core/appHtml.ts β€” parse, validate, cache template with invalidation for HMR
  • 🟠 Placeholders: %bosia.head%, %bosia.body% (required); %bosia.lang%, %bosia.nonce%, %bosia.assets%, %bosia.env.PUBLIC_*% (optional)
  • 🟠 Update html.ts builders (buildHtml, buildHtmlShellOpen, buildMetadataChunk, buildHtmlTail) to accept optional segments and slot user chrome
  • 🟠 Update renderer.ts to load template once per process and thread through 6 call sites
  • 🟠 Validation at build time in build.ts β€” fail fast if required placeholders missing
  • 🟑 Scaffold src/app.html in templates (default, todo) and demo with %bosia.lang% and data-theme attributes
  • 🟑 Favicon detection: if user's headOpen contains rel="icon", skip framework default favicon injection
  • 🟑 Unit tests: template loading, validation, parsing, caching, interpolation, segment structure
  • 🟑 New skill bosia-app-css documenting canonical src/app.css order and the Tailwind v4 / LightningCSS @import url(...) ordering rule (font imports must come before @import "tailwindcss", else silently dropped from public/bosia-tw.css). Catalog index docs/content/skills/SKILL.md updated (33 β†’ 34 skills); slotted under design conventions next to bosia-theme-tokens. Trigger: real-world incident in toko-mainan-anak where Fredoka font-family declarations rendered but the Google Fonts @import was stripped by LightningCSS because it sat after @source "../src".
  • 🟑 New CLI command bosia add font "<Family>" "<url>" (packages/bosia/src/cli/font.ts β†’ reuses existing mergeFontImports() from cli/fonts.ts). Prepends @import url(...) to src/app.css with /* bosia-font: <Family> */ marker so it survives Tailwind v4 / LightningCSS ordering. Idempotent. Wired into cli/index.ts (add font subcommand) with usage and example. Companion AI tool bosia_add_font added in Bosapi (bosapi/src/features/ai/tools/bosia.ts) so the agent stops hand-editing app.css and uses the safe path.

v0.5.10 β€” SvelteKit navigation parity βœ… (shipped 2026-05-20)

Closes the gap between Bosia's client navigation API and SvelteKit's $app/navigation. Userland apps were reaching for window.location.href for programmatic nav because goto() wasn't exported β€” and that escape hatch had its own caveats (full reload, lost SPA state). Now exposes goto, beforeNavigate, afterNavigate from bosia/client with the same shape SvelteKit ships.

  • 🟠 goto(url, opts?) exported from bosia/client. Returns a Promise that resolves after the nav effect settles (loaders ran, components mounted). Honors replaceState, invalidateAll, noScroll; accepts keepFocus and state for forward compatibility but does not yet honor them. Routes through router.navigate() β€” no parallel code path
  • 🟠 beforeNavigate(fn) / afterNavigate(fn) lifecycle hooks. nav.cancel() blocks SPA navigations; popstate (browser back/forward) cancellation is a no-op since history has already advanced. Auto-unregister on component destroy via onDestroy
  • 🟠 Router exposes navigation type ("link" | "goto" | "popstate" | "form" | "enter") and the Navigation object threading from router.navigate() into both lifecycle phases. Shared listener registry lives in core/client/navListeners.ts to break the ESM cycle between navigation.ts and router.svelte.ts
  • 🟠 router.navigate(path, { replace, source }) supports history.replaceState (used by goto({ replaceState: true })) and threads the source through to the Navigation object
  • 🟑 beforeunload fires beforeNavigate with willUnload: true so listeners can observe (cancellation requires native beforeunload event β€” out of scope)
  • 🟑 Hydration safety net β€” wrapped main() in core/client/hydrate.ts in a .catch() so any future hydrator failure logs to console instead of silently leaving "Loading…" on screen
  • 🟠 404/error pages no longer ship a stuck #__bs__ spinner that blocks clicking the "Go home" link. buildHtml() segments branch now gates spinner injection on empty body β€” non-streaming SSR responses (errors, form re-renders) skip it; streaming SSR and ssr=false paths still get it for the TTFB β†’ first-paint gap
  • 🟑 Demo route apps/demo/src/routes/(public)/nav-test/+page.svelte exercises all four patterns plus the cancel/event-log flow
  • 🟑 New docs page docs/content/docs/guides/navigation.md covers the four patterns and the lifecycle hooks; added to the Guides sidebar in docs/src/lib/docs/nav.ts
  • 🟑 New bosia-navigation skill (under docs/content/skills/) so AI agents pick the right navigation pattern and use the lifecycle hooks correctly. Catalog index (docs/content/skills/SKILL.md) bumped 34 β†’ 35; cross-references added in bosia-routing and bosia-auth-flow

Same-day addition (2026-05-20) β€” Surface dev-server errors to the inspector overlay

Inspector previously captured runtime errors only (Elysia handlers, client uncaughts, server process.on listeners). Dev-infrastructure errors β€” build failures after a file save, app-server crashes, .env reload failures, port conflicts β€” only reached the terminal, so the user (or an AI agent driving the editor) saw a stuck "App server is starting…" page or stale UI with no signal. These now flow through the same red badge UI as runtime errors, broadcast over the dev proxy's existing /__bosia/sse channel. When the proxy can't reach the app at all, browser HTML navigations get a fallback page that mounts the same overlay and replays buffered errors, then auto-reloads once the next build succeeds.

  • 🟠 packages/bosia/src/core/dev.ts captures build/app-crash/dev-uncaught errors into a bounded ring (50 entries, 30s TTL) with a 500ms dedup window β€” mirroring inspector/index.ts's replay buffer shape. Build and app-server stderr piped + tee'd so terminal output is unchanged, error summary lands in the buffer. process.on("uncaughtException" | "unhandledRejection") on the dev parent process surfaces watcher-callback and Bun.serve failures too
  • 🟠 New event: bosia-error over /__bosia/sse (same wire shape as inspector's ServerError). SSE handler flushes recent buffered errors to newly-connecting clients so errors that fired before the EventSource opened (initial build failure, crash loop) are still visible. Overlay's IIFE adds a second EventSource("/__bosia/sse") listener so the same pushError() path handles dev errors without UI changes
  • 🟠 New packages/bosia/src/core/dev-error-page.ts renders the fallback HTML page returned by the dev proxy when fetch(app) throws on an HTML navigation. Embeds the inspector overlay script, pre-seeds buffered errors via a global window.__BOSIA_PUSH_ERROR__, and subscribes to /__bosia/sse for the reload event so the page swaps itself out once the next build succeeds. Non-HTML (XHR/fetch/assets) requests keep the original plaintext 503 to avoid corrupting API responses
  • 🟑 .env reload failures inside the dev watcher no longer crash the dev parent β€” caught, logged, and routed through the same buffer so the user sees the validation error in the badge instead of a dead process

Deferred (logged for follow-up)

  • 🟑 pushState(url, state) / replaceState(url, state) for shallow routing
  • 🟑 onNavigate(fn) (runs between beforeNavigate and the actual nav)
  • 🟑 preloadCode(...routes) (preloads route module without data)
  • 🟑 applyAction(result) / deserialize(result) from $app/forms
  • 🟑 disableScrollHandling() for fine-grained scroll control
  • 🟠 Diagnose & fix window.location.href stall on static builds β€” needs a confirmed repro; safety-net try/catch is in place so the next occurrence surfaces a console error instead of staying on "Loading…"

v0.6.0 β€” Server response cache (skip-render) βœ… (shipped 2026-05-24)

Before v0.6, every HTML response re-ran metadata(), every layout load(), the page load(), render(), and Bun.gzipSync() β€” even when the result was byte-identical to the previous request. The new in-memory response cache short-circuits all of that and serves pre-compressed bytes (brotli or gzip) directly. Per-user safety comes from an identity hash of cookies/headers named in CACHE_KEYS, so logged-in users never see each other's HTML.

  • 🟠 New packages/bosia/src/core/cache.ts β€” tiny LRU + tagIndex + pathIndex, computeCacheKey(url, req, cookies), serveCached(entry, req) with Accept-Encoding: br | gzip | identity negotiation, buildCompressedVariants() (brotli + gzip), tag/path-based eviction.
  • 🟠 Renderer integration (renderer.ts) β€” cache read before metadata/load/render, cache write after chunks are built, streaming preserved on miss. CSP-enabled deploys skip the cache (per-request nonce is incompatible with cached bytes).
  • 🟠 API endpoint integration (server.ts) β€” +server.ts GET handlers cached with the same key rules. v0.6 invalidates API entries by URL/prefix only (no depends() for API yet).
  • 🟠 Public API β€” invalidate(key) / invalidateAll(prefix) from bosia mirror the existing browser-side invalidate() semantics. Form actions call them after a write.
  • 🟑 Per-route opt-out β€” export const cache = false; in +page.ts, +page.server.ts, or +server.ts. Generated $types.d.ts exports a CacheOption type alias for IDE support.
  • 🟑 Env vars β€” CACHE_KEYS (default session,sid,auth,token,jwt,Authorization) controls identity-hash inputs; CACHE_MAX_ENTRIES (default 500, 0 disables). Documented in guides/environment-variables (EN + ID) and the response-cache guide (EN + ID).
  • 🟑 Author guidance β€” new bosia-response-cache skill (docs/content/skills/bosia-response-cache/SKILL.md) walks AI agents through when to call invalidate() from server code, how to tag loaders with depends(), and when to opt a route out. Data-invalidation guides (EN + ID) gained a "Server-side invalidate() for the response cache" section.
  • 🟠 Dev proxy now forces inner app to Accept-Encoding: identity (packages/bosia/src/core/dev.ts). Previously the proxy forwarded the browser's Accept-Encoding: gzip,br,… to the inner app, the inner returned compressed bytes with Content-Encoding: gzip, and Bun's fetch() auto-decoded the body but left the Content-Encoding header on the Response. Header said gzip, body was plaintext β€” Safari threw NSURLErrorDomain:-1015 cannot decode raw data on every HTML navigation (curl/Chrome forgave the mismatch). Identity on the localhost dev wire is fine; the response-cache layer still serves precompressed bytes to real browsers in prod. Retry-on-startup behaviour preserved.
  • 🟠 core/cache.ts guards process.env reads β€” module is re-exported through the public bosia barrel (invalidate / invalidateAll), so it evaluates in the browser bundle whenever a user app imports anything from bosia client-side (e.g. demo/src/lib/utils.ts re-exports cn). Top-level process.env.CACHE_KEYS / process.env.CACHE_MAX_ENTRIES threw ReferenceError: Can't find variable: process on hydration in Safari. Now reads through a typeof process !== "undefined" ? process.env : {} shim and the startup console.log is gated on the same check.
  • 🟠 Server-only response-cache exports moved to bosia/server subpath β€” even with the typeof process shim, core/cache.ts was still evaluating client-side whenever a user imported anything from the shared bosia barrel, because lib/index.ts re-exported invalidate / invalidateAll from it. Added ./server to package exports, created packages/bosia/src/lib/server.ts re-exporting them, removed them from the shared barrel. Updated guides (docs/content/docs/{,id/}guides/{response-cache,data-invalidation}.md) and the bosia-response-cache skill to import { invalidate } from "bosia/server". Mirrors the existing bosia/client split. No live callers in demo/templates to update. The shim in cache.ts is kept as defense-in-depth.
  • 🟑 Inspector dev-error reporter type alignment β€” core/devErrorReport.ts declared source?: "server" | "uncaught" | "rejection" but pushServerError in core/plugins/inspector/index.ts accepted "elysia" | "uncaught" | "rejection". bun run check (tsc) failed with TS2322 in inspector/index.ts:150. Renamed "elysia" β†’ "server" in the inspector union and its two callsites (Elysia onError handler + global reporter default) so the framework-agnostic label matches the reporter's vocabulary; overlay just renders the string so no UI change.

Deferred to v0.7+

  • 🟑 Key-based invalidation for +server.ts endpoints β€” give API handlers a depends() argument or support export const tags = [...] so invalidate("app:user") evicts API responses too.
  • 🟑 TTL-based expiry β€” author wants pure-invalidate today, but TTL is useful for "refresh every N seconds" pages.
  • 🟑 Layout-level cache = false cascade β€” a layout opting out should make its child routes uncached too.
  • 🟑 Multi-replica cache (pub/sub invalidation) β€” single-replica only in v0.6.
  • 🟑 Soft-purge / stale-while-revalidate.
  • 🟑 Custom key function β€” export const cache = { key: (req) => string }.

v0.5.13 β€” Inspector component call-site chain βœ… (shipped 2026-05-23)

Alt-clicking a <button> rendered by a shared Button.svelte previously showed only Button.svelte:5:1 β€” the definition site β€” which was misleading for the user and unusable for the "Send to AI" hand-off because the agent had no idea which page rendered the element. The overlay now shows the full call-site chain (e.g. +page.svelte:42 β†’ Button.svelte:5) and ships the same chain inside the AI comment payload.

  • 🟠 Compile-time injection of <!--bosia:o=path:line:col--> / <!--bosia:c--> markers around Component / SvelteComponent / SvelteSelf AST nodes in injectLocs (packages/bosia/src/core/plugins/inspector/bun-plugin.ts). Comments survive Svelte compile because preserveComments: dev is already set, and run for both browser and bun targets so SSR HTML matches client hydration.
  • 🟠 Runtime collectStack(el) walks DOM ancestors + previous siblings with a depth counter that matches each bosia:c against its bosia:o, so sibling components on the same parent don't bleed into each other's stack. Returns outermost-first; wired into the hover tooltip, the AI form header, the AI comment payload (prepends Component tree (outer β†’ leaf): …\n\n), and the runtime-error lastInteraction field (packages/bosia/src/core/plugins/inspector/overlay.ts).
  • 🟑 Tooltip widened with max-width:90vw + ellipsis so long chains don't overflow the viewport.
  • βšͺ docs/content/docs/guides/inspector.md updated to describe the chain feature and extend the prod-output grep to check for both markers.
  • 🟑 bosia-inspector-edit skill (docs/content/skills/bosia-inspector-edit/SKILL.md) updated for the new payload β€” parses the Component tree (outer β†’ leaf): … prefix, defaults the target to the outermost call-site, requires a one-sentence justification when the agent picks the leaf instead. Catalog entry in docs/content/skills/SKILL.md updated.

Same-day addition (2026-05-23) β€” Env + CORS skills for AI agents

Bosapi-spawned preview apps (served via a-<uuid>.lvh.me:9000) were surfacing 403 Cross-origin request blocked: Origin "…lvh.me…" is not allowed and the AI agent kept reaching for CORS env vars to "fix" it β€” but the message comes from the CSRF check (packages/bosia/src/core/csrf.ts:51), not CORS, so changing CORS env never helped. The actual fix is allow-listing the preview host(s) in CSRF_ALLOWED_ORIGINS in the child .env (verified against working app toko-mainan-anak which carries CSRF_ALLOWED_ORIGINS=http://lvh.me:9000,http://a-<uuid>.lvh.me:9000). Skills now teach the agent both the env-prefix system and the CSRF-vs-CORS triage explicitly.

  • 🟑 New bosia-env skill (docs/content/skills/bosia-env/SKILL.md) β€” four-tier prefix (PUBLIC_STATIC_ / PUBLIC_ / STATIC_ / none), $env virtual module for user vars, process.env for framework-reserved vars (full table covering PORT, BODY_SIZE_LIMIT, IDLE_TIMEOUT, MAX_INFLIGHT, CORS_*, CSRF_ALLOWED_ORIGINS, TRUST_PROXY, DISABLE_X_FRAME_OPTIONS, CSP_DIRECTIVES, BOSIA_OUT_DIR). .env.example as the contract; .env* load order rules.
  • 🟑 New bosia-cors skill (docs/content/skills/bosia-cors/SKILL.md) β€” CORS env recipe (CORS_ALLOWED_ORIGINS + methods/headers/exposed/credentials/max-age), Vary: Origin invariant, and a triage table that distinguishes a real CORS failure (browser console "blocked by CORS policy", no response body in JS) from Bosia's CSRF rejection (403 response body with Cross-origin request blocked: Origin "…"). Preview-proxy workflow lists the lvh.me preview origin(s) in CSRF_ALLOWED_ORIGINS (primary) with TRUST_PROXY=true documented as the alternative for proxies that need forwarded headers reflected.
  • 🟑 Catalog docs/content/skills/SKILL.md updated 35 β†’ 37 skills; both entries added under framework conventions and into the discovery-order step 2; cross-references wired in both directions and to bosia-security-review / bosia-elysia-routes.

v0.5.11 β€” `$types` resolution inside `.svelte` files

tsc --noEmit resolves ./$types from .svelte files via the rootDirs: [".", ".bosia/types"] trick, so bun run check and bun run build both type-check params / PageProps correctly. But svelte-language-server (used by Zed, VS Code w/ Svelte extension, etc.) runs .svelte script blocks through a preprocessor and doesn't honor rootDirs from inside that virtual TS document β€” the editor reports Cannot find module './$types' and params collapses to implicit any. SvelteKit avoids this by shipping a dedicated language-tools plugin (@sveltejs/language-tools) that synthesizes $types virtually at LSP time. Bosia needs the same.

Acceptance: in a freshly scaffolded Bosia app, hovering PageProps in +page.svelte shows the generated type, autocomplete on params. lists only the route's dynamic segments, and no "module not found" diagnostic appears for ./$types. Same behavior in Zed and VS Code.

  • 🟠 Investigate options: (a) TypeScript Language Service plugin that hooks moduleResolution for $types specifiers from .svelte files; (b) fork/extend svelte-language-server config; (c) shim by re-exporting from a plain .ts barrel the LSP already sees. Pick the lowest-friction path.
  • 🟠 Ship the plugin/shim from packages/bosia and wire it into the scaffolding templates' tsconfig.json (compilerOptions.plugins or svelte.config.js) so new apps work out of the box.
  • 🟑 Verify in Zed and VS Code on apps/demo/src/routes/(public)/blog/[slug]/+page.svelte: hover shows Params = { slug: string }, autocomplete on params. lists slug, typing params.foo red-squiggles.
  • 🟑 Document the editor setup step in docs/content/docs/guides/routing.md (or a new "Editor setup" guide) β€” what extension to install, what tsconfig.json looks like.
  • βšͺ Note the limitation + workaround in the meantime under docs/content/docs/reference/sveltekit-differences.md. (Updated 2026-05-24 to reflect shipped features: navigation API, plugin system, response caching)

v0.5.4 β€” Brief intake skills βœ… (shipped 2026-05-17)

Six new design-track skills that gather product brief (identity / voice / visual / platform) into BRIEF.md at app root before any UI emit. Closes the "agent invents palette + tone every turn" drift bug.

  • 🟠 bosia-brief-intake β€” orchestrator. Walks the four group skills in order, writes BRIEF.md, chains bosia-brief-review. Auto-trigger surface: empty BRIEF.md.
  • 🟑 bosia-brief-identity β€” name, tagline, audience, language, formality, self-reference. Locks sapaan + UI string language for the rest of the session.
  • 🟑 bosia-brief-voice β€” tone adjectives, emoji/exclamation policy, microcopy spine table (5 rows: empty / error / confirm-destructive / success / primary action), domain glossary, copy no-go.
  • 🟑 bosia-brief-visual β€” palette intent β†’ theme pick decision matrix, shape, density, type, icons, custom marks. Runs bosia_add_theme + --primary/--accent override.
  • 🟑 bosia-brief-platform β€” form factors, primary surface, ID format regex, number/date Intl formatters, imagery aspect ratios, first-screen scaffold queue, MVP feature list (cap 7).
  • 🟑 bosia-brief-review β€” quality gate. P0/P1 checks: sections complete, theme installed matches brief, formatter modules scaffolded, sapaan consistent, no emoji leak in product strings, first-screen names resolve to real catalog entries.
  • 🟑 Catalog SKILL.md index updated β€” 25 β†’ 31, new section "Brief intake β€” design ✦", discovery order gains step 0 "check BRIEF.md".

Hotfix (same-day, 2026-05-17)

  • πŸ”΄ Fix bosia dev build crashing with Multiple files share the same output path on apps with multiple style-less +page.svelte routes. inspector's per-svelte virtual CSS chunk (packages/bosia/src/core/plugins/inspector/bun-plugin.ts) now skips emission when result.css.code is empty/whitespace, and replaces dots in the basename so Bun's [name]-[hash].[ext] chunk naming yields a unique [name] per route instead of collapsing every +page.svelte to [name]="+page". Production builds were unaffected (inspector self-disables under NODE_ENV=production).

Same-day addition (2026-05-17)

  • 🟑 bosia-frontend-design β€” new design-convention skill. Forces aesthetic stance (direction / typography / dominant colour + sharp accent / one memorable detail) before any UI emit. Avoids the "AI default" look (soft purple gradient, Inter, evenly-distributed feature cards). Adapted from nexu-io/open-design frontend-design; bodies rewritten for Svelte 5 + Bosia semantic tokens + registry-first composition. Ships with references/aesthetic-directions.md (11 starter directions: brutally-minimal, editorial, brutalist, retro-futuristic, maximalist, soft-pastel, luxury, industrial, organic, playful, art-deco) and a BRIEF.md Β§ Aesthetic template. Catalog SKILL.md index 31 β†’ 32; design-conventions section gains the third row.
  • 🟑 bosia-frontend-design wired into bosia-brief-intake as step 4 (after bosia-brief-visual), so every new app's BRIEF.md ends with a populated ## Aesthetic section before any feature work. Quick-start opener bumped 5 β†’ 6 questions. bosia-brief-visual hands off to the stance step. bosia-brief-review gains P0 checks B18 (stance committed, no AI-default direction/fonts), B19 (fonts wired in app.css @theme, not per-component), B20 (accent override applied to :root so the stance is load-bearing, not decorative). Halting failure extends to B1–B10 + B18–B20.
  • 🟑 Stance consumption wired downstream β€” no collision with stance-picking. bosia-design-review gains a P1 check confirming each emit honors Β§ Aesthetic (direction, memorable detail, fonts from app.css @theme) without re-picking. Six page scaffolds (bosia-landing, bosia-saas-landing, bosia-blog, bosia-pricing, bosia-mobile-screen, bosia-dashboard) gain a workflow step 1 "Read BRIEF.md Β§ Aesthetic and apply" plus a matching P0 item. Each scaffold is a pure consumer of the stance β€” no skill duplicates stance-picking responsibility.
  • 🟑 bosia-brief-intake ships first two reference files: references/quick-start-script.md (6-question opener with palette-intent β†’ direction inference defaults) and references/example-brief.md (Dombaku-style fully-filled BRIEF.md including Β§ Aesthetic). Frontmatter targets.files on bosia-frontend-design (BRIEF.md + src/app.css) and bosia-brief-intake (+ src/app.css) updated. Catalog SKILL.md Brief-intake table gains a footnote pointing readers to the stance step under design conventions.

v0.5.3 β€” API prerender βœ… (shipped 2026-05-16)

Same prerender ergonomics for +server.ts routes as pages already had. Drop the docs-only static-API post-build pipeline.

  • 🟠 Framework: +server.ts honors export const prerender = true β€” detectPrerenderRoutes scans manifest.apis, dynamic routes call entries(), prerenderApiOutPath() writes a single .json per route (no trailing-slash variants). Fetched body is written verbatim β€” handlers decide the payload shape (packages/bosia/src/core/prerender.ts)
  • 🟑 Dev runtime alias: API routes with prerender = true are also served at <path>.json, matching the URL static hosts will serve in prod. Non-prerender routes get no alias (packages/bosia/src/core/server.ts)
  • 🟑 Unit tests for prerenderApiOutPath and substituteParams rest-segment cases (packages/bosia/test/prerender-api.test.ts)
  • 🟑 Docs API routes migrated: /api/skills, /api/skills/[name], /api/components, /api/components/[...path], /api/blocks, /api/blocks/[...path] all opt into framework prerender. Dynamic routes export entries() from listSkills() / listRegistry()
  • 🟑 Removed generateSkillsApi() + generateRegistryApi() from docs/scripts/post-build.ts β€” post-build returns to sitemap-only

Hotfix (same-day, 2026-05-16)

  • πŸ”΄ Fix dev .json alias resolution: catch-all sibling routes (/api/components/[...path], /api/blocks/[...path], /api/skills/[name]) were absorbing the .json suffix into their rest-segment param, causing 4xx in dev. Logic now tries the bare path first when the URL ends in .json and prefers it only if the matched route opted into prerender = true. Extracted into packages/bosia/src/core/apiResolver.ts so it can be unit-tested independently of the bundler-virtual bosia:routes module
  • πŸ”΄ Fix /api/skills/<name> JSON shape: was emitting raw SKILL.md markdown into a .json file. Handler now returns Response.json({ name, content }) with frontmatter stripped via gray-matter, matching the v0.5.2 post-build shape
  • 🟑 New packages/bosia/test/apiResolver.test.ts β€” 10 cases covering flat-route alias, catch-all precedence, [name] precedence, non-prerender fall-through, and module() throw β†’ fallback
  • 🟑 New docs/test/api-prerender.test.ts β€” post-build sanity over dist/static/api/**/*.json: every artifact parses as JSON; list endpoints expose {skills|components|blocks}[]; skill detail returns {name, content} (not raw --- markdown); component/block detail returns {name, content, ...}. Would have caught both hotfix bugs at v0.5.3 release
  • 🟑 Renamed registry detail field mdFile β†’ content in /api/components/<path> and /api/blocks/<path> responses to match /api/skills/<name> shape (docs/src/lib/registry/list.ts)
  • πŸ”΄ Fix production-build docs crash on every page with code blocks (b is not a function (b({})) / A is not a function (createHighlighter)). Lazy await import("shiki") triggered Bun code-splitter to produce a chunk that called into its parent at top-level eval before the parent's named exports were initialized. Switched to static import { createHighlighter } from "shiki" in docs/src/lib/docs/markdown.ts β€” shiki is now bundled inline with the page-server bundle, no cross-chunk circular eval
  • 🟑 Normalize path field on /api/skills, /api/components, /api/blocks index + detail responses to the full detail-endpoint URL (e.g. /api/components/ui/button.json); skills detail gains path. Breaking for components/blocks index consumers that read bare-segment path. Internal RegistrySummary.path and entries() prerender seed remain segment-form (test in docs/test/api-prerender.test.ts asserts full-URL shape and on-disk resolution)

v0.5.2 β€” CLI ergonomics & registry API βœ… (shipped 2026-05-15)

Multi-component install and AI-discovery parity with skills.

  • 🟠 bosia add accepts multiple component names in one call; new -y/--yes flag auto-confirms overwrite prompt for CI use
  • 🟑 Static /api/components.json + /api/components/{path}.json and /api/blocks.json + /api/blocks/{path}.json emitted by docs/scripts/post-build.ts (superseded in v0.5.3 by the framework prerender)

v0.4.4 β€” Build CSS collision hotfix βœ… (shipped 2026-05-09)

Republish of 0.4.3 with a missed regression in the Svelte build path fixed.

  • πŸ”΄ Restore app.css β†’ JS no-op resolve in core/plugin.ts. Without it, every dynamic-imported route chunk that transitively reaches app.css produces an identical CSS sidecar (+page-<hash>.css) and Bun fails the build with Multiple files share the same output path. Tailwind CLI continues to emit the real stylesheet at public/bosia-tw.css (loaded via <link>); the bundler never needs the source CSS
  • 🟑 Regression test packages/bosia/test/svelte-build.test.ts β€” 12 dummy routes + shared app.css; fails without the no-op, passes with it

v0.4.3 β€” Request pipeline perf βœ… (shipped 2026-05-09)

Cut redundant work from the per-request hot path.

Done

  • 🟠 Resolve page route once per request and thread through renderSSRStream / renderPageWithFormData / form-action handler
  • 🟑 Cache getPublicDynamicEnv() at module scope
  • 🟠 Linear parent() data merging in layout loaders β€” O(dΒ²) β†’ O(d) with per-layer snapshot
  • 🟑 Drop redundant onBeforeHandle apiRoutes scan; non-GET catch-alls already cover every method
  • 🟠 Inline Svelte compile, drop bun-plugin-svelte β€” own .svelte / .svelte.[tj]s onLoad with css: "injected" (browser) / css: "external" (server). Eliminates the dynamic-import CSS-sidecar collision at the root and removes the double-compile workaround in core/plugin.ts

Open

  • 🟠 Truly progressive SSR streaming β€” renderSSRStream is currently blocking before first byte (load β†’ render β†’ enqueue prebuilt chunks). Real blocker is a parallel-aware loader runner that can flush layout/page chunks as each loader resolves (the trie matcher is unrelated β€” tracked separately under Performance (at scale)). depends() / invalidate() (shipped v0.5.0) is no longer a prerequisite
  • 🟑 Reduce safeJsonStringify cost on large loader payloads β€” done in v0.5.0 by migrating __BOSIA_PAGE_DATA__, __BOSIA_LAYOUT_DATA__, __BOSIA_FORM_DATA__ to <script type="application/json"> islands. Client reads via JSON.parse(document.getElementById(id).textContent). Escape surface drops from 5 JS-context sequences to </script / <!-- only; clean payloads are byte-identical to JSON.stringify. System globals (__BOSIA_ENV__, deps, SSR flag) kept as inline JS β€” small/fixed-shape, no benefit

Reference: backup/PERFORM_ISSUES.md (full request-pipeline review, 2026-05-08).


v0.4.5 β€” Blocks & Themes Registry

Two new registry kinds: Blocks (composed UI sections) and Themes (token sets). Closes the design-quality gap for LLM-generated apps (Bosapi) and hand-coders alike. Primitives stay unchanged.

CLI

  • 🟠 bun x bosia@latest add block <category>/<name> β€” install a block to src/lib/blocks/<path>/
  • 🟠 bun x bosia@latest add theme <name> β€” install a theme to src/lib/themes/<name>.css, patch app.css import
  • 🟑 Extend CLI dispatcher (packages/bosia/src/cli/index.ts) for add block/add theme sub-args
  • 🟑 Refactor add.ts β€” parameterize destination root; RegistryIndex gains blocks: string[], themes: string[]
  • 🟑 block.ts handler β€” recursive primitive deps via addComponent(), optional font @import merge into app.css
  • 🟑 theme.ts handler β€” copy tokens.css, swap @import in app.css (one-active-theme), font @import merge

Registry content

  • 🟠 Extend registry/index.json with blocks and themes arrays
  • 🟠 registry/themes/neutral/ β€” extracted from current apps/demo/src/app.css @theme block
  • 🟠 registry/themes/editorial/ β€” warm cream palette + Instrument Serif display
  • 🟠 registry/blocks/cards/feature-editorial/ β€” first block; matches Open Design reference (eyebrow numeral, serif title, tight leading, circular CTA)
  • 🟑 Refactor apps/demo/src/app.css to @import "./lib/themes/neutral.css" (visually unchanged)

Docs

  • 🟑 docs/content/docs/blocks/overview.md + per-block pages
  • 🟑 docs/content/docs/themes/overview.md + per-theme pages + creating-themes.md
  • 🟑 CardFeatureEditorialDemo.svelte registered in nav.ts and [...slug]/+page.svelte demos map

v0.5.0 β€” Full Plugin Lifecycle

Complete the plugin surface; uninstall + virtual modules.

  • 🟠 dev.onStart + dev.onFileChange wired in dev.ts
  • 🟠 client.onHydrate + client.onNavigate in core/client/hydrate.ts + router.svelte.ts
  • 🟠 Virtual modules from plugins β€” extend core/plugin.ts resolver pattern
  • 🟑 Plugin uninstall via bosia feat
  • 🟑 Docs: full plugin authoring guide

v0.6.0 β€” E2E Testing & Docs (Phase 5 + 6)

Full browser testing with Playwright + comprehensive test docs.

  • 🟠 startTestServer() β€” spin up a real Bosia server on a random port for E2E
  • 🟠 bosia test --e2e β€” auto-launch Playwright with the server
  • 🟑 Playwright config template in bosia create scaffolding
  • 🟑 Test file examples in project templates
  • 🟑 bosia feat test scaffolder for generating test files
  • 🟠 Docs: testing guide for end-user apps using bun test (unit-level; integration/component/E2E pending utilities)

v0.7.0 β€” CSS Pipeline Overhaul

Replace the app.css no-op workaround with a proper CSS dedup pipeline. Single global stylesheet doesn't scale: large apps need per-route CSS chunks, component-scoped styles, and code-split delivery.

Problem

  • Tailwind CLI runs separately from Bun build β†’ bundler has no view of CSS module graph
  • Bun's splitting: true emits one CSS sidecar per chunk that imports a shared CSS file β†’ collision when N routes transitively import app.css
  • Current fix (plugin.ts intercepts app.css β†’ empty JS module) ships ALL utilities in one public/bosia-tw.css regardless of which route uses them
  • Doesn't scale: 100+ route apps load every utility on every page; can't lazy-load route-specific CSS; can't tree-shake unused per-route styles

Goals

  • 🟠 CSS module graph dedup β€” bundler tracks every CSS import, identical content emitted once, referenced by N entries (Vite-style)
  • 🟠 Per-route CSS chunks β€” each route ships only the CSS it actually uses, loaded via <link> injected at SSR
  • 🟠 Drop app.css no-op interception in core/plugin.ts once dedup lands
  • 🟑 Component <style> blocks: continue with css: "injected" (already scoped + deduped via cssHash)
  • 🟑 Tailwind into bundler hot path β€” port @tailwindcss/vite shape to Bun plugin API so utilities are scanned + emitted as part of the build, not a parallel CLI step

Approach Options

  1. Wait on Bun upstream β€” file/track issue for CSS chunk dedup under splitting: true. Lowest effort, unbounded timeline.
  2. Custom Bun plugin β€” own CSS pipeline in core/cssPipeline.ts: intercept all .css imports, hash contents, emit one shared chunk per unique source, track route β†’ chunk mapping, inject <link> tags via render.head per request.
  3. Static layout import workaround β€” make root +layout.svelte a static import (not dynamic) in routes.client.ts. Collapses app.css into entry chunk β†’ no per-route duplication. Cheapest fix, but loses dynamic layout chains.

Acceptance

  • Builds with 100+ routes succeed without the app.css no-op
  • Each route ships ≀ what it imports (verified by inspecting dist/client/*.css sizes)
  • Component <style> still scoped via cssHash
  • No regression in test/svelte-build.test.ts (CSS collision regression test)

Not Planned

Intentional omissions β€” out of scope for the framework:

  • +page.ts / +layout.ts universal load (decided against)
  • Image optimization (infrastructure concern)
  • i18n (user's responsibility)
  • Rate limiting (reverse proxy concern)
  • Adapter system (intentionally tied to Bun + Elysia)
  • Service worker tooling (out of scope)