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.tswith HTTP verb exports - π Error pages β
+error.svelte
Data Loading
- π΄ Plain
export async function load()pattern (no wrapper) - π
$typescodegen β auto-generatedPageData,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)
- π‘
/_healthendpoint - π Cookie support (
cookies.get,cookies.set,cookies.delete) - π Security headers (X-Content-Type-Options, X-Frame-Options, etc.)
- π‘
DISABLE_X_FRAME_OPTIONS=trueenv var to omitX-Frame-Optionsfor intentional cross-origin iframe embedding - π Graceful shutdown handler (SIGTERM/SIGINT)
- π
.envfile support with$envvirtual module - π‘ CORS configuration (framework-level)
- π Session-aware fetch (cookies forwarded in internal API calls)
- π‘ Request timeouts on
load()andmetadata()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; outerbun runreports exit 0 instead of 130 - π Dev watcher safety net β 5s mtime poll of
src/complementsfs.watchso 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,sameSitevalues - π
PUBLIC_env scoping β only expose vars declared in.envfiles - π Streaming error safety β validate route match before creating stream
- π‘
safeJsonStringifycrash guard β try-catch for circular reference protection - π Open redirect validation on
redirect() - π‘ Cookie RFC 6265 validation β validate names against HTTP token spec; use
encodeURIComponentonly 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-preloadattribute for hover/viewport prefetch - π Fix client-side navigation with query strings/hashes
- π‘ Use
insertAdjacentHTMLfor 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
- π
$libalias βsrc/lib/* - π‘
bosia:routesvirtual module - π‘ Validate Tailwind CSS binary exists before build
- π‘ Prerender fetch timeout
- π‘ Fix
withTimeouttimer leak - βͺ Remove duplicate static file serving
- π Static site output β merge prerendered HTML + client assets + public into
dist/static/for static hosting - π‘ Validate
.envvariable names β reject invalid identifiers that break codegen - π‘
.envparser 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--templateflag and interactive picker) - π
bosia addβ registry-based UI component installation - π
bosia featβ registry-based feature scaffolding - π‘
bosia addindex-based path resolution β resolves component names fromindex.jsoninstead of blindly prefixingui/ - π‘
bosia featnested feature dependencies βfeaturesfield in meta.json for recursive installation - π‘
bosia featoverwrite prompt β asks before replacing existing files - π‘
bosia addmulti-component install βbosia add button card inputinstalls all in one call - π‘
bosia add -y/--yesflag β auto-confirm overwrite prompts for CI / scripts
Templates & Features
- π
todotemplate (formerlydrizzle) β PostgreSQL + Drizzle ORM with full CRUD todo demo - π
drizzlefeature βbosia feat drizzlescaffolds DB connection, schema aggregator, migrations dir, seed runner - π Multi-engine
drizzlefeature β adapter,drizzle.config.ts, and seed-runner branch onDATABASE_URLscheme (postgres, mysql, sqlite file, sqlite in-memory) over Bun's built-in drivers (no per-engine npm dep) - π
bosia-brief-databaseskill + hook intobosia-brief-intakeβ captures DB engine + connection during brief intake, writes## Databaseblock to BRIEF.md - π
todofeature βbosia feat todoscaffolds todo schema, repository, service, routes, components, and seed data - π‘
todocomponent βbun x bosia@latest add todoinstalls todo-form, todo-item, todo-list components - π‘ Registry as single source of truth β
bosia create --template todoinstalls features from registry viatemplate.jsoninstead of duplicating files
Hooks & Middleware
- π
hooks.server.tswithHandleinterface - π‘
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
todocomponents β subfolder pattern matchingui/, 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 β
Metadatatype supportslangandlinkfields; dynamic<html lang>;<link>tag rendering in streaming SSR - π‘ Docs SEO β OG tags, Twitter cards, canonical URLs, hreflang alternates on all pages
- π‘
robots.txtandsitemap.xmlgeneration for docs site
v0.1.0
- π‘ Rename framework from
bosbuntobosia - βͺ Dead code cleanup (
renderSSR,buildHtmlShell, unexported internals) - π‘
splitCsvEnvhelper 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.mdfor full context, attack scenarios, and proposed diffs.
- Cookie secure defaults β default
HttpOnly; Secure; SameSite=Laxoncookies.set()with opt-out - Auto-detect
Cache-Controlon/__bosia/data/βprivate, no-cachewhen cookies accessed;public, max-age=0, must-revalidateotherwise - π΄
load()fetchcookie scoping βmakeFetchnow forwards theCookieheader only to same-origin requests or origins in theINTERNAL_HOSTSallowlist; third-party hosts get no cookie. User-suppliedinit.headers.cookieis preserved - π΄ Audit #1 β
allowExternalredirect validation β still validate againstjavascript:,data:,vbscript:schemes even whenallowExternal: true(moveDANGEROUS_SCHEMEScheck above the early return inerrors.ts:32) - π Audit #4 β Trusted proxy configuration β
TRUST_PROXYenv to control whenX-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-inCSP_DIRECTIVESenv emits matchingContent-Security-Policyheader - π Audit #2 β CORS preflight validation β validate
Access-Control-Request-Method/Access-Control-Request-Headersagainst allowed config inhandlePreflight(cors.ts:53-69) - π Audit #3 β CORS
Vary: Originon all responses when CORS is configured β prevent CDN caching bugs on non-matching origins (set atserver.tsrequest level, not only ingetCorsHeaders) - π‘ Audit #5 β Validate prerender
entries()return values β reject/,\,..in dynamic segment values before URL substitution (prerender.ts:44-50) - π‘ Escape
langattribute in HTML shell β<html lang="${lang}">injectslangraw; if ametadata()deriveslangfrom URL/user input it can break out of the attribute - βͺ Validate
CORS_MAX_AGEenv β reject non-numeric values instead of producingNaNheader
Security test coverage (from audit)
- π‘ Test:
allowExternal: truestill rejectsjavascript:/data:/vbscript:URLs - π‘ Test:
handlePreflightrejects whenAccess-Control-Request-Methodis not inallowedMethods - π‘ Test:
Vary: Originis 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_PROXYenv gatesX-Forwarded-*header trust in CSRF checks
Performance
- π Parallelize client + server builds β run both
Bun.build()calls withPromise.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.desiredSizeto prevent memory buildup on slow/disconnected clients - π Streaming SSR error recovery β render proper error page instead of bare
<p>Internal Server Error</p>whenrender()throws mid-stream - π
renderPageWithFormDataloader error handling β currently does not catchHttpError/Redirectthrown fromloadRouteData()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.exitedafterkill()), use random port instead of hardcoded 13572 - π‘ Fix
buildAndRestartrecursive tail call β replace recursion withwhileloop to prevent stack growth under rapid file changes
Client
- π‘ Bound prefetch cache size β
prefetchCachegrows 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.tscurrently SPA-navigates on Cmd/Ctrl/Shift/Alt+click and middle-click, breaking "open in new tab/window". Bail whene.button !== 0, any modifier key is held,e.defaultPrevented, or anchor hasrel="external"
Build
- π‘ Fail build on tsconfig.json corruption β don't silently continue with degraded config
- π‘
compress()threshold uses character count not byte count βbody.lengthon a UTF-8 string under-counts multi-byte content; switch toBuffer.byteLengthorTextEncoder().encode(...).lengthbefore threshold check - π‘
.envparser inline-comment stripping βKEY="value" # notecurrently keeps# noteas 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-Prototo the inner app server (dev.ts:208-220) β without them the inner CSRF check derivesexpectedOrigin = http://localhost:APP_PORTwhile the browser'sOriginishttp://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
.envvars on hot-reload
v0.2.1 β Features & DX
New capabilities and developer experience improvements.
Data Loading
- π
depends()andinvalidate()β selective data reloading - π‘ Prefetch sends the loader cache mask β hover/viewport
data-bosia-preloadwas warming the data endpoint with no mask, re-running every loader server-side; now it sends the same_invalidatedbits as a real nav - π‘
setHeaders()in load functions β set response headers from loaders
Navigation
- π
beforeNavigate/afterNavigatelifecycle hooks - π Scroll restoration and snapshot support (
export const snapshot)
Routing
- π Layout reset (
+layout@.svelteor+page@.svelte) - π Route-level
+error.svelteβ per-layout error boundaries instead of global-only - π‘ Page option:
ssrtoggle (export const ssr = false) - π‘ Page option:
trailingSlashconfiguration
Forms
- π
use:enhanceprogressive enhancement β client-side fetch submission with automatic form state management (like SvelteKit)
Types
- π Typed route params β generate
{ slug: string }from[slug]instead ofRecord<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
/_healthinstead ofBun.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. Seedocs/guides/request-deduplication.mdfor 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
RegExpat startup instead of parsing on every match - π Concurrency / backpressure ceiling β Bun currently accepts unlimited concurrent connections (
server.ts:812only setsidleTimeout/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_INFLIGHTenv var, defaultInfinity(off, no behavior change);/_healthexempt; cap-check runs before all work so the 503 is cheap. Docs +.env.examplefiles 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 viaBun.brotliCompressSyncgated onAccept-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 fromCACHE_KEYS), per-route opt-out viaexport const cache = false, server-sideinvalidate(key)/invalidateAll(prefix)frombosiamirroring 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.tsendpoints - π‘ 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 β rootApp.svelte+ error pages are rendered in separate Svelterender()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")andprocess.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 andprocess.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!isDevso the dev inspector keeps owning error display - π‘ Structured logging β replace emoji-prefixed
console.log/console.errorthroughoutserver.tswith 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:906hardcodes the 2 s force-exit window and 10 s drain. Expose viaSHUTDOWN_DRAIN_MS/SHUTDOWN_FORCE_MSfor deploys with long-running streaming responses. Source: 2026-05-23 pre-prod audit - βͺ Startup banner shows resolved hostname β
server.ts:880-882logshttp://localhost:${PORT}even though Bun binds0.0.0.0by 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 featper-file strategies βmeta.jsonfiles: FileEntry[]withstrategyfield:write(default),skip-if-exists,append-line,append-block,merge-json. Replaces all-or-nothing replace prompt for shared files likesrc/features/drizzle/schemas.ts - π‘ Document
meta.jsonschema and strategies indocs/(CLI /bosia featpage) - π‘
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
writestrategy (force one to declare append-line/append-block) - π
authfeature scaffold β usesappend-blockto register hooks insrc/hooks.server.tsand routes barrel - π‘
s3/storagefeature β bucket client + upload route using new strategies - π‘ Track installed features per project (
.bosia/installed.json) β enablebosia feat listand 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.jsonso scaffolded projects format-on-create. Pre-commit hook auto-formats staged files. No lint.
CLI
- π
bosia testcommand β wrapsbun testwith framework-aware defaults - π‘ Auto-load
.env.test(fallback.env) before running tests - π‘ Set
BOSIA_ENV=testautomatically - π‘ 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,prerenderpath/URL helpers)
Test Utilities (`bosia/testing`)
- π
createRequestEvent()β mock factory for testing+server.tshandlers and hooks - π
createLoadEvent()β mock factory for testingload()functions - π‘
createMetadataEvent()β mock factory for testingmetadata()functions - π
mockCookies()β in-memory cookie jar implementingCookiesinterface - π‘
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 standardResponseback - π 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/sveltecompatibility 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.tsloader βpackages/bosia/src/core/config.ts; resolve fromprocess.cwd(), compile viaBun.build({ target: "bun" }), cache, default to{ plugins: [] } - π΄ Public types in
packages/bosia/src/lib/index.tsβBosiaPlugin,BosiaConfig,BuildContext,DevContext,RenderContext,defineConfighelper
Elysia Hooks
- π΄
backend.before/backend.aftermount points inserver.tsβbeforeruns raw routes (e.g./openapi.json) bypassing framework middleware;afterreceivesRouteManifestfor introspection
Build Hooks
- π
build.preBuild/build.postScan/build.postBuildinbuild.tsβ callpreBuildbeforeloadEnv,postScanafterscanRoutes(),postBuildaftergenerateStaticSite() - π
build.bunPlugins(target)merged into client + serverBun.build()plugin arrays
Render Hooks
- π
render.headfragments injected before</head>inbuildMetadataChunk - π
render.bodyEndfragments injected before</body>inbuildHtmlTail - π
RenderContext(request, route, metadata) threaded fromrenderer.tsintohtml.tsbuilders
First-Party Plugin
- π
bosia/plugins/server-timingβ exercisesbackend.before; addsServer-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/openapifirst-party plugin - π
build.postScanreadsRouteManifest, emitsdist/openapi.json - π Runtime mount via
backend.beforeβGET /openapi.json,GET /docs(Scalar/Swagger UI) - π‘ Optional
schemaexport on+server.ts(TypeBox or Zod, decide later) - π‘ Docs: OpenAPI usage page
v0.4.2 β OpenTelemetry Plugin
Tracing + metrics for production apps.
- π
bosia/plugins/opentelemetryfirst-party plugin - π OTLP exporter config via env vars (
OTEL_EXPORTER_OTLP_ENDPOINT, etc.) - π Trace
backend.beforerequest β response,load()calls, render time - π‘ Verify
devparity β telemetry must work inbosia 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/inspectorfirst-party plugin (dev-only) - π Contributes Bun plugin via
build.bunPlugins()β runs beforeSveltePlugin()and replaces its.svelteonLoadwith an injecting variant - π Parses
.sveltesource withsvelte/compilerparse(), walksRegularElementnodes, injectsdata-bosia-loc="<relpath>:<line>:<col>"viamagic-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 capturesdata-bosia-loc - π
POST /__bosia/locateendpoint (mounted viabackend.before) β receives{ file, line, col }, opens editor (or POSTs toaiEndpointwith comment) - π‘ Editor integration β
code -g file:line(configurable viainspector({ 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 checkout of the box.
- π Ship
.gitignorewithbun x bosia createβ npm pack strips.gitignore, so templates store it as_gitignoreandcopyDirrestores the dotfile name on copy - π‘ Ignore generated Tailwind output
public/bosia-tw.cssin template.prettierignoreand.gitignore(default, demo, todo) sobun run checksucceeds on a clean scaffold - π‘
bun run check:templatesβ packs viabun pm pack, extracts the tarball, and asserts eachtemplates/*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.tsso freshly scaffolded projects get Alt+click-to-source out of the box.
- π‘ Add
bosia.config.tstopackages/bosia/templates/{default,demo,todo}/enablinginspector({ editor: "code" }).copyDirincli/create.tscopies it as-is (not in the exclusion list); no template substitutions needed. Production-safe (plugin self-disables underNODE_ENV=production) - βͺ Note preconfigured state in
docs/content/docs/guides/inspector.mdso 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/; standalonebun run buildkeeps writing to./dist/.
- π Decouple URL namespace (
/dist/client/...) from on-disk location viaOUT_DIRinpaths.ts(readsBOSIA_OUT_DIR, default./dist) - π
dev.tshardcodes.bosia/devand passesBOSIA_OUT_DIRto spawned build + app-server children; never reads the env itself - π
build.ts,prerender.ts,html.ts,server.ts,cli/start.tsall read fromOUT_DIRinstead of hardcoded./distliterals - π‘ Verification path:
BOSIA_OUT_DIR=.bosia/verify bun run buildproduces full artifacts (manifest, client, server, prerendered, static, route-manifest) without touching./dist. Catches whattsc --noEmit+svelte-checkmiss (route scan, prerender child, server-entry compile). Verified atapps/demo
v0.5.6 β Build/dev `.bosia/` cleanup collision β (shipped 2026-05-18)
Follow-up to v0.5.5.
OUT_DIRwas split, butbuild.tsstill blanket-wiped./.bosiaat startup β clobbering a concurrently-runningbosia devwhose compiled server lives at.bosia/dev/. Cleanup is now scoped.
- π΄
build.tscleanup is scoped toOUT_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. FixesENOENT reading .bosia/dev/server/+page-*.jsmid-request whenbun run buildruns alongsidebun run dev.
v0.5.7 β `params` as a top-level page/layout prop β (shipped 2026-05-19)
Match SvelteKit:
+page.svelteand+layout.sveltereceiveparamsas a sibling prop ofdata, not nested underdata.params. Network protocol (data endpoint payload, SSR injection) is unchanged βparamsis stripped at the component boundary.
- π
App.sveltepassesparamsas a separate prop on pages and layouts; SSR branch strips mergedparamsoffpageDatavia local helper - π
hydrate.tsseedsappState.pageDatawithout the mergedparamskey (still seedsappState.routeParamsfrom same payload) - π
routeTypes.tscodegen:PageData/LayoutDatano longer intersect{ params: Params };PageProps/LayoutPropsdeclareparams: Paramsas a sibling ofdata - π‘ Update demo + template
blog/[slug]/+page.svelteand docs (README.md,docs/content/docs/guides/routing.md) to consumeparamsas a top-level prop - π‘ Standardize
defaultandtodostarter templates on the(public)/route group convention used bydemo, 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 β
aiEndpointhandoff. Live-only (no server buffer, no SSE replay), dev-only (production unaffected β plugin self-disables).
- π Server capture: Elysia
.onError()hook +uncaughtException/unhandledRejectionprocess listeners installed lazily insidebackend.before().uncaughtExceptionrethrows sodev.tscrash-recovery still triggers. 500ms dedup window onsource:message:firstFrameprevents render-loop floods (packages/bosia/src/core/plugins/inspector/index.ts) - π SSE broadcaster at
/__bosia/errorsβ module-scoped controller Set,event: bosia-errordata frames, 25s:pingkeepalive, abort-driven cleanup. No replay buffer (live-only contract) - π Reorder Elysia onError chain in
server.ts: base 500 responder now registered afterplugin.backend.beforeloop 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+unhandledrejectionlisteners + 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.tsnow emitssourcemap: "linked"in dev ("none"in production). Newinspector/sourcemap.tslazy-resolves compiled stack frames β source(file, line, col)via@jridgewell/trace-mappingat POST time only for the error the user clicks "Send to AI" on. Per-processMap<path, TraceMap>cache; cache resets on app respawn so edits are never stale. Graceful degradation when.mapis missing - π‘ Last-interaction context: track the most recent
data-bosia-locthe user clicked/keyed on and appendLast 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(defaulttrue) config flag onInspectorOptionsβ 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 anybind:*on writable state) crashed the browser withRangeError: Maximum call stack size exceededon 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 namedfunction get()for$inspectstack traces; Bun rewrites$.getto a named importget; 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.tsandpackages/bosia/src/core/svelteCompiler.tsto rename the innerget/setto$$g/$$s(length-preserving so cached source-map columns stay accurate, names absent fromsvelte/internal/clientexports). Dev-only β prod compile uses anonymous arrows so the shim is skipped. - π΄ Inject Inspector-extracted component CSS via a runtime
<style>element instead of aloader: "css"virtual module. Bun'ssplitting: truenames CSS chunks after the importing JS chunk's[name](not the virtual module's uid), so when β₯2 routes share a styled.sveltecomponent the bundler emits identical+page-<hash>.csschunks and fails withMultiple 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.htmlwith%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.tsbuilders (buildHtml,buildHtmlShellOpen,buildMetadataChunk,buildHtmlTail) to accept optional segments and slot user chrome - π Update
renderer.tsto 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.htmlin templates (default,todo) and demo with%bosia.lang%anddata-themeattributes - π‘ Favicon detection: if user's
headOpencontainsrel="icon", skip framework default favicon injection - π‘ Unit tests: template loading, validation, parsing, caching, interpolation, segment structure
- π‘ New skill
bosia-app-cssdocumenting canonicalsrc/app.cssorder and the Tailwind v4 / LightningCSS@import url(...)ordering rule (font imports must come before@import "tailwindcss", else silently dropped frompublic/bosia-tw.css). Catalog indexdocs/content/skills/SKILL.mdupdated (33 β 34 skills); slotted under design conventions next tobosia-theme-tokens. Trigger: real-world incident intoko-mainan-anakwhere Fredoka font-family declarations rendered but the Google Fonts@importwas stripped by LightningCSS because it sat after@source "../src". - π‘ New CLI command
bosia add font "<Family>" "<url>"(packages/bosia/src/cli/font.tsβ reuses existingmergeFontImports()fromcli/fonts.ts). Prepends@import url(...)tosrc/app.csswith/* bosia-font: <Family> */marker so it survives Tailwind v4 / LightningCSS ordering. Idempotent. Wired intocli/index.ts(add fontsubcommand) with usage and example. Companion AI toolbosia_add_fontadded 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 forwindow.location.hreffor programmatic nav becausegoto()wasn't exported β and that escape hatch had its own caveats (full reload, lost SPA state). Now exposesgoto,beforeNavigate,afterNavigatefrombosia/clientwith the same shape SvelteKit ships.
- π
goto(url, opts?)exported frombosia/client. Returns a Promise that resolves after the nav effect settles (loaders ran, components mounted). HonorsreplaceState,invalidateAll,noScroll; acceptskeepFocusandstatefor forward compatibility but does not yet honor them. Routes throughrouter.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 viaonDestroy - π Router exposes navigation
type("link" | "goto" | "popstate" | "form" | "enter") and theNavigationobject threading fromrouter.navigate()into both lifecycle phases. Shared listener registry lives incore/client/navListeners.tsto break the ESM cycle betweennavigation.tsandrouter.svelte.ts - π
router.navigate(path, { replace, source })supportshistory.replaceState(used bygoto({ replaceState: true })) and threads the source through to the Navigation object - π‘
beforeunloadfiresbeforeNavigatewithwillUnload: trueso listeners can observe (cancellation requires nativebeforeunloadevent β out of scope) - π‘ Hydration safety net β wrapped
main()incore/client/hydrate.tsin 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 emptybodyβ non-streaming SSR responses (errors, form re-renders) skip it; streaming SSR andssr=falsepaths still get it for the TTFB β first-paint gap - π‘ Demo route
apps/demo/src/routes/(public)/nav-test/+page.svelteexercises all four patterns plus the cancel/event-log flow - π‘ New docs page
docs/content/docs/guides/navigation.mdcovers the four patterns and the lifecycle hooks; added to the Guides sidebar indocs/src/lib/docs/nav.ts - π‘ New
bosia-navigationskill (underdocs/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 inbosia-routingandbosia-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.onlisteners). Dev-infrastructure errors β build failures after a file save, app-server crashes,.envreload 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/ssechannel. 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.tscaptures build/app-crash/dev-uncaught errors into a bounded ring (50 entries, 30s TTL) with a 500ms dedup window β mirroringinspector/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 andBun.servefailures too - π New
event: bosia-errorover/__bosia/sse(same wire shape as inspector'sServerError). 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 secondEventSource("/__bosia/sse")listener so the samepushError()path handles dev errors without UI changes - π New
packages/bosia/src/core/dev-error-page.tsrenders the fallback HTML page returned by the dev proxy whenfetch(app)throws on an HTML navigation. Embeds the inspector overlay script, pre-seeds buffered errors via a globalwindow.__BOSIA_PUSH_ERROR__, and subscribes to/__bosia/ssefor thereloadevent 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 - π‘
.envreload 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 betweenbeforeNavigateand 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.hrefstall 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 layoutload(), the pageload(),render(), andBun.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 inCACHE_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)withAccept-Encoding: br | gzip | identitynegotiation,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.tsGET handlers cached with the same key rules. v0.6 invalidates API entries by URL/prefix only (nodepends()for API yet). - π Public API β
invalidate(key)/invalidateAll(prefix)frombosiamirror the existing browser-sideinvalidate()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.tsexports aCacheOptiontype alias for IDE support. - π‘ Env vars β
CACHE_KEYS(defaultsession,sid,auth,token,jwt,Authorization) controls identity-hash inputs;CACHE_MAX_ENTRIES(default500,0disables). Documented inguides/environment-variables(EN + ID) and the response-cache guide (EN + ID). - π‘ Author guidance β new
bosia-response-cacheskill (docs/content/skills/bosia-response-cache/SKILL.md) walks AI agents through when to callinvalidate()from server code, how to tag loaders withdepends(), and when to opt a route out. Data-invalidation guides (EN + ID) gained a "Server-sideinvalidate()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'sAccept-Encoding: gzip,br,β¦to the inner app, the inner returned compressed bytes withContent-Encoding: gzip, and Bun'sfetch()auto-decoded the body but left theContent-Encodingheader on theResponse. Header said gzip, body was plaintext β Safari threwNSURLErrorDomain:-1015 cannot decode raw dataon 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.tsguardsprocess.envreads β module is re-exported through the publicbosiabarrel (invalidate/invalidateAll), so it evaluates in the browser bundle whenever a user app imports anything frombosiaclient-side (e.g.demo/src/lib/utils.tsre-exportscn). Top-levelprocess.env.CACHE_KEYS/process.env.CACHE_MAX_ENTRIESthrewReferenceError: Can't find variable: processon hydration in Safari. Now reads through atypeof process !== "undefined" ? process.env : {}shim and the startupconsole.logis gated on the same check. - π Server-only response-cache exports moved to
bosia/serversubpath β even with thetypeof processshim,core/cache.tswas still evaluating client-side whenever a user imported anything from the sharedbosiabarrel, becauselib/index.tsre-exportedinvalidate/invalidateAllfrom it. Added./serverto packageexports, createdpackages/bosia/src/lib/server.tsre-exporting them, removed them from the shared barrel. Updated guides (docs/content/docs/{,id/}guides/{response-cache,data-invalidation}.md) and thebosia-response-cacheskill toimport { invalidate } from "bosia/server". Mirrors the existingbosia/clientsplit. No live callers in demo/templates to update. The shim incache.tsis kept as defense-in-depth. - π‘ Inspector dev-error reporter type alignment β
core/devErrorReport.tsdeclaredsource?: "server" | "uncaught" | "rejection"butpushServerErrorincore/plugins/inspector/index.tsaccepted"elysia" | "uncaught" | "rejection".bun run check(tsc) failed with TS2322 ininspector/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.tsendpoints β give API handlers adepends()argument or supportexport const tags = [...]soinvalidate("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 = falsecascade β 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 sharedButton.sveltepreviously showed onlyButton.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 aroundComponent/SvelteComponent/SvelteSelfAST nodes ininjectLocs(packages/bosia/src/core/plugins/inspector/bun-plugin.ts). Comments survive Svelte compile becausepreserveComments: devis already set, and run for bothbrowserandbuntargets so SSR HTML matches client hydration. - π Runtime
collectStack(el)walks DOM ancestors + previous siblings with a depth counter that matches eachbosia:cagainst itsbosia: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 (prependsComponent tree (outer β leaf): β¦\n\n), and the runtime-errorlastInteractionfield (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.mdupdated to describe the chain feature and extend the prod-output grep to check for both markers. - π‘
bosia-inspector-editskill (docs/content/skills/bosia-inspector-edit/SKILL.md) updated for the new payload β parses theComponent 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 indocs/content/skills/SKILL.mdupdated.
Same-day addition (2026-05-23) β Env + CORS skills for AI agents
Bosapi-spawned preview apps (served via
a-<uuid>.lvh.me:9000) were surfacing403 Cross-origin request blocked: Origin "β¦lvh.meβ¦" is not allowedand 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) inCSRF_ALLOWED_ORIGINSin the child.env(verified against working apptoko-mainan-anakwhich carriesCSRF_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-envskill (docs/content/skills/bosia-env/SKILL.md) β four-tier prefix (PUBLIC_STATIC_/PUBLIC_/STATIC_/ none),$envvirtual module for user vars,process.envfor framework-reserved vars (full table coveringPORT,BODY_SIZE_LIMIT,IDLE_TIMEOUT,MAX_INFLIGHT,CORS_*,CSRF_ALLOWED_ORIGINS,TRUST_PROXY,DISABLE_X_FRAME_OPTIONS,CSP_DIRECTIVES,BOSIA_OUT_DIR)..env.exampleas the contract;.env*load order rules. - π‘ New
bosia-corsskill (docs/content/skills/bosia-cors/SKILL.md) β CORS env recipe (CORS_ALLOWED_ORIGINS+ methods/headers/exposed/credentials/max-age),Vary: Origininvariant, 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 (403response body withCross-origin request blocked: Origin "β¦"). Preview-proxy workflow lists the lvh.me preview origin(s) inCSRF_ALLOWED_ORIGINS(primary) withTRUST_PROXY=truedocumented as the alternative for proxies that need forwarded headers reflected. - π‘ Catalog
docs/content/skills/SKILL.mdupdated 35 β 37 skills; both entries added under framework conventions and into the discovery-order step 2; cross-references wired in both directions and tobosia-security-review/bosia-elysia-routes.
v0.5.11 β `$types` resolution inside `.svelte` files
tsc --noEmitresolves./$typesfrom.sveltefiles via therootDirs: [".", ".bosia/types"]trick, sobun run checkandbun run buildboth type-checkparams/PagePropscorrectly. Butsvelte-language-server(used by Zed, VS Code w/ Svelte extension, etc.) runs.sveltescript blocks through a preprocessor and doesn't honorrootDirsfrom inside that virtual TS document β the editor reportsCannot find module './$types'andparamscollapses to implicitany. SvelteKit avoids this by shipping a dedicated language-tools plugin (@sveltejs/language-tools) that synthesizes$typesvirtually at LSP time. Bosia needs the same.Acceptance: in a freshly scaffolded Bosia app, hovering
PagePropsin+page.svelteshows the generated type, autocomplete onparams.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
moduleResolutionfor$typesspecifiers from.sveltefiles; (b) fork/extendsvelte-language-serverconfig; (c) shim by re-exporting from a plain.tsbarrel the LSP already sees. Pick the lowest-friction path. - π Ship the plugin/shim from
packages/bosiaand wire it into the scaffolding templates'tsconfig.json(compilerOptions.pluginsorsvelte.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 showsParams = { slug: string }, autocomplete onparams.listsslug, typingparams.foored-squiggles. - π‘ Document the editor setup step in
docs/content/docs/guides/routing.md(or a new "Editor setup" guide) β what extension to install, whattsconfig.jsonlooks 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.mdat 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, writesBRIEF.md, chainsbosia-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. Runsbosia_add_theme+--primary/--accentoverride. - π‘
bosia-brief-platformβ form factors, primary surface, ID format regex, number/dateIntlformatters, 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.mdindex updated β 25 β 31, new section "Brief intake β design β¦", discovery order gains step 0 "check BRIEF.md".
Hotfix (same-day, 2026-05-17)
- π΄ Fix
bosia devbuild crashing withMultiple files share the same output pathon apps with multiple style-less+page.svelteroutes.inspector's per-svelte virtual CSS chunk (packages/bosia/src/core/plugins/inspector/bun-plugin.ts) now skips emission whenresult.css.codeis 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.svelteto[name]="+page". Production builds were unaffected (inspector self-disables underNODE_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 fromnexu-io/open-designfrontend-design; bodies rewritten for Svelte 5 + Bosia semantic tokens + registry-first composition. Ships withreferences/aesthetic-directions.md(11 starter directions: brutally-minimal, editorial, brutalist, retro-futuristic, maximalist, soft-pastel, luxury, industrial, organic, playful, art-deco) and aBRIEF.md Β§ Aesthetictemplate. CatalogSKILL.mdindex 31 β 32; design-conventions section gains the third row. - π‘
bosia-frontend-designwired intobosia-brief-intakeas step 4 (afterbosia-brief-visual), so every new app's BRIEF.md ends with a populated## Aestheticsection before any feature work. Quick-start opener bumped 5 β 6 questions.bosia-brief-visualhands off to the stance step.bosia-brief-reviewgains P0 checks B18 (stance committed, no AI-default direction/fonts), B19 (fonts wired inapp.css @theme, not per-component), B20 (accent override applied to:rootso 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-reviewgains a P1 check confirming each emit honors Β§ Aesthetic (direction, memorable detail, fonts fromapp.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-intakeships first two reference files:references/quick-start-script.md(6-question opener with palette-intent β direction inference defaults) andreferences/example-brief.md(Dombaku-style fully-filled BRIEF.md including Β§ Aesthetic). Frontmattertargets.filesonbosia-frontend-design(BRIEF.md + src/app.css) andbosia-brief-intake(+ src/app.css) updated. CatalogSKILL.mdBrief-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.tsroutes as pages already had. Drop the docs-only static-API post-build pipeline.
- π Framework:
+server.tshonorsexport const prerender = trueβdetectPrerenderRoutesscansmanifest.apis, dynamic routes callentries(),prerenderApiOutPath()writes a single.jsonper 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 = trueare 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
prerenderApiOutPathandsubstituteParamsrest-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 exportentries()fromlistSkills()/listRegistry() - π‘ Removed
generateSkillsApi()+generateRegistryApi()fromdocs/scripts/post-build.tsβ post-build returns to sitemap-only
Hotfix (same-day, 2026-05-16)
- π΄ Fix dev
.jsonalias resolution: catch-all sibling routes (/api/components/[...path],/api/blocks/[...path],/api/skills/[name]) were absorbing the.jsonsuffix into their rest-segment param, causing 4xx in dev. Logic now tries the bare path first when the URL ends in.jsonand prefers it only if the matched route opted intoprerender = true. Extracted intopackages/bosia/src/core/apiResolver.tsso it can be unit-tested independently of the bundler-virtualbosia:routesmodule - π΄ Fix
/api/skills/<name>JSON shape: was emitting rawSKILL.mdmarkdown into a.jsonfile. Handler now returnsResponse.json({ name, content })with frontmatter stripped viagray-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, andmodule()throw β fallback - π‘ New
docs/test/api-prerender.test.tsβ post-build sanity overdist/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βcontentin/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)). Lazyawait 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 staticimport { createHighlighter } from "shiki"indocs/src/lib/docs/markdown.tsβ shiki is now bundled inline with the page-server bundle, no cross-chunk circular eval - π‘ Normalize
pathfield on/api/skills,/api/components,/api/blocksindex + detail responses to the full detail-endpoint URL (e.g./api/components/ui/button.json); skills detail gainspath. Breaking for components/blocks index consumers that read bare-segmentpath. InternalRegistrySummary.pathandentries()prerender seed remain segment-form (test indocs/test/api-prerender.test.tsasserts 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 addaccepts multiple component names in one call; new-y/--yesflag auto-confirms overwrite prompt for CI use - π‘ Static
/api/components.json+/api/components/{path}.jsonand/api/blocks.json+/api/blocks/{path}.jsonemitted bydocs/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 incore/plugin.ts. Without it, every dynamic-imported route chunk that transitively reachesapp.cssproduces an identical CSS sidecar (+page-<hash>.css) and Bun fails the build withMultiple files share the same output path. Tailwind CLI continues to emit the real stylesheet atpublic/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
onBeforeHandleapiRoutes scan; non-GET catch-alls already cover every method - π Inline Svelte compile, drop
bun-plugin-svelteβ own.svelte/.svelte.[tj]sonLoadwithcss: "injected"(browser) /css: "external"(server). Eliminates the dynamic-import CSS-sidecar collision at the root and removes the double-compile workaround incore/plugin.ts
Open
- π Truly progressive SSR streaming β
renderSSRStreamis 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
safeJsonStringifycost 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 viaJSON.parse(document.getElementById(id).textContent). Escape surface drops from 5 JS-context sequences to</script/<!--only; clean payloads are byte-identical toJSON.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 tosrc/lib/blocks/<path>/ - π
bun x bosia@latest add theme <name>β install a theme tosrc/lib/themes/<name>.css, patchapp.cssimport - π‘ Extend CLI dispatcher (
packages/bosia/src/cli/index.ts) foradd block/add themesub-args - π‘ Refactor
add.tsβ parameterize destination root;RegistryIndexgainsblocks: string[],themes: string[] - π‘
block.tshandler β recursive primitive deps viaaddComponent(), optional font@importmerge intoapp.css - π‘
theme.tshandler β copytokens.css, swap@importinapp.css(one-active-theme), font@importmerge
Registry content
- π Extend
registry/index.jsonwithblocksandthemesarrays - π
registry/themes/neutral/β extracted from currentapps/demo/src/app.css@themeblock - π
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.cssto@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.svelteregistered innav.tsand[...slug]/+page.sveltedemos map
v0.5.0 β Full Plugin Lifecycle
Complete the plugin surface; uninstall + virtual modules.
- π
dev.onStart+dev.onFileChangewired indev.ts - π
client.onHydrate+client.onNavigateincore/client/hydrate.ts+router.svelte.ts - π Virtual modules from plugins β extend
core/plugin.tsresolver 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 createscaffolding - π‘ Test file examples in project templates
- π‘
bosia feat testscaffolder 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.cssno-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: trueemits one CSS sidecar per chunk that imports a shared CSS file β collision when N routes transitively importapp.css - Current fix (
plugin.tsinterceptsapp.cssβ empty JS module) ships ALL utilities in onepublic/bosia-tw.cssregardless 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.cssno-op interception incore/plugin.tsonce dedup lands - π‘ Component
<style>blocks: continue withcss: "injected"(already scoped + deduped viacssHash) - π‘ Tailwind into bundler hot path β port
@tailwindcss/viteshape to Bun plugin API so utilities are scanned + emitted as part of the build, not a parallel CLI step
Approach Options
- Wait on Bun upstream β file/track issue for CSS chunk dedup under
splitting: true. Lowest effort, unbounded timeline. - Custom Bun plugin β own CSS pipeline in
core/cssPipeline.ts: intercept all.cssimports, hash contents, emit one shared chunk per unique source, track route β chunk mapping, inject<link>tags viarender.headper request. - Static layout import workaround β make root
+layout.sveltea static import (not dynamic) inroutes.client.ts. Collapsesapp.cssinto entry chunk β no per-route duplication. Cheapest fix, but loses dynamic layout chains.
Acceptance
- Builds with 100+ routes succeed without the
app.cssno-op - Each route ships β€ what it imports (verified by inspecting
dist/client/*.csssizes) - Component
<style>still scoped viacssHash - 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.tsuniversal 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)