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.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) - π‘ Cookie
sameSiteaccepts both casings (lax/Lax) β normalized to canonical header - π Protocol-aware
Securecookies β auto-downgrade over HTTP with warn;TRUST_PROXY=truehonorsx-forwarded-proto - π 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) - π Bun-native drizzle migrate runner β
src/features/drizzle/migrate.tsreplacesdrizzle-kit migratefor sqlite/postgres/mysql apps (drizzle-kit's sqlite migrate requiresbetter-sqlite3/@libsql/client; bun-native runner usesdrizzle-orm/bun-sqlite/migrator+drizzle-orm/bun-sql/migrator). - π
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 β shipped asfile-uploadin 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/filesPOST that pipes throughBun.Imagecompression (WebP @ 0.85, fit-inside 1920Γ1080). Install-time dialect selection via the new per-feature options mechanism β-dis declared infile-upload's ownmeta.json, not hard-coded in the CLI - π‘ 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.6.5 β Compile-time component-import audit β (shipped 2026-05-27)
A scaffolded app crashed on first SSR render with
undefined is not a functionbecause+page.sveltedidimport * as Card from "$lib/components/ui/card/index.ts"and used<Card.Root>β butcard/index.tsexportsCard/CardContent, notRoot.bosia buildsucceeded silently: neithersvelte/compilernor 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/..."), usesBun.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 containingexport *are treated as opaque to avoid false positives. - π
packages/bosia/src/core/svelteCompiler.tsβ switchedcompile()tomodernAst: true(legacyhtmlAST β modernfragment/instance/module), wired the audit intoonLoad, and added module-scoped per-file dedupe (Map<absPath, Promise>) so the audit runs exactly once per file across the parallelbrowser+bunbuild targets. - π Promotes select
svelte/compilerwarnings 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 fromplugin.tsandplugins/inspector/bun-plugin.tsso the audit and the existing resolver share one alias / tsconfig-paths / relative-path implementation and onelineColFromOffsethelper.plugin.ts:onResolve($)now delegates toresolveImportPath(). - π‘
BosiaConfig.strictImports(boolean |{ unbound, namespaceMember, warnings }) β per-component opt-out.BOSIA_STRICT_IMPORTS=0env var downgrades to aconsole.warnat 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.sveltefalse-flagged<DemoComponent />when bound via a sibling{@const}. Fragment pre-pass collects allConstTagbindings 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 viaUploadArea, then click the crop button to overlayCropImageagainst the file's object URL. Object URLs are same-origin, so thecrossOriginattribute 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, opensCropImageagainstURL.createObjectURL(file), wraps the returned Blob as aFileand callsdone(file). Replaces the two earlier per-block demos. - π‘
docs/src/routes/api/demo-upload/+server.tsβ tinyPOSTreturning{ url, ok }so the demo Upload button doesn't 500. - βͺ Both
docs/content/docs/blocks/files/{crop-image,upload-area}.mdfrontmatterdemo:now points atFilesUploadCropDemo.[...slug]/+page.svelteimports the new demo only; deletedFilesCropImageDemo.svelteandFilesUploadAreaDemo.svelte. - π‘
registry/blocks/files/crop-image/block.svelteβ switched the 400px viewport fromh-[400px]tostyle="height: 400px;". The class itself works for end-users (their Tailwind scans their ownsrc/), 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 intobosia-tw.cssfrom the docs build alongsideregistry/components/ui. - π
docs/src/lib/docs/content.tsβcontentDiranddemoFileno longer resolve relative toimport.meta.dir. In dev, the server bundle lives at.bosia/dev/server/*.js(three levels deep), in prod atdist/server/*.js(two levels deep); the old../../content/docstraversal silently missed the content dir in dev, making every catch-all docs page 404 via theloadDoc β null β error(404)path. Both paths now anchor onprocess.cwd()(same approach[...slug]/+page.server.ts:12already uses), making dev and prod resolution identical.
Same-day addition (2026-05-26) β `file-upload` feature + CLI dialect flags
The
files/upload-areablock has shipped since v0.6.3 but bosia had no server-side counterpart β every user had to write their ownPOST /api/files. Closes the gap with a full backend feature, and adds install-time DB dialect selection tobun x bosia featso the same feature can ship Postgres / MySQL / SQLite Drizzle tables without a runtime selector.
- π
registry/features/file-upload/β full backend scaffold.file.service.tsvalidates MIME (image/jpeg|png|webp|heic|avif), decodes viaBun.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 pathsrc/features/file-upload/schemas/file.table.tsβ install-time dialect filter picks one.storage/adapter pattern:LocalStorage(Bun.write+UPLOAD_DIR/PUBLIC_BASE_URLenv) andS3Storage(Bun.s3.file().write/delete).src/routes/api/files/+server.ts(GETlist,POSTupload) +[id]/+server.ts(DELETEcascades 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/--yesand--local; everything after the feature name flows toresolveFeatureOptions()which parses against the feature's ownmeta.jsonoptions: 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(whenchoices) or.text; with-y, fall back todefault.FileEntry.when?: Record<string, string>filters which files install. Resolved option values thread throughInstallOptions.featureOptionsnamespaced asfeatureName.optionNameso dependency features can read them; the root feature also receives rawfeatureArgsfor 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 torunFeat. Help text updated to reflect that feature-specific flags follow the feature name. - βͺ
packages/bosia/src/cli/registry.tsβInstallOptionsgainedfeatureOptions(resolved values) andfeatureArgs(raw tokens for the root feature). No CLI-level dialect type β dialect is nowfile-upload-specific. - βͺ
registry/index.jsonβfeaturesarray gainsfile-upload. - π‘
docs/content/docs/guides/file-upload.mdβ install / env / wiring / S3 swap docs; cross-link added fromblocks/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-referencesbosia-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>.jsoncould seeSKILL.mdbody 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)indocs/src/lib/skills/list.tsβ reads<SKILLS_ROOT>/<name>/references/, filters to.mdfiles, validates slugs against^[a-z0-9-]+$, returns{ file, path }[]sorted by file. Silent[]on missing dir. - π‘
GET /api/skills/[name]response gainedreferences: 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,realpathtraversal guard mirrors the existing[name]route. Returns{ name, file, path, content }withcache-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-areaaccepts an optionalonCropRequestcallback so callers wirecrop-imagein only when they want cropping. Newfiles/category, two icons added (crop,zoom-in).
- π‘
registry/blocks/files/crop-image/β Svelte 5 cropper wrappingsvelte-easy-crop(^5.0.0); aspect/shape presets, zoom slider, returnsBlobviaonCropComplete. Inlined canvas crop helper (no separate util file). Usesui/button,ui/label,ui/slider,ui/icon,ui/sonner. Cropped output is resized to fitmaxWidth Γ maxHeight(default 1920Γ1080, never upscales) and re-encoded atquality(default 0.85). Newformatprop 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,XMLHttpRequestprogress,Progressbar. Props:uploadUrl(required),accept,maxSizeMB,fieldName,extraFields,headers,enableCrop+onCropRequest,onUploaded,onError. Expects JSON{ url, ... }response. - βͺ
registry/components/ui/icon/icons.tsβ addedcropandzoom-inpaths (lucide-static). - βͺ
registry/index.jsonβblocksarray gainsfiles/crop-imageandfiles/upload-area. - βͺ Docs pages
docs/content/docs/blocks/files/crop-image.mdandupload-area.md; Files group added todocs/src/lib/docs/nav.ts;FilesCropImageDemoandFilesUploadAreaDemoregistered in[...slug]/+page.svelte. - π‘
packages/bosia/src/core/build.tsβ addedconditions: ["svelte"]to both client and serverBun.buildcalls so Svelte component libraries (likesvelte-easy-crop) resolve to theirsvelteexport entry. Initial attempt used a genericonResolvehandler in the framework plugin that walked package.json for asveltefield; even when it returnedundefinedfor non-Svelte packages, it broke shiki's chunked CommonJS interop at runtime (b0 is not defined/bundle_full_exports is not definedon the page-server bundle). The Bun-nativeconditionsoption 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 puttingdb.select(...)directly in+page.server.tsloaders, importing tables out offeatures/drizzle/tables/, and skipping a service/repository layer entirely. The existingbosia-drizzle-usageskill's "Quick Start" example actually taught this anti-pattern. New skillbosia-clean-architecturedefines 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-architectureskill (docs/content/skills/bosia-clean-architecture/SKILL.md) β eight rules (R1βR8) covering nodbin routes, repository ownership, service-owned validation, derived valibot validators viadrizzle-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 usingwarung-nasi's+page.server.tsas before/after),shared-folder.md(what belongs infeatures/shared/and what does not). - π‘
bosia-drizzle-featureupdated β 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-usageupdated β Quick Start rewritten so loaders callCatalogService.summary()notdb.select(...); Workflow now writes the repository first, then service; new red flags fordbin routes anddb.*in services; P0 tightened to forbiddbimports undersrc/routes/**. - π‘ Catalog
docs/content/skills/SKILL.mdbumped 38 β 39 skills;bosia-clean-architectureadded 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 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)