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.5


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)
  • 🟑 Cookie sameSite accepts both casings (lax/Lax) β€” normalized to canonical header
  • 🟠 Protocol-aware Secure cookies β€” auto-downgrade over HTTP with warn; TRUST_PROXY=true honors x-forwarded-proto
  • 🟠 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)
  • 🟠 Bun-native drizzle migrate runner β€” src/features/drizzle/migrate.ts replaces drizzle-kit migrate for sqlite/postgres/mysql apps (drizzle-kit's sqlite migrate requires better-sqlite3/@libsql/client; bun-native runner uses drizzle-orm/bun-sqlite/migrator + drizzle-orm/bun-sql/migrator).
  • 🟠 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 ([email protected] or [email protected])
  • 🟠 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 β†’ shipped as file-upload in v0.6.4 (2026-05-26): bun x bosia@latest feat [-y] file-upload [-d sqlite|postgres|mysql] scaffolds Drizzle-backed metadata + local/S3 adapter + /api/files POST that pipes through Bun.Image compression (WebP @ 0.85, fit-inside 1920Γ—1080). Install-time dialect selection via the new per-feature options mechanism β€” -d is declared in file-upload's own meta.json, not hard-coded in the CLI
  • 🟑 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.6.5 β€” Compile-time component-import audit βœ… (shipped 2026-05-27)

A scaffolded app crashed on first SSR render with undefined is not a function because +page.svelte did import * as Card from "$lib/components/ui/card/index.ts" and used <Card.Root> β€” but card/index.ts exports Card/CardContent, not Root. bosia build succeeded silently: neither svelte/compiler nor the Bun bundler cross-checks template component identifiers against their imported source. The audit closes that gap.

  • 🟠 packages/bosia/src/core/svelteAudit.ts β€” walks the modern Svelte 5 AST fragment (Component, SvelteComponent), extracts top-level bindings from <script> / <script module> (named / default / namespace imports + locals), tracks shadowing scopes from {#each ... as Name}, {#snippet name(params)}, and {@const Name = ...}. For namespace imports (import * as Card from "$lib/..."), uses Bun.Transpiler.scan() to introspect the resolved source's exports; missing members report with the full export list + Levenshtein-1 "did you mean". Bare-package specifiers (lucide-svelte) and modules containing export * are treated as opaque to avoid false positives.
  • 🟠 packages/bosia/src/core/svelteCompiler.ts β€” switched compile() to modernAst: true (legacy html AST β†’ modern fragment/instance/module), wired the audit into onLoad, and added module-scoped per-file dedupe (Map<absPath, Promise>) so the audit runs exactly once per file across the parallel browser + bun build targets.
  • 🟠 Promotes select svelte/compiler warnings to errors: component_name_lowercase, bind_invalid_value, invalid_html_attribute β€” silently-broken cases the user almost never wants to ship.
  • 🟑 resolveImport.ts + sourceLoc.ts β€” extracted from plugin.ts and plugins/inspector/bun-plugin.ts so the audit and the existing resolver share one alias / tsconfig-paths / relative-path implementation and one lineColFromOffset helper. plugin.ts:onResolve($) now delegates to resolveImportPath().
  • 🟑 BosiaConfig.strictImports (boolean | { unbound, namespaceMember, warnings }) β€” per-component opt-out. BOSIA_STRICT_IMPORTS=0 env var downgrades to a console.warn at runtime without failing the build.
  • 🟑 packages/bosia/test/svelte-audit.test.ts β€” 8 fixtures cover the repro (missing namespace export), positive cases (correct member, named import, each-block shadowing, bare-package skip), and edge cases (unbound identifier, dotted on default import, env override).
  • 🟑 ConstTag siblings β€” {@const Foo = ...} now scopes its binding across the whole surrounding fragment (not just ConstTag's own children). Previously the docs [...slug]/+page.svelte false-flagged <DemoComponent /> when bound via a sibling {@const}. Fragment pre-pass collects all ConstTag bindings before descending into children.

v0.6.4 β€” Combined files demo, CORS-safe βœ… (shipped 2026-05-26)

The crop block's docs demo was loading a remote Unsplash URL with crossorigin="anonymous". The browser blocked it as a CORS failure and the cropper rendered blank. Replaced the two separate demos (one cropper, one uploader) with a single combined demo that mirrors the reference CMS UploadTab β€” pick a file via UploadArea, then click the crop button to overlay CropImage against the file's object URL. Object URLs are same-origin, so the crossOrigin attribute is no longer needed and the cropper just works.

  • 🟑 docs/src/lib/components/demos/FilesUploadCropDemo.svelte β€” single combined demo. UploadArea (enableCrop, uploadUrl="/api/demo-upload") β†’ on crop button, captures the (file, done) pair, opens CropImage against URL.createObjectURL(file), wraps the returned Blob as a File and calls done(file). Replaces the two earlier per-block demos.
  • 🟑 docs/src/routes/api/demo-upload/+server.ts β€” tiny POST returning { url, ok } so the demo Upload button doesn't 500.
  • βšͺ Both docs/content/docs/blocks/files/{crop-image,upload-area}.md frontmatter demo: now points at FilesUploadCropDemo. [...slug]/+page.svelte imports the new demo only; deleted FilesCropImageDemo.svelte and FilesUploadAreaDemo.svelte.
  • 🟑 registry/blocks/files/crop-image/block.svelte β€” switched the 400px viewport from h-[400px] to style="height: 400px;". The class itself works for end-users (their Tailwind scans their own src/), but in the docs preview the cropper area sometimes collapsed before/until the docs' Tailwind picked up the registry/blocks source on the next rebuild. Inline style is the safe cross-host fallback for fixed dimensions registry blocks rely on.
  • 🟑 docs/src/app.css β€” added @source "../../registry/blocks/**/*.{svelte,ts,js}" so utility classes declared inside registry blocks are emitted into bosia-tw.css from the docs build alongside registry/components/ui.
  • 🟠 docs/src/lib/docs/content.ts β€” contentDir and demoFile no longer resolve relative to import.meta.dir. In dev, the server bundle lives at .bosia/dev/server/*.js (three levels deep), in prod at dist/server/*.js (two levels deep); the old ../../content/docs traversal silently missed the content dir in dev, making every catch-all docs page 404 via the loadDoc β†’ null β†’ error(404) path. Both paths now anchor on process.cwd() (same approach [...slug]/+page.server.ts:12 already uses), making dev and prod resolution identical.

Same-day addition (2026-05-26) β€” `file-upload` feature + CLI dialect flags

The files/upload-area block has shipped since v0.6.3 but bosia had no server-side counterpart β€” every user had to write their own POST /api/files. Closes the gap with a full backend feature, and adds install-time DB dialect selection to bun x bosia feat so the same feature can ship Postgres / MySQL / SQLite Drizzle tables without a runtime selector.

  • 🟠 registry/features/file-upload/ β€” full backend scaffold. file.service.ts validates MIME (image/jpeg|png|webp|heic|avif), decodes via Bun.Image, fit-inside resizes to 1920Γ—1080 (no upscale), re-encodes WebP @ 0.85, persists (id, key, url, mime, size, width, height, createdAt) via Drizzle, returns the row. Three dialect-specific table files (file.pg.table.ts, file.mysql.table.ts, file.sqlite.table.ts) all target the same install path src/features/file-upload/schemas/file.table.ts β€” install-time dialect filter picks one. storage/ adapter pattern: LocalStorage (Bun.write + UPLOAD_DIR/PUBLIC_BASE_URL env) and S3Storage (Bun.s3.file().write/delete). src/routes/api/files/+server.ts (GET list, POST upload) + [id]/+server.ts (DELETE cascades to storage) + src/routes/uploads/[...path]/+server.ts (path-traversal-guarded local static stream, Cache-Control: immutable).
  • 🟠 packages/bosia/src/cli/feat.ts β€” per-feature options system. Top-level only handles -y / --yes and --local; everything after the feature name flows to resolveFeatureOptions() which parses against the feature's own meta.json options: FeatureOption[] schema (each entry: { name, flag?, long?, prompt?, choices?, default?, required? }). Unknown flags abort with a list of valid ones. Missing values prompt via @clack/prompts.select (when choices) or .text; with -y, fall back to default. FileEntry.when?: Record<string, string> filters which files install. Resolved option values thread through InstallOptions.featureOptions namespaced as featureName.optionName so dependency features can read them; the root feature also receives raw featureArgs for its own parse.
  • βšͺ packages/bosia/src/cli/index.ts β€” feat subcommand argv handler simplified: first non-flag token is the name, everything else (including pre-name -y) flows to runFeat. Help text updated to reflect that feature-specific flags follow the feature name.
  • βšͺ packages/bosia/src/cli/registry.ts β€” InstallOptions gained featureOptions (resolved values) and featureArgs (raw tokens for the root feature). No CLI-level dialect type β€” dialect is now file-upload-specific.
  • βšͺ registry/index.json β€” features array gains file-upload.
  • 🟑 docs/content/docs/guides/file-upload.md β€” install / env / wiring / S3 swap docs; cross-link added from blocks/files/upload-area.md. Nav entry under Guides.
  • βšͺ docs/content/skills/bosia-file-upload/SKILL.md β€” new skill teaching the AI when to install file-upload (avatar/profile picture/media library triggers), R1–R5 rules, workflow, and anti-patterns. Cross-references bosia-env, bosia-drizzle-feature, bosia-elysia-routes, bosia-block-compose.

v0.6.3 β€” Skills API exposes references βœ… (shipped 2026-05-25)

AI agents fetching /api/skills/<name>.json could see SKILL.md body but not the companion reference files (references/checklist.md, references/design-principles.md, …) that carry the actionable detail. They had to guess paths or scrape the docs site. The skill detail response now lists every reference with its fetch URL, and each reference has a dedicated JSON endpoint.

  • 🟑 listSkillReferences(name) in docs/src/lib/skills/list.ts β€” reads <SKILLS_ROOT>/<name>/references/, filters to .md files, validates slugs against ^[a-z0-9-]+$, returns { file, path }[] sorted by file. Silent [] on missing dir.
  • 🟑 GET /api/skills/[name] response gained references: SkillReference[] so agents discover the available reference files in one round-trip.
  • 🟑 New route docs/src/routes/api/skills/[name]/references/[file]/+server.ts β€” prerendered, entries() enumerates (name, file) pairs across all skills, realpath traversal guard mirrors the existing [name] route. Returns { name, file, path, content } with cache-control: public, max-age=60. Raw markdown body β€” no frontmatter parsing on references.

Same-day addition (2026-05-25) β€” Files blocks (crop + upload)

Registry had no file-handling blocks. Ported two from a working CMS: an interactive image cropper and a drag-and-drop upload area. Both installable standalone β€” upload-area accepts an optional onCropRequest callback so callers wire crop-image in only when they want cropping. New files/ category, two icons added (crop, zoom-in).

  • 🟑 registry/blocks/files/crop-image/ β€” Svelte 5 cropper wrapping svelte-easy-crop (^5.0.0); aspect/shape presets, zoom slider, returns Blob via onCropComplete. Inlined canvas crop helper (no separate util file). Uses ui/button, ui/label, ui/slider, ui/icon, ui/sonner. Cropped output is resized to fit maxWidth Γ— maxHeight (default 1920Γ—1080, never upscales) and re-encoded at quality (default 0.85). New format prop with "auto" default β€” round crops and PNG sources go to WebP (with PNG fallback when WebP isn't encodable) so a JPEG β†’ round crop no longer balloons from a forced PNG re-encode.
  • 🟑 registry/blocks/files/upload-area/ β€” drag-drop + click-to-pick with preview, size validation, XMLHttpRequest progress, Progress bar. Props: uploadUrl (required), accept, maxSizeMB, fieldName, extraFields, headers, enableCrop + onCropRequest, onUploaded, onError. Expects JSON { url, ... } response.
  • βšͺ registry/components/ui/icon/icons.ts β€” added crop and zoom-in paths (lucide-static).
  • βšͺ registry/index.json β€” blocks array gains files/crop-image and files/upload-area.
  • βšͺ Docs pages docs/content/docs/blocks/files/crop-image.md and upload-area.md; Files group added to docs/src/lib/docs/nav.ts; FilesCropImageDemo and FilesUploadAreaDemo registered in [...slug]/+page.svelte.
  • 🟑 packages/bosia/src/core/build.ts β€” added conditions: ["svelte"] to both client and server Bun.build calls so Svelte component libraries (like svelte-easy-crop) resolve to their svelte export entry. Initial attempt used a generic onResolve handler in the framework plugin that walked package.json for a svelte field; even when it returned undefined for non-Svelte packages, it broke shiki's chunked CommonJS interop at runtime (b0 is not defined / bundle_full_exports is not defined on the page-server bundle). The Bun-native conditions option avoids touching the resolver graph and fixes both issues at once.

Same-day addition (2026-05-25) β€” Clean-architecture skill for generated apps

Bosapi-generated apps (e.g. data/users/.../warung-nasi/) were putting db.select(...) directly in +page.server.ts loaders, importing tables out of features/drizzle/tables/, and skipping a service/repository layer entirely. The existing bosia-drizzle-usage skill's "Quick Start" example actually taught this anti-pattern. New skill bosia-clean-architecture defines the strict controller β†’ service β†’ repository split, prescribes the six-file feature folder (table, validator, dto, repository, service, index), and the existing drizzle skills now teach the same shape and cross-link to it.

  • 🟑 New bosia-clean-architecture skill (docs/content/skills/bosia-clean-architecture/SKILL.md) β€” eight rules (R1–R8) covering no db in routes, repository ownership, service-owned validation, derived valibot validators via drizzle-valibot, one entity per feature, cross-feature via service namespace, table home, barrel exports. Three workflows (scaffold, refactor, new-table decision). P0/P1 checklist gate.
  • 🟑 Three companion references β€” feature-template.md (copy-adapt for all six files + caller examples), refactor-recipe.md (grep β†’ extract β†’ swap-import using warung-nasi's +page.server.ts as before/after), shared-folder.md (what belongs in features/shared/ and what does not).
  • 🟑 bosia-drizzle-feature updated β€” folder diagram gained *.repository.ts, *.validator.ts, *.dto.ts; R2 split into repository + service with examples; workflow updated to 9 steps; new anti-patterns and P1 items for the repo/service split.
  • 🟑 bosia-drizzle-usage updated β€” Quick Start rewritten so loaders call CatalogService.summary() not db.select(...); Workflow now writes the repository first, then service; new red flags for db in routes and db.* in services; P0 tightened to forbid db imports under src/routes/**.
  • 🟑 Catalog docs/content/skills/SKILL.md bumped 38 β†’ 39 skills; bosia-clean-architecture added under framework conventions and into the discovery-order step 2.

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)