Roadmap
Apa yang sudah selesai, berikutnya, dan ke mana arah Bosia.
Track what's done, what's next, and where we're headed. Current version: 0.8.8
0.8.8 (2026-07-11) — Inspector: AI popover clamped to viewport
openForm()placed the popover at a fixedr.bottom+6— off-screen for elements near the bottom of the page/iframe (rukoku editor embed).
- 🟠
plugins/inspector/overlay.ts— mount at 0,0, measureoffsetWidth/Height, flip above when no room below, clamp both axes to the (iframe) viewport with 8px margin. - ⚪ Known ceiling: position computed once at click; no scroll/resize reposition (popover is short-lived).
0.8.8 (2026-07-11) — `__BRAND__` guard reaches all templates
The 0.8.3 brand guard lives in
bosia sync, but only thedefaulttemplate'scheckscript ran sync — demo/shop/store (rukoku apps) passedbun run checkwith__BRAND__still in the footer.
- 🟠
templates/{demo,shop,store}/package.json—checknow runsbosia sync &&first (also fixes tsc checking stale.bosia/codegen). - ⚪ Existing scaffolded apps keep their baked
checkscript; fix is the same one-line edit per app.
0.8.7 (2026-07-10) — `export const snapshot` (page state on back/forward)
SvelteKit-parity
snapshot = { capture, restore }in+page.svelte. Mirrors scroll restoration: captured at the same save points keyed byhistory.state.bosiaIndex, restored post-tick()after the destination mounts; persisted tosessionStorageon unload (JSON-serializable values only).
- 🟠
core/client/router.svelte.ts—pageSnapshotsmap +savePageSnapshot()at the foursaveScroll()sites;pendingSnapshothandoff (popstate + reload-restore ininit()); separate sessionStorage flush so a non-serializable snapshot can't kill scroll persistence. - 🟠
core/client/App.svelte—bind:this={pageInstance}on both<PageComponent>spots;router.getPageSnapshotclosure;settleSnapshot()insettleScroll()+ first-hydration branch. - ⚪
Snapshot<T>type inbosia/client+ generated$types.d.ts(routeTypes.ts);routeTypes.test.tsassertion; docsguides/navigation(en+id) "Snapshots" section;bosia-navigationskill R2b;CHANGELOG.mdappended. - ⚪ Deliberately skipped: layout snapshots (need per-depth instance refs in the recursive layout snippets); non-JSON values (no devalue); inherits the
beforeunloadmobile page-discard ceiling from scroll.
0.8.7 (2026-07-10) — `setHeaders()` in load functions
SvelteKit-parity
setHeaders(headers)on the server-loader event. One accumulator shared across layout+page loaders (cross-loader dup detection); keys lowercased;set-cookieand duplicates throw → existing 500 path. Headers land on SSR HTML, the response-cache write, form-action re-renders, and/__bosia/data/*.json. Data endpoint: loadercache-controlwins over the computed default unless cookies were accessed — thenprivate, no-cachewins (privacy beats intent).
- 🟡
hooks.ts—LoadEvent.setHeaderstype +makeSetHeaders()(lives here so tests avoidbosia:routes);renderer.tswires it into both loader events, returnsloaderHeaders, spreads onto response sites + cache write. - ⚪
compress()+serveCached()base keys lowercased so extraHeaders override instead of comma-joining. - ⚪
server.tsdata endpoint destructuresloaderHeadersout of the JSON body, merges with cookie-privacy override. - ⚪
test/setHeaders.test.ts; docsguides/server-loaders(en+id) incl. missingdependsrow;bosia-routingskill R3b; roadmap check-off;CHANGELOG.md; version0.8.7. - ⚪ Deliberately skipped: headers on error/redirect responses; prerender = silent no-op; masked data requests don't re-set headers.
0.8.6 (2026-07-05) — Scroll restoration on back/forward
Browser auto-restoration fired at popstate time, before the SPA rendered the destination — it clamped against the wrong document height (worse with
+loading.svelteskeletons). Router now owns it:scrollRestoration="manual", position saved per history entry, restored after the nav settles.
- 🟠
core/client/router.svelte.ts— manual mode; per-entry index inhistory.state.bosiaIndex; save on push/hash-push/popstate/unload;pendingScrollhandoff; sessionStorage persistence + reload restore. - 🟠
core/client/App.svelte— sharedsettleScroll()replaces the two duplicated top-scroll blocks; popstate branch restoresrouter.pendingScrollaftertick(). - ⚪ Docs
guides/navigation(en+id) "Scroll behavior" section;bosia-navigationskill notes it's built-in (never hand-roll inafterNavigate).CHANGELOG.mdappended. - ⚪ Split off:
export const snapshot(form/UI state restore) — shipped 0.8.7. - ⚪ Known ceiling: scroll save relies on
beforeunload, which mobile page-discard can skip (router.svelte.ts:192) — addvisibilitychange/pagehidefallback if reports come in. - ⚪ Known ceiling: restore is a single post-
tick()jump; images/async content loading later can shift the position (App.svelte:78) — re-clamp on load events if it bites.
0.8.6 (2026-07-05) — Unify dev/prod component CSS (drop the collision workaround)
Investigated the open 0.8.5 Bun-collision item. It does NOT reproduce on Bun 1.3.14: CSS-chunk hashes now include source-module identity, and shared CSS hoists to one chunk (proved via isolated
Bun.buildharness). Prod was never at risk — its client compiler usescss:"injected"(styles in JS, no chunking). Only the dev inspector usedcss:"external"+ a hand-rolled runtime<style>injection to dodge the (now-fixed) collision — functionally identical toinjected, just more code.
- 🟠
plugins/inspector/bun-plugin.ts— inspector compile nowcss: generate==="client" ? "injected" : "external", mirroringsvelteCompiler.ts. Deleted thevirtualCssmap, theVIRTUAL_NSvirtual-module resolve/load handlers, and the runtime injection block (~30 lines); dropped unusedbasenameimport. - ⚪
test/svelte-build.test.ts— new dev-inspector case: many+pageroutes sharing one styled component → build succeeds, zero CSS chunks, scoped keyframe present in JS. - ⚪ Version bump
0.8.6+CHANGELOG.md.
0.8.5 (2026-07-04) — Content-hashed Tailwind CSS delivery
Prod served
/bosia-tw.csswith no Cache-Control (browser heuristic caching → stale styles after deploys); dev used?v=Date.now().
- 🟠 Tailwind output →
dist/client/bosia-tw-<sha256:10>.css(temp file + rename viatwHash.ts);twfield in dist/manifest.json; all 4 html link sites viatwCssLink(), no cache buster; fallback/bosia-tw.cssfor old artifacts. Immutable caching free viaHASHED_BASENAME. - 🟡 CSS output collision under
splitting: true— RESOLVED in 0.8.6. Doesn't reproduce on Bun 1.3.14 (hash now includes module identity + shared CSS hoists); won't file upstream. Dev switched tocss:"injected", workaround removed.
docs 0.8.2 (2026-07-03) — Footers + marketing sections
Registry had 19 navbars and 17 heros but zero footers and nothing for the middle of a landing page. Net-new design across four phases.
- Phase A —
footers/*(12 blocks): 6 standard + 6 themes, docs, demos, nav group. - Phase B — 7 section families (16 blocks): pricing, testimonials, faq, cta, features, stats, logos.
- Phase C —
pages/landing/*(2 pages): saas + simple. - Phase D — skills (
bosia-footers,bosia-sections) + landing/saas/pricing skill repoints + indexes.
0.8.4 (2026-07-03) — Cache security fixes (framework review S-findings)
Review of the response-cache + static-serving layers found identity-hash collision + memory-DoS risks and 3 perf issues. Discovered during planning:
Bun.brotliCompressSyncdoesn't exist — brotli variants were never built (fixed by P1).
- 🔴 S1 — identity hash FNV-1a-32 → SHA-256 (64-bit hex); collision could serve user A's page to user B.
- [~] S2 — anonymous-identity write gate — CANCELED during planning. Replaced by: [x] loud runtime warning (dev+prod, once per name) when a cached response's loader read a cookie not in
CACHE_KEYS, + docs callout in response-cache guide. - 🟠 S3 —
CACHE_MAX_BODY_BYTESper-entry cap (default 2MB,0unlimited); guard incacheSet+ early-skip before compression. - 🟡 P1 — brotli via
node:zlibquality 5 (also un-breaks brotli entirely). - 🟡 P2 — prerendered pages: boot-time manifest (
buildPrerenderManifest) instead of per-requestexists()probes. - 🟡 P3 — percent-encoded static paths 404 fixed (
decodeURIComponentinlookupStatic+ dev fallthrough). - 🟠 Identity-aware dedup — data-endpoint dedup key now includes the
CACHE_KEYSidentity hash (computeCacheKey), so per-user routes dedup safely. BREAKING:(private)no longer skips dedup; custom session cookies must be inCACHE_KEYS. - 🟡 P4 — LRU eviction is O(evicted.tags), not O(all tags):
LRU.setreturns the evicted{key,value}socacheDeleteIndexOnlyusesentry.tagsdirectly. - 🟠 Header-auth cache-isolation caveat — startup banner + response-cache docs now say the uncovered-cookie warning cannot see request headers; custom auth headers must be in
CACHE_KEYS. - 🟡 Auth
nextround-trip —registry/features/authlogin/register now honor?next=viasafeNext(same-site paths only, else/dashboard); was always forcing/dashboard. - 🟠 Route
scopedeleted — zero runtime consumers after identity-aware dedup;(private)is an ordinary route group (scanner/types/ambient/codegen field removed). - 🟠 Miss coalescing —
coalesceMissgate in cache.ts wired into SSR + API paths; N concurrent misses on one key build once, waiters re-check after the leader's microtask write. - 🟡
CookieJar.peek— identity hashing no longer flipsaccessed/readNames, so it can't forceCache-Control: private(latent bug on all cached paths).
docs 0.8.1 (2026-07-02) — Storefront e-commerce expansion (reviews, cart, wishlist, search, account)
The storefront covered browse/buy but nothing around it. Five phases add the real-world pieces: PDP reviews, cart page + wishlist (first favs-API UI), search, account area, quick-view + mega-menu.
- 🟠 Phase A —
blocks/storefront/reviews(summary bars, list, working form) wired intopages/storefront/product; docs product family + demo. - 🟠 Phase B —
empty-state,cart-lines,wishlist-gridblocks;pages/storefront/{cart,wishlist};order-summarygains actalabel prop; docs family + 2 page docs + 3 demos. - 🟠 Phase C —
search-overlayblock +pages/storefront/search(header already hadonSearch); layout family docs + search page docs + demo wiring. - 🟠 Phase D —
store/orders.tssamples;account-nav,order-list,order-detail,address-book,account-settings;pages/storefront/account(client-side section switching); account family docs + page docs + 2 demos. - 🟡 Phase E —
quick-viewmodal (+product-cardonQuickVieweye button),mega-menustandalone panel; product/layout/catalog docs + demo wiring.
docs 0.8.1 (2026-07-02) — Extract Mode Switcher component
The dark/light/system toggle lived inline in the navbar and couldn't be reused. Extract it as a standalone
mode-switchercomponent. Also fixes a bug: a stored theme was read into state on load but never applied, so the theme visually reset on refresh.
- ⚪
registry/components/ui/mode-switcher/— new component (cycle button), apply theme on init. - ⚪ Navbar consumes
<ModeSwitcher />; theme logic + Button import removed; dep →ui/mode-switcher. - ⚪ Register in
registry/index.json; docs page + demo + overview + nav entries. - ⚪
bosia-theme-tokensskill — "Runtime light/dark switch" note so agents reach forui/mode-switcher.
0.8.3 / docs 0.8.1 (2026-07-02) — `__BRAND__` placeholder + guard
AI agents install navbar/storefront blocks but forget to rename the hardcoded brand ("Mercato", "Brand"), shipping someone else's name in the nav/header/footer. A real-looking word doesn't trip the "replace me" reflex. Fix: rename the literal to an unmistakable
__BRAND__sentinel, failbosia sync(→bun run check) while any survive, and teach the block-compose skill to rename it.
- 🟠
registry/blocks/navbars/*/block.svelte(19) +storefront/{header,footer}— brand text →__BRAND__. - 🟠
packages/bosia/src/core/brandGuard.ts+cli/sync.ts— scansrc/, fail on leftover__BRAND__. - ⚪
bosia-block-composeskill — R8 + checklist gate for the placeholder. - ⚪ Version bump
bosia 0.8.3+docs 0.8.1+ bothCHANGELOG.md. - ⚪ Docs landing redesign — shared
LandingPage.svelte(EN/ID), code-forward hero, live registry showcase. - ⚪ Chart tooltip — draw with native SVG rect/text, not
<foreignObject>; kills Chrome's ghost-box repaint bug.
0.8.2 (2026-07-01) — Host-managed dev mode
A multi-tenant host (rukoku) edits app source then watches the dev server self-trigger a rebuild on every file event — and the watcher can compile a half-written file mid-edit, wedging the preview with a stale-manifest 500. Add an opt-in mode where the host is the single clock: it drives one build per turn via
/__bosia/rebuildand reads the real{ ok }result. Default unset → normalbosia devbyte-for-byte unchanged.
- 🟠
packages/bosia/src/core/dev.ts—BOSIA_DEV_MANAGED=1gate; skip thefs.watch+ mtime-poll starts. - 🟠
packages/bosia/src/core/dev.ts—buildAndRestart()returnsPromise<boolean>(real build result). - 🟠
packages/bosia/src/core/dev.ts—POST /__bosia/rebuildsibling of hold/resume, returns{ ok }. - ⚪ Version bump
0.8.2+CHANGELOG.md.
0.8.0 (2026-06-30) — `+loading.svelte` route skeletons
Navigating between pages leaves stale content on screen until the router atomically swaps in the new page+layouts (
App.svelte). Move the per-app skeleton trick into the framework as a Next.js-style convention: a+loading.sveltein a route folder renders automatically during nav, nested inside the layouts shared with the current page. No app-side resolver, nobeforeNavigatewiring.
- 🟠
packages/bosia/src/core/types.ts—PageRoute.loading: string | null. - 🟠
packages/bosia/src/core/scanner.ts— detect sibling+loading.svelte; update conventions header. - 🟠
packages/bosia/src/core/routeFile.ts— emitloadingimport +layoutPathsidentity keys in bothclientRoutesgenerators. - 🟠
packages/bosia/src/core/client/App.svelte— load + render the skeleton on real path changes under the shared-layout prefix; reset on settle. - 🟠
packages/bosia/src/core/server.ts— fallback manifest synthesis includesloading: null. - ⚪
test/scanner.test.ts— detect-vs-null+loading.sveltecase. - ⚪ Docs (en+id):
guides/routing(Loading Skeletons),guides/navigation(cross-ref),project-structuretable,reference/sveltekit-differencesrow. - ⚪ Skills:
bosia-routing(R5b + checklist + source),bosia-navigation(R9 + source). - ⚪ Version bump
0.8.0(framework + docs) + bothCHANGELOG.md. - ⚪ Follow-up: nearest-ancestor
+loading.svelteinheritance (full Next.js segment semantics) — currently per-folder only. - ⚪ Follow-up: warm the
+loading.sveltechunk on hover/viewport inprefetch.tsso the skeleton paints instantly on click. - ⚪ Follow-up: cold-import lag for non-hover nav (touch/keyboard/programmatic) on first visit — skeleton shows after the chunk downloads; self-heals on 2nd visit. Fix only if noticeable: touchstart/focusin warm triggers, or idle warm-all.
0.7.8 (2026-06-27) — Inspector: drop the breadcrumb header
The alt+click comment form showed the framework-stripped component chain (
Button.svelte → +page.svelte) as a header above the textarea. Meaningless noise for non-technical end users (e.g. rukoku's). The AI still gets full file/tree context viabuildContext(el)in the payloadcomment, so the visible header is pure clutter — remove it.
- 🟠
packages/bosia/src/core/plugins/inspector/overlay.ts—openForm()no longer builds thechain/header;form.innerHTMLstarts at the<textarea>.buildContext/submitunchanged, AI payload unaffected. - ⚪ Version bump
0.7.8(framework) +CHANGELOG.md.
0.7.7 (2026-06-23) — Per-response frame-guard opt-out
The framework re-applies
X-Frame-Options: SAMEORIGINto every response after the userhandleruns, so a proxy hub can't serve an embeddable preview even after stripping the upstream header. Add a per-response opt-out so the guard stays on the hub's own pages.
- 🟠
packages/bosia/src/core/hooks.ts— exportNO_FRAME_GUARD_HEADER(internal marker header). - 🟠
packages/bosia/src/core/server.ts— when a response carries the marker, strip it and skip onlyX-Frame-Options(other security headers stay). - ⚪
packages/bosia/src/lib/index.ts— re-exportNO_FRAME_GUARD_HEADERfrom the public API. - ⚪ Version bump
0.7.7(framework) +CHANGELOG.md.
0.7.5 (2026-06-21) — `store` template (Postgres + MinIO/S3)
shopbakes SQLite + empty S3 vars. Add a siblingstoretemplate that defaults to Postgres (nativeBun.SQLviadrizzle-orm/bun-sql, no driver dep) and MinIO/S3 uploads — the production-shaped stack — socreate … --template storeneeds no post-scaffold rewiring.
- 🟠
packages/bosia/templates/store/— copy ofshop;template.jsonall dialectspostgres;.env.examplePostgres URL + MinIO defaults;instructions.txt+README.mdreworded;data/dropped (S3 uploads, no SQLite file). - 🟠
packages/bosia/src/cli/create.ts— registerstoreinTEMPLATE_DESCRIPTIONS. - ⚪ Docs —
getting-startedtemplate table +reference/cli--templateline & bullets (en + id). - ⚪
bosia-shop-templateskill — cover thestoresibling (postgres default, no driver/@aws-sdkdeps). - ⚪ Version bump
0.7.5(framework) /0.7.8(docs).
0.7.4 (2026-06-16) — Navbar block family (18 `navbars/*` blocks + `bosia-navbars` skill)
The registry shipped hero and auth families but no navigation bars. This ports the standalone Navbar Stock design system (18 specimens across standard, themed and app/interactive layouts) into a new
navbars/block category, mirroring the heros port end-to-end. The hardcoded acid-lime accent collapses toprimary; dark bars invert tobg-foreground text-background, glass usesbackdrop-blur-xl. Theme-agnostic semantic tokens only, so each navbar restyles across all 19 themes — no new theme added.
- 🟠 18 navbar blocks under
registry/blocks/navbars/*— standard ×6, themes ×6, app ×6; self-contained<header>sections, inline Tailwind on semantic tokens, inlined primitives. Registered inindex.json. - ⚪ Docs — 3 grouped family pages (
blocks/navbars/{standard,themes,app}) with stacked live previews (3 demos registered); nav gains a Blocks → Navbars group. - ⚪ New
bosia-navbarsdesign skill (catalog of all 18, golden rule, token map, icon list,[[bosia-navigation]]/[[bosia-heros]]cross-links) + skills-index row (count 51 → 52). - 🟠 Stripped the embedded site navbar from all 17
heros/*blocks — page-level blocks must not carry navbar chrome (clashed with layoutnavbars/*). Rule documented inbosia-heros/bosia-block-composeR7/bosia-page-shellR1. - 🟠 Added
navbars/overlay(family now 19) — transparent absolute navbar with light text + outline CTA floating over a full-bleed hero photo. Registered; added to Themes docs +NavbarsThemesDemo; exception documented. - ⚪
CHANGELOG.md— appended under 0.7.4.
0.7.3 (2026-06-15) — Login/auth page family (new `auth/*` pages + 9 blocks + `bosia-login` skill)
The registry shipped a storefront page family but no auth/login pages — only a single
cards/loginblock. This ports the standalone Login Design System (12 themes, centered + split layouts, the full sign-in/up/forgot/magic-link/OTP/SSO screen set) into Bosia as a visual-only page family: 9 reusableauth/*blocks (semantic tokens, brand →primary, status → emerald/amber/destructive, social brand logos keep their official colours) and 6 pages composed from them. One card, two layouts (centered ↔ split) via a one-linevariantswap, mirroring the storefrontpurposepattern. Backend stays inbosia-auth-flow— pages note the pairing.
- 🟠 9 blocks under
registry/blocks/auth/*—auth-shell(centered ↔ split),auth-card,brand,social-row,divider,auth-field,password-strength,otp-input,form-message. Registered inindex.json. - 🟠 6 pages under
registry/pages/auth/{login,register,forgot,magic-link,otp,sso}/— composition only; eachmeta.jsonlists itsauth/*block deps so the installer recurses; defaultvariant="centered", swappable tosplit. - ⚪ Docs — new
blocks/authpage + 6pages/auth/*pages (7 demos registered), Pages overview gains an auth section, nav gains Blocks → Auth and Pages → Auth groups. - ⚪ New
bosia-logindesign skill (catalog of the 6 pages + 9 blocks, token map, voice, centered↔split swap,[[bosia-auth-flow]]pairing) + skills-index row (count 50 → 51). - ⚪
CHANGELOG.md— appended under 0.7.3.
0.7.3 (2026-06-15) — Prebuilt template artifacts for fast `bosia create`
bosia create --template shopwas slow:installFeaturefetched 150+ files serially from GitHub raw. Fix: CI bakes the finished scaffold into a version-locked Release tarball;createdownloads + extracts it. Registry path stays the fallback.
- 🟠
packages/bosia/templates/shop/template.json— opt in with"prebuilt": true(only heavy templates need it;default/demohave notemplate.jsonand are already instant). - 🟠
cli/create.ts— whenprebuilt && !--local && !BOSIA_BUILDING_PREBUILT, downloadv<version>/<template>.tar.gz, extract, substitute{{PROJECT_NAME}}, restore.env; falls back to registry on 404/offline. - 🟠
scripts/build-prebuilt-templates.ts(new) — iteratetemplates/*, skip unlessprebuilt; scaffold with literal{{PROJECT_NAME}}+--no-installvia local registry;tar -czf dist/prebuilt/<t>.tar.gz. - ⚪
packages/bosia/package.json—build:templatesscript; version0.7.3. - ⚪
.github/workflows/publish.yml— Bun setup +bun run build:templates; attachdist/prebuilt/*.tar.gzto the release. - ⚪
.github/workflows/refresh-prebuilt.yml(new) — registry/template changes without a version bump rebuild artifacts andgh release upload --clobberonto the existing release. Keeps the create fast path in sync. - ⚪ Docs —
reference/cli.mddocuments the fast prebuilt download + registry fallback +--no-install.
0.7.2 (2026-06-14) — Brief intake: defer ALL installs to after approval
Bug: during Quick-start intake the AI ran
bosia_add_theme/bosia_add_blockbeforebrief_request_approval, so the Setuju button never appeared. Fix: intake is now capture-only; all installs move to a new step 9 after BRIEF.md is complete.
- 🟠
bosia-brief-intake/SKILL.md— steps 3.3/3.4 now record theme/first-screen choices (no install); new step 9 "Install NOW" runs after status complete; step-5 approval gate hardened; R5 bans installs before complete. - 🟠
bosia-brief-visual/SKILL.md— reworded to "records, doesn't install"; "Workflow side effects" → "Deferred install — NOT during intake" (run by intake step 9); checklist splits recording from the add-theme checks. - 🟠
bosia-brief-platform/SKILL.md— same treatment: description reworded; "Workflow side effects" → "Deferred install — NOT during intake"; format-helper + block scaffolds run in intake step 9; checklist gate annotated. - 🟠
bosia-brief-intake/SKILL.md(+example-brief.md) —## Todoseed now carries TWO items: "Redesign login & register" + new "Replace mock data with real DB integration". Step 6, BRIEF.md template, and example brief updated.
0.7.1 — Inspector: structured AI payload (labeled fields)
The alt+click → "Send to AI" comment now carries labeled fields —
url,pageFile(nearest+page/+layout),componentleaf, cappedtext, framework-strippedtree— so the AI traces props back to the page, not the leaf.
- 🟠
inspector/overlay.ts—isFrameworkFrame/fileOf/userFrames/findPageFile/ownText/buildContexthelpers;submit()sendsbuildContext(el) + "---" + comment; form header shows the framework-stripped chain. - ⚪ Docs —
guides/inspector.mdaiEndpointsection rewritten with the labeled format + whypageFilematters. - ⚪ Skill —
bosia-inspector-edit/SKILL.mdrewritten to the new[Inspector]contract (split on---, default topageFileover thecomponentleaf, useurl/textto disambiguate).
0.7.0 — Deprecated `page.params` fallback
Re-adds a deprecated, working
paramsgetter on thepageobject (bosia/client) so legacy code reaching forpage.paramskeeps working. Returns reactiveappState.routeParams, dev-only warn-once, removed in 1.0.0.$props()stays taught.
- 🟠
page.svelte.ts— addget params()returningappState.routeParams;@deprecatedJSDoc + dev-onlyconsole.warn(warn-once, guarded byNODE_ENV !== "production"so it tree-shakes out of prod). - ⚪ Docs —
guides/routing.mddeprecated callout;reference/roadmap.mdline updated;reference/changelog.mdentry. Skills left as the correct$props()pattern.
Shop template defaults to sqlite-file
Flips the
shoptemplate from PostgreSQL to built-in SQLite (sqlite://./data/app.db) — zero-config, no DB server. Per-feature dialects already supported sqlite; this only changes template defaults plus an ENOENT fix fordata/.
- 🟠
packages/bosia/templates/shop/template.json— all fivefeatureOptionsdialectspostgres→sqlite. - 🟠
packages/bosia/templates/shop/.env.example—DATABASE_URL→sqlite://./data/app.db;instructions.txtwording → SQLite. - 🟠
registry/features/drizzle/drizzle-index.sqlite.ts—mkdirSync(dirname(path))before opening the file (skip:memory:) sodb:migrateagainst./data/app.dbdoesn't ENOENT. - ⚪
_gitignoreignoresdata/*.db*; shipdata/.gitkeep. Docs/skills (bosia-shop-template) updated postgres-default → sqlite-file.
0.6.25 — Port the Mercato storefront (new `page` tier + 24 blocks + clay theme)
Ports the Mercato React storefront. Adds a new
pageregistry tier (page = group of blocks, no backend) viabosia add page. 24storefront/*blocks compose 4 pages sharing one runes cart, mapped to existing themes + a newclaytheme.
- 🟠 New
pageregistry tier + CLI —cli/page.ts(runAddPage), routed inaddRouter.ts(+pages/...alias);pagesadded toManifestandRegistryIndex;bosia add listshows pages; dispatch tests. - 🟠 Harvested 24 sections under
registry/blocks/storefront/*—store(runes cart/favs/drawer + catalogue), header/footer, home sections, product card/grid/drawer, listing + PDP + checkout blocks. Registered inindex.json. - 🟠 Added the
claytheme (registry/themes/clay/) — warm paper neutrals, terracotta primary, soft warm shadows, Newsreader/Hanken Grotesk/Geist Mono fonts; registered inregistry/index.json(19 themes). - 🟠 Four pages under
registry/pages/storefront/{home,listing,product,checkout}/— composition only, one sharedcreateCart()wired through header/grids/drawer; eachmeta.jsonlists its block/ui deps so the installer recurses. - ⚪ Docs — new Pages section + 4 page pages, 6 grouped
blocks/storefront/*pages,themes/clay; 10 demos; nav groups; newbosia-storefrontskill;$lib/blocks/$pagesaliases +registry/pagesTailwind@source.
0.6.24 — Port the Hero Stock hero system (17 blocks)
Ports the Hero Stock React design system (17 full-bleed hero sections across 6 verticals) into a new
heros/block category. Hardcoded accents collapse toprimary; dark photo heroes invert. Semantic tokens only — no new theme.
- 🟠 Ported 17 hero blocks under
registry/blocks/heros/*(commerce ×7, education ×2, food ×2, fashion ×2, services ×2, saas ×2); inline Tailwind on semantic tokens, pickers as local$state, kept Unsplash images. Registered. - ⚪ Docs — 17 per-hero pages (each backed by its
meta.jsonso/api/blockslists each with its own install line); single-hero previews; newbosia-herosskill; nav grouped by vertical; skills-index row.
0.6.24 — Port the Cardstock card system (29 blocks + 6 themes)
Replaces the single
cards/feature-editorialblock (now removed — it painted its numeral withaccent, so it never followed the brand). The new cards map the brand colour toprimaryand use semantic tokens only, so they restyle across themes.
- 🟠 Extended the theme contract on all 12 existing themes — added
--font-monoand theme-scoped elevation (--shadow-xs/sm/md/lgwired through--elevation-*per:root/.dark; midnight gets a faint primary glow). - 🟠 Added 6 Cardstock themes —
paper,carbon(brutalist hard-offset shadows),bloom(diffuse tint),terminal+grape(dark glow),sage; each with light + dark, registered inregistry/index.json(18 themes) + docs pages + nav. - 🟠 Ported 29 card blocks under
registry/blocks/cards/*(data, people, commerce, media, utility, auth); inline Tailwind on semantic tokens, status colours shadcn-style, sparkline/ring/mini-bars as inline SVG. Registered. - 🟠 Removed
registry/blocks/cards/feature-editorial/; re-pointed tests, CLI help, docs demo/page, and thebosia-landing/bosia-saas-landing/bosia-block-composeskills tocards/feature(now takestitle/body/icon/ctaprops). - ⚪ Docs — 6 per-category card pages with live demo galleries + per-card install lines; new
bosia-cardsskill cataloguing all 29.
0.6.24 — Remove the Todo template + feature ahead of rebuilding card blocks
The
todostarter template (and itstodofeature/components) was the original CRUD demo. We're rebuilding the card blocks from scratch, so it's being pulled to clear the way. Theeditorialcard block stays until replacements exist.
- 🟠 Deleted
packages/bosia/templates/todo/,registry/features/todo/,registry/components/todo/, anddocs/content/docs/components/todo/. - 🟠
registry/index.json— droppedtodofromfeaturesand the fourtodo*componentsentries. - 🟠
packages/bosia/src/cli/{create,index,add,feat}.ts— removed thetodotemplate description/example and re-pointed the staletododoc-comment examples at still-present features. - ⚪ Docs — removed the Todo template rows and component pages from
getting-started.md,components/overview.md,reference/cli.md,guides/inspector.md, thenav.tsTodo group, and thebosia-block-composeexample.
0.6.24 — Three-state theme switcher (Light / Dark / System)
Theme switching was binary (light/dark) and the logic was duplicated across four slightly-different places.
- 🟠
core/html.ts— extracted the inline FOUC bootstrap (duplicated 4×) into oneTHEME_INIT_JSconstant; now resolves an explicitsystemvalue live againstprefers-color-scheme, compatible with storeddark/light. - 🟠
navbar.svelte— replaced the buggyisDarkboolean (never persisted, out of sync with FOUC) with a 3-statelight/dark/systemcycle that persists tolocalStorage, syncs the DOM, follows live OS changes. Sun/Moon/Monitor. - 🟠
docs/src/lib/components/ThemeToggle.svelte+docs/src/routes/+layout.svelte— mirror the same 3-state logic and system-aware FOUC script (Monitor inline SVG added). - ⚪
docs/content/docs/guides/styling.md+docs/content/docs/components/ui/navbar.md— document the three modes, storage values, and cycle pattern. - ⚪ Bumped
svelteto^5.56.3in the 5 stalepackage.jsonfiles (docs, 4 templates); root,packages/bosia, andapps/demowere already current.
0.6.24 — `add theme` strips the template's default `:root`/`.dark` block
A theme's
tokens.cssdefines its own:root/.dark, but the template'sapp.cssships a default block earlier in the file. Afteradd themeboth coexisted at the same specificity and the template's won.
- 🟠
templates/{default,shop,demo}/src/app.css— wrapped the default:root/.darktoken block in/* bosia-theme-vars */sentinels so removal targets only the template defaults, never user:rootrules.@theme {}stays above, untouched. - 🟠
cli/theme.ts—runAddThemenow callsstripTemplateThemeVars(appCssPath)after wiring the@import, removing the sentinel-bounded block (idempotent). The installed theme's:rootbecomes the only one, so its tokens win.
0.6.23 (2026-06-11) — Surface yesterday's shop-template bugs in skills + ban the `postgres` npm pkg
0.6.22 shipped the
shoptemplate and patched 5 install-blocking bugs. Three reflect cross-cutting gotchas future authors will hit again, and the shoppackage.jsonstill carried a stalepostgresdep.
- 🟠
core/dev.ts— the proxy's "App server is starting…" 503 is now an HTML page carrying the/__bosia/ssereload client (was bare text/plain), so a live-reload racing into a rebuild auto-recovers instead of sticking. - 🟠
core/dev.ts— reload-hold control:POST /__bosia/holdsuppresses the SSE reload broadcast,POST /__bosia/resumeflushes one reload if any rebuild fired.holddoubles as a heartbeat (90s safety timer) - 🟠
packages/bosia/templates/shop/package.json— drop unused"postgres": "^3.4.0"; scaffold uses Bun-nativedrizzle-orm/bun-sqlagainstBun.SQL, no userland driver needed. - 🟠
bosia-bun-runtime/SKILL.md— new## Postgres — Bun.SQLsection with the object-form snippet + Bun 1.3.xFailedToOpenSocketgotcha; banned-packages table gainspostgres/pgrows pointing atBun.SQL. - 🟠
bosia-database-setup/SKILL.md— reversed the misleading "bun add postgres" line into a "no install" directive cross-linking[[bosia-bun-runtime]]; R5 gains a one-liner about the URL-string gotcha. - 🟠
bosia-drizzle-feature/SKILL.md— new R11 ("Registry files use import paths from their target location") coveringmeta.json#files[].targetdepth, with theauthfeature's 3-up import as the example; P0 gate added. - 🟠
sidebar-menu-item.svelte—hasChildren = $derived(!!children && !href)so leaf items with a{#snippet icon()}body don't render as collapsible parents (Svelte 5 fillschildrenfrom whitespace around named snippets). - 🟠
features/shop/admin-sidebar.svelte— Orders is now a parent groupingAll orders/Pending/Refundsunder oneSidebarMenuItem. Logo<img>tags gainh-5 w-5so the SVG renders at 20px instead of overflowing. - ⚪
bosia-shop-template/SKILL.md— new skill bundling eight rules for extending a shop scaffold: don't re-install features, edit sidebars in place, nopostgres/pg/@aws-sdk, routes under(private)/dashboard/, services notdb.select - 🟠
core/server.ts— API-route handlers now treatthrow redirect(...)/throw error(...)like page actions (303 / typed JSON) instead of a generic 500; also supportsreturn redirect(...). Found via shopPOST /logout. - 🟠
templates/{default,shop,todo,demo}/src/app.css— add@custom-variant dark (&:where(.dark, .dark *));. Tailwind v4 defaultsdark:toprefers-color-scheme, so the navbar'sclassList.toggle("dark")was a no-op. - 🟠
features/shop/admin-sidebar.svelte— header rebuilt perSidebarDemo: theme-aware logo button doubling as a collapse toggle, brand row, working<SidebarTrigger> - 🟠
registry/features/shop/meta.json— add"ui/breadcrumb"tocomponentssobosia create … --template shopactually scaffolds the breadcrumb files. - 🟠
templates/shop/src/routes/(private)/+layout.svelte— derivesegmentsfrompage.url.pathname, render<Breadcrumb>above{@render children()}; last segment becomes<BreadcrumbPage>, earlier ones<BreadcrumbLink>. - 🟠
packages/bosia/templates/shop/src/routes/(public)/+page.svelte— add a<svelte:head>with<title>Welcome to your shop</title>+<meta name="description">so the home page has basic SEO instead of an empty<head>. - 🟠
features/auth/login-page.svelte+register-page.svelte— sibling<meta name="description">inside the existing<svelte:head>(login: "Sign in to your account."; register: "Create your account. First account becomes admin."). - ⚪ CLI-internal bugs (block-deps 404 routing,
--localflag drop, dialect default underskipPrompts:true) deliberately omitted — code-only fixes, no public API to document. - 🟠
cli/block.ts— session-levelinstalledSet mirrorsadd.ts:40;runAddBlockearly-returns when a block is already installed this session. Without itfiles/upload-areawas re-written during shop scaffold (two pullers). - ⚪
registry/features/shop/meta.json— dropui/typography(unused) andui/form/ui/input/ui/button(already declared byauth; component installer dedupes viaadd.ts:116so removal is safe).
0.6.22 (2026-06-10) — `shop` template + `auth` / `rbac` / `shop` registry features
Templates bottomed out at
todo(one CRUD feature), but the most common "build me an app" prompt is a storefront.
- 🟠
registry/features/auth/— multi-dialectuser/sessiontables,Bun.password.hashargon2id. - 🟠
registry/features/rbac/—permissiontable with composite PK,can(userId, resource, action, scope?)with*wildcards,resources.tsregistry,auth-handle.tsrewritten to attachlocals.can(...), seed grants*to the first user. - 🟠
registry/features/shop/— multi-dialectproduct/order/order_item/cart_itemtables,valibotvalidators, repositories + services,PublicNavbar+AdminSidebar,resources.append.tsadds eightshop.*permissions. - 🟠
templates/shop/— thin glue:template.jsondeclaresfeatures: ["auth","rbac","file-upload","shop"] - ⚪
packages/bosia/src/cli/create.ts—TEMPLATE_DESCRIPTIONSmap gainsshop: "Online store starter with auth, RBAC, S3 uploads, products/orders/cart". - ⚪
docs/content/docs/getting-started.md— template table gains ashoprow.
0.6.22 (2026-06-10) — Sidebar docs + skill (fix three AI mis-uses) + `DropdownMenu` floating mode
AI agents hit three sidebar failures: (1) wrapping leaf
SidebarMenuItems inDropdownMenuswallowed thehref; (2) skipping the user footer.
- ⚪
components/ui/sidebar.md— new "Choosing the right item shape" table with a "never wrap inDropdownMenu" callout; new "User Footer" section. - ⚪
bosia-sidebar/SKILL.md— new skill. STOP block names the three failures. R1–R7 cover leaf/parent shape, no-foreign-trigger,iconsnippet,bind:collapsed, user-footer, floating/side/align,trigger="hover". P0 checklist gates. - 🟠
dropdown-menu.svelte— context exposestriggerEl/setTriggerEl. Triggerbind:thissyncs viauntrack - ⚪
SidebarDemo.svelte— leading "Dashboard" leaf withLayoutDashboardicon (R1); user footer rebuilt withDropdownMenu+Avatar,justify-starton the trigger (fixes chevron),floating side="top" align="start"on the content.
0.6.21 (2026-06-09) — Fix three AI-agent app-building failures (block install, EACCES, `page` export)
AI agents hit three failures: (1)
bosia add blocks/cards/feature-editorial404'd (CLI only routedblocksingular); (2)bosia add ui/typographyaborted on EACCES overwriting a foreign-uid file.
- 🟠
packages/bosia/src/cli/addRouter.ts— new dispatcher; routesblocks/<cat>/<name>tokens torunAddBlock, splits mixed batches (components + blocks) into onerunAddcall plus per-block calls. - 🟠
packages/bosia/src/cli/index.ts—addcase callsrouteAddwith injected runners; help text adds the alias. - 🟠
packages/bosia/src/cli/registry.ts— newwriteRegistryFile(dest, content)helper does unlink + retry on EACCES/EPERM, then surfaces a chown hint if still failing. - 🟠
packages/bosia/src/cli/add.tsandblock.ts— component/block file write loops route through the new helper. - 🟠
core/client/page.svelte.ts(new) — reactivepageobject backed by$derivedoverrouter.currentRoute; exposespage.url/page.pathname. Noparamsgetter — Bosia already plumbsparamsinto+page/+layoutvia$props(). - 🟠
packages/bosia/src/lib/client.ts— re-exportpage. - 🟠
docs/content/skills/bosia-block-compose/SKILL.md— canonicalbosia add block cards/feature-editorialexample. - 🟠
docs/content/skills/bosia-saas-landing/SKILL.mdandbosia-landing/SKILL.md— split single install line into per-category calls (theme / components / block). - 🟠
docs/content/skills/bosia-svelte-runes/SKILL.mdR6.5 — illustrative "removed import" example switched frompageto a deletedcnutility so the lesson no longer contradicts the now-realpageexport. - ⚪
packages/bosia/test/registry.test.ts— coverage forrouteAdddispatch (block, alias, mixed batch, multi-block, plain components),readRegistryJSONblocks-category path,writeRegistryFilehappy-path/overwrite. - ⚪
packages/bosia/test/page-store.test.ts(new) — compile-and-wiring checks for thepagemodule (compiles viasvelte/compiler, output referencesderived,bosia/clientre-exportspage).
0.6.21 (2026-06-09) — `bosia-image-dialog` skill + block (multi-image picker)
AI-generated apps seed
images.unsplash.complaceholders but there was no UI primitive to swap them without clobbering. Newfiles/image-dialogblock composesUploadArea+ an External URL tab + an existing-images gallery.
- 🟠
registry/blocks/files/image-dialog/—block.svelte(Dialog + Tabs + selection state, embedsUploadArea, fetches/api/filesonce on mount) andmeta.json(deps: button, dialog, input, label, sonner, tabs, upload-area). - 🟠
registry/index.json— addfiles/image-dialogtoblocks. - ⚪
docs/content/skills/bosia-image-dialog/SKILL.md— workflow steering, P0/P1 checklist, anti-patterns (single-image flows, id-vs-url storage, merge-with-stale-state). - ⚪
docs/content/skills/bosia-file-upload/SKILL.md— cross-reference the new dialog for replace-existing / gallery flows. - ⚪
docs/content/docs/blocks/files/image-dialog.md+docs/src/lib/components/demos/FilesImageDialogDemo.svelteregistered indocs/src/routes/(docs)/[...slug]/+page.svelte. - ⚪
docs/src/lib/docs/nav.ts— insert Image Dialog under Blocks → Files.
0.6.21 (2026-06-09) — Drawer component (mobile bottom-sheet)
Drawer was the last unbuilt Priority-2 overlay. Mobile action sheets had no first-class component.
- 🟠
registry/components/ui/drawer/— 8 svelte sub-components (drawer, content, trigger, close, header, title, description, footer) +index.ts+meta.json - 🟠
registry/index.json— addui/drawer. - 🟠
docs/content/docs/components/ui/drawer.md— usage, props, sub-components, accessibility, Drawer-vs-Dialog guidance. - 🟠
docs/src/lib/components/demos/DrawerDemo.svelte+ register indocs/src/routes/(docs)/[...slug]/+page.svelte. - 🟠
docs/src/lib/docs/nav.ts— insert Drawer entry under UI children. - ⚪
docs/content/skills/bosia-drawer/SKILL.md— workflow steering for AI agents (mobile-first contract, P0/P1 checklist). - ⚪
backup/COMPONENT_PLAN.md— flip Drawer to[x].
0.6.19 (2026-06-08) — `.webmanifest` 404 + stale-build recovery
Two unrelated prod bugs on komba (pure-SSR) after the 0.6.18 deploy. (1)
/site.webmanifest404'd.
- 🟠
packages/bosia/src/core/server/staticServer.ts— add.webmanifestto theisStaticPathwhitelist. - 🟠
packages/bosia/src/core/build.ts:156-170— hash the client entry filename (naming.entry: "[name]-[hash].[ext]") sostaticManifestserves it asimmutableand per-build URL changes invalidate the browser cache automatically. - 🟠
core/client/hydrate.ts— add a production-onlyunhandledrejectionhandler that detects failed dynamicimport()and triggerslocation.replace(?_v=…). Loop-guard viasessionStorage["bosia:reload-attempt"](10s window). - ⚪ Follow-up: surface a stale-build event on the router (
onStaleChunkhook?) so apps can show a soft toast instead of a hard reload — defer until the safety net proves insufficient.
0.6.18 (2026-06-07) — pure-SSR apps still lost `public/` in production containers
Bug: komba (pure SSR, zero prerendered routes) on 0.6.17 —
/bosia-tw.css404'd becausegenerateStaticSite()early-returned whendist/prerendered/didn't exist, sopublic/never mirrored todist/static/. Fix: always mirrorpublic/
- 🟠
packages/bosia/src/core/prerender.ts—generateStaticSite()now copiespublic/unconditionally; SSG-specific copies (prerendered + client mirror) gated ondist/prerendered/existing. - 🟠 Test: build emits
dist/static/withpublic/contents even when no prerendered routes exist.
Same-day addition (2026-06-06) — replace custom `` wrapper with `@lucide/svelte`
The hand-curated
registry/components/ui/icon(95 inline SVG paths) plus 28 components with hardcoded<svg>duplicated work per glyph. Decision: drop the wrapper, import each icon from@lucide/svelte
- 🟠
apps/demo/package.json,docs/package.json— add@lucide/sveltedep. - 🟠
registry/components/ui/icon/— delete; removeui/iconfromregistry/index.json. - 🟠 Migrate 17 registry components (accordion, select, checkbox, pagination, calendar, carousel, sidebar, breadcrumb, command, date-picker, input-otp, radio-group, resizable, nav-menu, combobox, navbar.
- 🟠 Update 3 skill examples (
bosia-dashboard,bosia-mobile-screen,bosia-empty-states) and the docsSidebarDemo. - 🟠
components/ui/icon.mdrewritten as a@lucide/svelteguide with a deprecation callout;overview.md,navbar.md,upload-area.md,crop-image.mdcallouts updated;IconGrid.sveltedeleted. - ⚪ New
docs/content/skills/bosia-icon/skill steering AI agents toward@lucide/svelte(neverlucide-svelte).
Same-day addition (2026-06-06) — production runtime needed `src/app.html`
Bug from komba Dockerfile: runner copies only
dist/. Container boots →renderer.tsreadssrc/app.htmlfrom cwd → missing → throws.
Decision: emit parsed segments to dist/app-html.json during build; renderer reads dist first, falls back to src/app.html for dev/HMR. Zero app changes — dist/ is already in every Docker COPY.
- 🟠
packages/bosia/src/core/appHtml.ts— addwriteAppHtmlSegments(segments, outDir)(serializes to${outDir}/app-html.json);getAppHtmlSegments(cwd)now tries persisted artifact first, falls back toloadAppHtmlTemplate(cwd). - 🟠
packages/bosia/src/core/build.ts— after writing route-manifest, callwriteAppHtmlSegments(appHtml)so production runtime has the segments insidedist/.
0.6.17 (2026-06-07) — production runtime also needed `src/hooks.server.ts`, `bosia.config.ts`, and `public/`
Same komba Dockerfile incident. Once dist had
app-html.json, the app booted but every auth request 303'd to/login:server.tsreadsrc/hooks.server.tsfrom cwd with no fallback →locals.userundefined. Same forbosia.config.ts
Decision: extend the dist-first / src-fallback pattern (used for app-html.json) to hooks, config, and static assets. Build emits self-contained ESM artifacts under dist/
- 🟠
core/build.ts— newbundleRuntimeUserFiles(cwd)step after the server bundle: externalize npm + bosia/elysia/bun/svelte, Bun.buildsrc/hooks.server.ts→dist/hooks.server.jsandbosia.config.*→dist/bosia.config.js - 🟠
packages/bosia/src/core/server.ts— hook loader checks${OUT_DIR}/hooks.server.jsfirst, falls back tosrc/hooks.server.ts. Log line reports which path won. - 🟠
packages/bosia/src/core/config.ts—loadBosiaConfigchecks${OUT_DIR}/bosia.config.jsfirst, dynamic-imports it directly (skips Bun.build at server start); falls back to the existing compile-from-source path for dev. - 🟠
packages/bosia/src/core/staticManifest.ts— walk${outDir}/static/(mirror ofpublic/written by build) so prod images can ship onlydist/.addOncekeepspublic/canonical when both exist (dev double-walk).
Same-day addition (2026-06-04) — `parent()` returns `{}` on client-side JSON nav
Bug from komba:
(await parent()).farmIdworked on SSR but returnedundefinedon client-side nav (14 routes)
Decision: make parent() see the real parent chain even when layer loaders are skipped. Two viable shapes — pick one:
- A (server-side): when handling
/__bosia/data/*.json, accept aparentSnapshotspayload in the POST body (the client already has cached data per skipped layer); merge intoparentDatabefore the page loader. Lowest churn. - B (client-side): when a page loader is selected to re-run and any ancestor layout is skipped, re-run those layouts server-side too. Simpler invariant ("parent() always sees fresh data") but defeats the whole point of selective re-runs.
A is preferred. Plus a P0 doc/skill update so the workaround (locals-based farm/user scope) is the documented pattern even after the framework fix lands, since locals is also better for typed access.
- 🟠 Client: emit
parentSnapshots: Record<depth, data>for skipped layers. Done inApp.svelte(nav) +prefetch.tsvia a sharedbuildParentSnapshotshelper — POST with{ parentSnapshots }only when non-empty, else keep the cacheable GET. - 🟠
packages/bosia/src/core/server.ts—/__bosia/databranch parsesparentSnapshotsfrom the POST body (guarded try/catch → undefined on GET/malformed) and forwards intoloadRouteDatavia a new arg. - 🟠
core/renderer.ts—loadRouteDataacceptsparentSnapshots - ⚪
bosia-routing/SKILL.md— added a rule under R3: don't useparent()for scope identifiers (farmId/orgId/userId), read fromevent.locals;parent()is fine for view-layer data,localsis the source of truth for cross-loader scope. - ⚪ Regression test under
apps/demo: a layout server returning{ orgId }, a child+page.server.tsasserting(await parent()).orgId. Verify SSR AND a client-nav JSON fetch. (Not yet added — needs a client-nav harness.)
Same-day addition (2026-05-30) — file-upload private-by-default + skill rule
0.6.11 made
/uploads/<key>.webpreach+server.ts, but the default handler had no auth/ownership check andfilehad nouserId
- 🟠
registry/features/file-upload/file.{sqlite,pg,mysql}.table.ts— adduser_idNOT NULL (text for sqlite/pg, varchar(36) for mysql). - 🟠
registry/features/file-upload/file.repository.ts—getAllByUser(userId),getByKey(key),getOwned(id, userId),remove(id, userId)— ownership is part of every query. - 🟠
registry/features/file-upload/file.service.ts—upload(file, userId)stores userId;getAll(userId)filters;getByKey(key)exposes the row for the route's ownership check;remove(id, userId)rejects on ownership mismatch. - 🟠
registry/features/file-upload/api-files-server.ts,api-files-id-server.ts,uploads-static-server.ts— all gated onlocals.user; uploads-static responds withContent-Type: record.mime+Cache-Control: private, no-store. - 🟠
bosia-file-upload/SKILL.md— new R5.5 "Files are private by default", anti-patterns (remove auth check, repoint topublic/uploads, dropuser_id), P0 gates on 401 + cross-user 404 curl,[[bosia-auth-flow]]declared a hard prereq.
Same-day addition (2026-05-30) — API routes shadow static fallthrough
Bug from fotoku:
/uploads/<uuid>.webp404'd even with a+server.tsat/uploads/[...path]. Root cause: the static-files block (matches by extension) ran beforeresolveApiMatch, so any static-extension URL short-circuited into static lookup.
- 🟠
packages/bosia/src/core/server.ts— move the API-match block (+server.tsresolution + cache + error handling) above the static-files block and prerender block. No logic change inside the moved block. - ⚪
apps/demo/src/routes/uploads/[...path]/+server.ts,apps/demo/uploads/sample.webp,apps/demo/src/routes/(public)/uploads-test/+page.svelte— regression demo so/uploads-testrenders an image served via the +server.ts handler.
Same-day addition (2026-05-30) — fix `props_id_invalid_placement` in UI components
Svelte 5 requires
$props.id()to be the entire RHS of a top-levelconst. Nine UI components called it inside a template literal (const baseId = \tabs-${$props.id()}`;`), which throws at init.
- 🟠
components/ui/{accordion,collapsible,hover-card,menubar-menu,nav-menu-item,popover,sidebar-menu-item,tabs,tooltip}.svelte
Same-day addition (2026-05-30) — `bosia feat drizzle` defaults to sqlite-file, not in-memory
Bug: AI ran
bosia feat drizzleand gotsqlite://:memory:, losing data on restart. Three drifts:meta.jsonDATABASE_URLwas a comment string with:memory:last; the runtime fallback was:memory:
- 🟠
registry/features/drizzle/meta.json— single concreteDATABASE_URL=sqlite://./data/app.db, no inline comment options. - 🟠
registry/features/drizzle/drizzle-index.ts+drizzle.config.ts— runtime fallback nowsqlite://./data/app.db, notsqlite://:memory:. - 🟠
docs/content/skills/bosia-database-setup/SKILL.md— default scheme updated tosqlite://./data/app.db; references tosrc/features/drizzle/db.tscorrected toindex.ts(actual file shipped by the feature).
Same-day addition (2026-05-30) — UI ids use `$props.id()` instead of `crypto.randomUUID` / `Math.random`
Generated apps crashed with
crypto.randomUUID is not a functionover plain http (LAN IP, preview subdomains)
- 🟠
field,tooltip,popover,hover-card,nav-menu-item,menubar-menu— replacecrypto.randomUUID().slice(0, 8)with$props.id().menubar-menunow prefixes its id withmenubar-for consistency with siblings. - 🟠
registry/components/ui/tabs/tabs.svelte,sidebar/sidebar-menu-item.svelte,accordion/accordion.svelte,collapsible/collapsible.svelte— replaceMath.random().toString(36).slice(2, 10)base ids with$props.id(). - 🟠
registry/components/ui/alert-dialog/alert-dialog.svelte,dialog/dialog.svelte— collapse the two separateMath.random()calls into one$props.id()shared bytitleIdanddescriptionId. - ⚪ Server-side
crypto.randomUUIDinfeatures/file-upload/file.service.tsandfile.{mysql,sqlite}.table.tsleft as-is — runs in Node wherecryptois always available, and those ids persist to the DB rather than per-render.
Same-day addition (2026-05-30) — brief intake: drop DB question + approval-button tool + `bosia-database-setup` skill
Two pain points: (a) AI kept asking follow-up questions after the Quick Start batch (heading said "six", script listed 7, no rule against follow-ups)
- 🟠
bosia-brief-intake/SKILL.md— Quick Start now five questions (palette + aesthetic merged into Q5). Step 5 is the approval gate viabrief_request_approval - 🟠
bosia-brief-intake/references/quick-start-script.md— rewritten as a five-question script with the merged Q5 and an updated inference table. "After answers locked" now references thebrief_request_approvaltool call. - 🟠
bosia-database-setup/SKILL.md— new skill replacingbosia-brief-database. Triggers on user intent ("pakai postgres", "buat tabel"). Workflow A = engine swap; Workflow B = new table/column. R1 keeps sqlite-file the silent default. - 🟠
docs/content/skills/bosia-brief-database/— deleted; superseded bybosia-database-setup. - ⚪
docs/content/skills/SKILL.mdcatalog — count bumped 44 → 45; added abosia-database-setuprow to the "Conventions — framework ·" section. - ⚪
docs/content/skills/bosia-drizzle-usage/SKILL.md+references/troubleshooting.md— swapped everybosia-brief-databasereference tobosia-database-setup.
Same-day addition (2026-05-29) — fix in-page anchor link scroll
Bosia's SPA router intercepted every
<a>click, re-ran the full page load, thenscrollTo(0,0). Hash-only links like<a href="#features">never scrolled becausee.preventDefault()killed the browser default.
- 🟠
core/client/router.svelte.ts— short-circuit same-page hash nav in the click handler: whenanchor.pathname + anchor.searchmatches the current location and a hash is present, skipnavigate() - 🟠
core/client/router.svelte.ts— exportscrollToHash(hash)helper: decodes the fragment, resolvesgetElementById, callsscrollIntoView(), returns whether it found a target. Used by both the click handler andApp.svelte. - 🟠
core/client/App.svelte— replace unconditionalwindow.scrollTo(0,0)after nav settle withtick().then(() => scrollToHash(hash) || scrollTo(0,0))in both paths.
Same-day addition (2026-05-28) — new skills `bosia-page-shell` + `bosia-query-defaults`
AI agents kept (a) re-rendering navbar/footer in every
+page.svelteinstead of+layout.svelte, (b) hand-rolling<table>instead ofui/data-table, (c) shipping repositorylistwith nolimit/offset/orderBy. No skill said otherwise.
- 🟠
bosia-page-shell/SKILL.md— new skill. R1: chrome lives in+layout.svelte, never+page.svelte. R2 layout-depth table. R3 requires(private)/+layout.server.tsto producedata.user. R5 forbids hand-rolled<table> - 🟠
bosia-query-defaults/SKILL.md— new skill. R1 fixes the repo signature tolist(db, { limit, offset, orderBy?, where? })returning{ rows, total }. R2 defaults limit/offset/orderBy. R3 clamps limit ≤100. - ⚪
docs/content/skills/SKILL.md— catalog updated from 42 → 44 skills; new rows added to "Conventions — design ✦" (bosia-page-shell) and "Conventions — framework ·" (bosia-query-defaults).
Same-day addition (2026-05-28) — fix `feat` block install + non-interactive `add block`
AI agent ran
file_upload_installand got a silent 404 (the block path lives underblocks/, notcomponents/). The retry hung on@clack/promptsbecause the MCP runner never closes stdin. Framework fixes only — no per-app patching.
- 🟠
cli/feat.ts—FeatureMetagainsblocks?: string[]. After component install, iteratemeta.blocksand callrunAddBlockso block deps route to the block installer (was 404'ing throughaddComponent - 🟠
cli/block.ts—runAddBlockacceptsInstallOptions, gates the "Replace existing block?" prompt behind!skipPrompts, threadsoptionsinto recursiveaddComponentcalls, honors-y - ⚪
packages/bosia/src/cli/index.ts—add blockdispatch now passes through-y(was filtering to--long flags only). - ⚪
registry/features/file-upload/meta.json— movedfiles/upload-areaandfiles/crop-imagefromcomponentsto the newblocksfield. - 🟡
bosia-file-upload/SKILL.md— R5 now shows the explicitimport UploadArea from "$lib/blocks/files/upload-area/block.svelte"and full props; workflow step 5 gainsdb:generate && db:migrate
Same-day addition (2026-05-28) — fix `file-upload` end-to-end (Bun.Image, image host, route placement)
AI agent scaffolded
bosia feat file-uploadand hit three regressions: (1)Bun.Image.open/decodedoesn't exist, uploads crash; (2)PUBLIC_BASE_URLbakes the wrong host into stored URLs.
- 🔴
features/file-upload/file.service.ts— rewrite the Bun.Image pipeline. Drop thedecodeImageshim. Usenew Bun.Image(bytes), read dims frommetadata(), positionalresize(w, h, opts),.webp({ quality: 85 }).bytes() - 🟠
features/file-upload/storage-local.ts—save()returns relative/uploads/${key}(noPUBLIC_BASE_URLprefix). The browser resolves against the page origin → works forlvh.mepreview, prod domains, and localhost without env tuning. - ⚪
registry/features/file-upload/meta.json— drop misleadingPUBLIC_BASE_URL=http://localhost:3000default (now empty string). (F2) - 🟠
bosia-routing/SKILL.md— new R6 hard rule: authenticated UI MUST live under(private). Anti-pattern block(public)/admin/...❌ vs(private)/admin/...✅. Decision rule + P0 checklist entry. (F3a) - 🟠
docs/content/skills/bosia-dashboard/SKILL.md— STOP rule at top: files under(private)/; create(private)/+layout.server.tsif absent. (F3b) - 🟠
docs/content/skills/bosia-crud-flow/SKILL.md— STOP rule at top: resource routes under(private)/<resource>/...; admin CRUD never(public). (F3c) - 🟡
bosia-bun-runtime/SKILL.md— newBun.Imagesection: constructor,metadata(), positionalresize, per-format encoders (.webp({ quality: 0–100 })),.bytes(). Anti-pattern callout forBun.Image.open/decode. (F4) - ⚪
docs/content/skills/bosia-file-upload/SKILL.md— R2 cross-references the newBun.Imagesection inbosia-bun-runtimefor the API surface. (F5)
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 - 🟡 Single-^C stop — CLI wrappers survive SIGINT and wait for the child (no more orphaned server); dev skips the drain for instant exit; second ^C force-quits (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 needsbetter-sqlite3 - 🟠
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
Docs site + component registry roadmap moved to
docs/ROADMAP.md. Only framework-level (packages/bosia) items remain below.
- 🟠 GitHub Actions for auto-publishing to npm and deploying docs
- 🟡 Dev server auto-restart on crash
- 🟠 SEO infrastructure —
Metadatatype supportslangandlinkfields; dynamic<html lang>;<link>tag rendering in streaming SSR
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 — 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.
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 — without them the CSRF check derives the wrongexpectedOrigin, 403'ing same-origin POST/form actions in dev. DX-only, prod unaffected. - 🟡 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 — shipped 0.8.7
Navigation
- 🟠
beforeNavigate/afterNavigatelifecycle hooks — exported frombosia/client; fired by SPA router around pushState/popstate navs and on full-page unload (willUnload=true); cancel support viacancel()on programmatic navs - 🟠 Scroll restoration and snapshot support (
export const snapshot) — scroll restoration shipped 0.8.6;snapshotshipped 0.8.7
Routing
- 🟠 Layout reset (
[email protected]or[email protected]) - 🟠 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 — share the in-flight loader promise for concurrent identical GET requests instead of running twice. Reworked in 0.3.1 around a folder convention: dedup ON by default keyed on URL only.
- 🔴 Dedup key cross-user data leak — replaced cookie-fingerprint identity with a folder convention. Routes under
(private)skip dedup and run per-request. - 🟡 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 accepted unlimited connections, the likely OOM vector under slow-loris. Add an env-gated soft cap (
MAX_INFLIGHT) reusing the in-flight counter, return 503 when exceeded. Shipped v0.5.13. - 🟡 Response cache + brotli —
Bun.gzipSync()ran on every HTML >2KB with no precompressed cache; no brotli. Add an LRU cache + brotli. Shipped v0.6.0 — skip-render cache keyed on URL + identity hash, per-route opt-out, brotli+gzip per entry. - 🟡 Static-asset fallthrough cost — every static hit called
Bun.file().exists()up to 4×. Build a boot manifest so prod lookups are a Map check. Shipped v0.6.9 —staticManifest.tswalks the dirs once at boot. - 🟡 Collapse SSR
render()calls — rootApp.svelte+ error pages render in separate Svelterender()invocations. Profile under representative load first.
Server Reliability
- 🟠 Process-level error handlers in prod — install
process.on("uncaughtException"/"unhandledRejection")outside the dev inspector path. - 🟡 Structured logging — replace emoji-prefixed
console.log/errorinserver.tswith a level-based logger that emits JSON in prod (pretty in dev) with a request ID. - ⚪ 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[]with astrategyfield:write(default),skip-if-exists,append-line,append-block,merge-json. Replaces the all-or-nothing replace prompt for shared files. - 🟡 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 → shipped asfile-uploadin v0.6.4:bun x bosia feat [-y] file-upload [-d sqlite|postgres|mysql]scaffolds Drizzle metadata + local/S3 adapter +/api/filesPOST (WebP @0.85, fit 1920×1080) - 🟡 Track installed features per project (
bosia.jsonat root, committed) — enablesbosia feat list/add list. Schema{ version, features, components, blocks }keyed by name.
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.tstotemplates/{default,demo,todo}/enablinginspector({ editor: "code" }).copyDircopies it as-is; no 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 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_DIRplus only the codegen files this build owns (routes.ts,routes.client.ts,env.server.ts,env.client.ts,types). No more blanket.bosia/rmSync.
v0.5.7 — `params` as a top-level page/layout prop ✅ (shipped 2026-05-19)
Match SvelteKit:
+page.svelte/+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 in a passive badge inside the running app. "Send to AI" per row reuses the alt-click →
aiEndpointhandoff. Live-only (no buffer/replay), dev-only (prod unaffected).
- 🟠 Server capture: Elysia
.onError()+uncaughtException/unhandledRejectionlisteners installed lazily insidebackend.before().uncaughtExceptionrethrows so crash-recovery still triggers. 500ms dedup prevents render-loop floods. - 🟠 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 the Elysia onError chain in
server.ts: the base 500 responder now registers AFTER theplugin.backend.beforeloop so plugin handlers fire first. - 🟠 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.tsemitssourcemap: "linked"in dev ("none"in prod). Newinspector/sourcemap.tslazy-resolves compiled frames → source via@jridgewell/trace-mapping, only for the clicked error. - 🟡 Last-interaction context: track the most recent
data-bosia-locthe user clicked/keyed and appendLast user interaction: <file>:<line>:<col>to the payload. - 🟡
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}>crashed withRangeError: Maximum call stack size exceededon first render. Svelte's dev output wraps the binding in a namedfunction get(); Bun rewrites$.getto a named importget
- 🔴 Post-process Svelte compile output in
inspector/bun-plugin.tsandsvelteCompiler.tsto rename the innerget/setto$$g/$$s(length-preserving so source-map columns stay accurate). Dev-only — prod uses anonymous arrows. - 🔴 Inject Inspector-extracted component CSS via a runtime
<style>element instead of aloader: "css"virtual module.
v0.5.9 — `src/app.html` template ✅ (shipped 2026-05-20)
SvelteKit-style document shell. Users create
src/app.htmlwith%bosia.head%/%bosia.body%placeholders to control HTML chrome (lang, data attributes, favicon, analytics)
- 🟠
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-cssdocumentingsrc/app.cssorder + the Tailwind v4 / LightningCSS rule: font@import url(...)must come before@import "tailwindcss"or it's silently dropped. Catalog 33 → 34. - 🟡 New CLI command
bosia add font "<Family>" "<url>"(cli/font.ts, reusesmergeFontImports()). Prepends@import url(...)tosrc/app.csswith a/* bosia-font: <Family> */marker so it survives LightningCSS ordering. Idempotent.
v0.5.10 — SvelteKit navigation parity ✅ (shipped 2026-05-20)
Closes the gap between Bosia's client nav API and SvelteKit's
$app/navigation. Apps reached forwindow.location.hrefbecausegoto()wasn't exported (full reload, lost state)
- 🟠
goto(url, opts?)exported frombosia/client. Returns a Promise resolving after the nav settles. HonorsreplaceState,invalidateAll,noScroll - 🟠
beforeNavigate(fn)/afterNavigate(fn)lifecycle hooks.nav.cancel()blocks SPA navigations; popstate cancellation is a no-op since history already advanced. Auto-unregister on destroy viaonDestroy. - 🟠 Router exposes navigation
type("link"|"goto"|"popstate"|"form"|"enter") and theNavigationobject threading into both lifecycle phases. - 🟠
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 blocking the "Go home" link.buildHtml()now gates spinner injection on emptybody— non-streaming SSR skips it; streaming SSR andssr=falsestill get it for the TTFB 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 so AI agents pick the right navigation pattern and use the lifecycle hooks correctly. Catalog index 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 captured runtime errors only. Dev-infra errors — build failures, app crashes,
.envreload failures, port conflicts.
- 🟠
core/dev.tscaptures build/app-crash/dev-uncaught errors into a bounded ring (50 entries, 30s TTL) with a 500ms dedup. Build + app-server stderr piped + tee'd so terminal output is unchanged. - 🟠 New
event: bosia-errorover/__bosia/sse(same wire shape as inspector'sServerError). The SSE handler flushes recent buffered errors to new clients so pre-connect errors stay visible. - 🟠 New
core/dev-error-page.tsrenders the fallback HTML the dev proxy returns whenfetch(app)throws on an HTML nav. Embeds the overlay, pre-seeds buffered errors, subscribes to/__bosia/ssereloadto swap itself out. - 🟡
.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(), layoutload(), pageload(),render(), andBun.gzipSync()even when byte-identical. The new in-memory cache short-circuits all of it.
- 🟠 New
core/cache.ts— tiny LRU +tagIndex+pathIndex,computeCacheKey(url, req, cookies),serveCached(entry, req)withAccept-Encoding: br|gzip|identitynegotiation,buildCompressedVariants()(brotli + gzip), tag/path 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(default 500, 0 disables). Documented inguides/environment-variablesand the response-cache guide (EN+ID). - 🟡 Author guidance — new
bosia-response-cacheskill walks agents through when to callinvalidate()from server code, how to tag loaders withdepends(), and when to opt a route out. - 🟠 Dev proxy now forces the inner app to
Accept-Encoding: identity. Previously it forwardedgzip,br, the inner returned compressed bytes, Bun'sfetch()auto-decoded but leftContent-Encoding: gzip - 🟠
core/cache.tsguardsprocess.envreads — re-exported through the publicbosiabarrel, it evaluated in the browser bundle and threwReferenceError: processon hydration in Safari. - 🟠 Server-only response-cache exports moved to
bosia/server—core/cache.tsstill evaluated client-side via the shared barrel. Added./servertoexports, createdlib/server.ts, removed them from the shared barrel. - 🟡 Inspector dev-error reporter type alignment —
devErrorReport.tsdeclaredsource?: "server"|...butpushServerErroraccepted"elysia"|..., failingbun run check(TS2322)
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 function:import * as Card+<Card.Root>, butindex.tsexportsCard/CardContent, notRoot.bosia buildsucceeded silently.
- 🟠
core/svelteAudit.ts— walks the modern Svelte 5 AST, extracts top-level bindings from<script>, tracks shadowing from{#each}/{#snippet}/{@const}. For namespace imports,Bun.Transpiler.scan()introspects the source's exports. - 🟠
core/svelteCompiler.ts— switchedcompile()tomodernAst: true, wired the audit intoonLoad, added module-scoped per-file dedupe (Map<absPath, Promise>) so it runs once across the parallelbrowser+buntargets. - 🟠 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.tsandinspector/bun-plugin.tsso the audit and the resolver share one alias/tsconfig-paths/relative-path implementation and onelineColFromOffsethelper. - 🟡
BosiaConfig.strictImports(boolean |{ unbound, namespaceMember, warnings }) — per-component opt-out.BOSIA_STRICT_IMPORTS=0env var downgrades to aconsole.warnat runtime without failing the build. - 🟡
test/svelte-audit.test.ts— 8 fixtures cover the repro (missing namespace export), positives (correct member, named import, each-block shadowing, bare-package skip), and edges (unbound identifier, dotted on default import, env override). - 🟡 ConstTag siblings —
{@const Foo = ...}now scopes its binding across the whole surrounding fragment, not just its own children. Previously a sibling-bound<DemoComponent />false-flagged.
v0.6.4 — Combined files demo, CORS-safe ✅ (shipped 2026-05-26)
The crop block's docs demo loaded a remote Unsplash URL with
crossorigin="anonymous"; the browser blocked it as CORS and the cropper rendered blank. Replaced the two demos with one: pick a file viaUploadArea
- 🟡
demos/FilesUploadCropDemo.svelte— single combined demo.UploadArea(enableCrop) → on crop, captures the(file, done)pair, opensCropImageagainstURL.createObjectURL(file), wraps the Blob as aFile, callsdone(file) - 🟡
docs/src/routes/api/demo-upload/+server.ts— tinyPOSTreturning{ url, ok }so the demo Upload button doesn't 500. - ⚪ Both
blocks/files/{crop-image,upload-area}.mdfrontmatterdemo:now points atFilesUploadCropDemo.[...slug]/+page.svelteimports only the new demo; deletedFilesCropImageDemo.svelteandFilesUploadAreaDemo.svelte. - 🟡
blocks/files/crop-image/block.svelte— switched the 400px viewport fromh-[400px]tostyle="height: 400px;". The class works for end-users. - 🟡
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—contentDir/demoFileno longer resolve relative toimport.meta.dir(dev bundle 3 levels deep, prod 2), which missed the content dir in dev → every catch-all docs page 404'd.
Same-day addition (2026-05-26) — `file-upload` feature + CLI dialect flags
The
files/upload-areablock shipped since v0.6.3 but bosia had no server-side counterpart.
- 🟠
registry/features/file-upload/— full backend.file.service.tsvalidates MIME, decodes viaBun.Image, fit-resizes to 1920×1080, re-encodes WebP @0.85, persists via Drizzle. Three dialect table files target one install path. - 🟠
cli/feat.ts— per-feature options system. Top-level handles only-y/--local; everything after the feature name flows toresolveFeatureOptions(), parsed against the feature's ownmeta.jsonoptionsschema. Unknown flags abort. - ⚪
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 so 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. - ⚪
bosia-file-upload/SKILL.md— new skill teaching when to install file-upload (avatar/profile/media-library triggers), R1–R5, workflow, anti-patterns.
v0.6.3 — Skills API exposes references ✅ (shipped 2026-05-25)
AI agents fetching
/api/skills/<name>.jsonsaw theSKILL.mdbody but not the companion reference files that carry the actionable detail, so they guessed paths or scraped the site.
- 🟡
listSkillReferences(name)indocs/src/lib/skills/list.ts— reads<SKILLS_ROOT>/<name>/references/, filters to.md, 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
api/skills/[name]/references/[file]/+server.ts— prerendered,entries()enumerates(name, file)pairs,realpathtraversal guard mirrors the[name]route. Returns{ name, file, path, content }withmax-age=60
Same-day addition (2026-05-25) — Files blocks (crop + upload)
Registry had no file-handling blocks. Ported two from a working CMS: an image cropper and a drag-and-drop upload area. Both installable standalone.
- 🟡
registry/blocks/files/crop-image/— Svelte 5 cropper wrappingsvelte-easy-crop - 🟡
blocks/files/upload-area/— drag-drop + click-to-pick with preview, size validation,XMLHttpRequestprogress,Progressbar. Props:uploadUrl(required),accept,maxSizeMB,fieldName,extraFields,headers,enableCrop - ⚪
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. - 🟡
core/build.ts— addedconditions: ["svelte"]to bothBun.buildcalls so Svelte libs (likesvelte-easy-crop) resolve to theirsvelteexport. An earlier genericonResolvehandler broke shiki's chunked CJS interop.
Same-day addition (2026-05-25) — Clean-architecture skill for generated apps
Bosapi-generated apps put
db.select(...)directly in+page.server.tsloaders and skipped a service/repository layer.
- 🟡 New
bosia-clean-architectureskill — eight rules (R1–R8): nodbin routes, repository ownership, service-owned validation, derived valibot validators, one entity per feature, cross-feature via service namespace, table home. - 🟡 Three companion references —
feature-template.md(copy-adapt for all six files + callers),refactor-recipe.md(grep → extract → swap-import usingwarung-nasi),shared-folder.md(what belongs infeatures/shared/and what doesn't). - 🟡
bosia-drizzle-featureupdated — folder diagram gained*.repository.ts/*.validator.ts/*.dto.ts; R2 split into repository + service with examples; workflow now 9 steps; new anti-patterns and P1 items for the split. - 🟡
bosia-drizzle-usageupdated — Quick Start rewritten so loaders callCatalogService.summary()notdb.select(...); workflow writes the repository first, then service; new red flags fordbin 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.svelteshowed onlyButton.svelte:5:1
- 🟠 Compile-time injection of
<!--bosia:o=...-->/<!--bosia:c-->markers aroundComponent/SvelteComponent/SvelteSelfnodes ininjectLocs - 🟠 Runtime
collectStack(el)walks DOM ancestors + previous siblings with a depth counter matching eachbosia:cto itsbosia:o, so siblings don't bleed. Returns outermost-first. - 🟡 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 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.
Same-day addition (2026-05-23) — Env + CORS skills for AI agents
Bosapi preview apps (
a-<uuid>.lvh.me) surfaced403 Cross-origin request blockedand the AI kept reaching for CORS env vars.
- 🟡 New
bosia-envskill — four-tier prefix (PUBLIC_STATIC_/PUBLIC_/STATIC_/none),$envvirtual module for user vars,process.envfor framework-reserved vars. - 🟡 New
bosia-corsskill — CORS env recipe (CORS_ALLOWED_ORIGINS+ methods/headers/credentials/max-age), theVary: Origininvariant, and a triage table distinguishing a real CORS failure from Bosia's CSRF rejection. - 🟡 Catalog
SKILL.mdupdated 35 → 37 skills; both entries added under framework conventions and into discovery-order step 2; cross-references wired both ways and tobosia-security-review/bosia-elysia-routes.
v0.5.11 — `$types` resolution inside `.svelte` files
tsc --noEmitresolves./$typesfrom.sveltevia therootDirstrick, socheck/buildtype-check correctly. Butsvelte-language-serverdoesn't honorrootDirsin its virtual TS document.Acceptance: in a freshly scaffolded app, hovering
PagePropsin+page.svelteshows the generated type, autocomplete onparams.lists only the route's dynamic segments, and no "module not found" appears for./$types. Same in Zed and VS Code.
- 🟠 Investigate options: (a) a TS Language Service plugin hooking
moduleResolutionfor$typesfrom.sveltefiles; (b) fork/extendsvelte-language-serverconfig. - 🟠 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 crashMultiple files share the same output pathon apps with multiple style-less+page.svelteroutes.
Same-day addition (2026-05-17)
- 🟡
bosia-frontend-design— new design skill forcing an aesthetic stance (direction/typography/dominant colour + sharp accent/one memorable detail) before any UI emit, avoiding the AI-default look. - 🟡
bosia-frontend-designwired intobosia-brief-intakeas step 4, so every BRIEF.md ends with a populated## Aestheticsection. Quick-start opener bumped 5 → 6 questions. - 🟡 Stance consumption wired downstream —
bosia-design-reviewgains a P1 check that each emit honors § Aesthetic without re-picking. Six page scaffolds (landing, saas-landing, blog, pricing, mobile-screen. - 🟡
bosia-brief-intakeships two reference files:references/quick-start-script.md(6-question opener with palette → direction inference) andreferences/example-brief.md(Dombaku-style filled BRIEF.md). Frontmattertargets.filesupdated.
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 one.jsonper route. Fetched body written verbatim. - 🟡 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. - 🟡 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 were absorbing the.jsonsuffix into their rest-segment param, causing 4xx in dev. - 🔴 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; list endpoints expose their array. - 🟡 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 (
createHighlighternot a function). Lazyawait import("shiki")made Bun's splitter call into the parent before its exports initialized. - 🟡 Normalize
pathon/api/skills,/api/components,/api/blocksindex + detail responses to the full detail URL (e.g./api/components/ui/button.json)
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 reachingapp.cssproduces an identical CSS sidecar → Bun fails with "Multiple files share the same output path" - 🟡 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)
Open
- 🟠 Truly progressive SSR streaming —
renderSSRStreamis blocking before first byte (load → render → enqueue). The real blocker is a parallel-aware loader runner that flushes chunks as each loader resolves. - 🟡 Reduce
safeJsonStringifycost on large loader payloads — done in v0.5.0 by moving page/layout/form data to<script type="application/json">islands read viaJSON.parse(...textContent)
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 - 🟢 Six existing themes wired into sidebar nav + skill metadata (zinc, stone, claude, ocean, forest, rose) — v0.6.22 (2026-06-10)
- 🟢 Four new themes added: sunset (warm orange), midnight (indigo dark-first), mono (brutalist), amber (cozy hospitality) — v0.6.22 (2026-06-10)
- 🟠
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 (deferred)
Status (2026-07-04): deferred pending Bun upstream CSS-chunk dedup under
splitting: true(issue to file — see 0.8.5). Hashed delivery shipped in 0.8.5. Per-route Tailwind chunks rejected: Tailwind v4 emits one deduplicated utilities file by design; splitting would duplicate shared utilities and ship more bytes. Component styles are already scoped + code-split viacss: "injected". Acceptance items below stay open under the upstream dependency.
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)