{"_id":"@agberohq/oja","_rev":"11-cb49cec83fb5d766238c02531b26ee5e","name":"@agberohq/oja","dist-tags":{"latest":"0.3.0"},"versions":{"0.0.6":{"name":"@agberohq/oja","version":"0.0.6","keywords":["spa","framework","router","zero-build","vanilla-js","template","reactive"],"author":{"name":"Agbero HQ","email":"https://github.com/agberohq"},"license":"MIT","_id":"@agberohq/oja@0.0.6","maintainers":[{"name":"agbero","email":"agbero.hq@gmail.com"}],"homepage":"https://github.com/agberohq/oja","bugs":{"url":"https://github.com/agberohq/oja/issues"},"dist":{"shasum":"26a48a928abeb7df80fb7fe2573f5d7fe6b41cea","tarball":"https://registry.npmjs.org/@agberohq/oja/-/oja-0.0.6.tgz","fileCount":48,"integrity":"sha512-KNEGdy2L4RoIkvJTppULSTKHrvj6MAFQITic3NY2tIWitXIZx3u3NdZbALng2ZUBTmZ6bv8Y4JwSXZxPvaznyw==","signatures":[{"sig":"MEUCIFeX2YnMNJvMPOjdBE04a/u9BIl2cvWoz5fNCb18PVdIAiEAwWk4WO7i3sxOh+KR9qbQmNPrIbJ4PvD8i7Lf/SP4+ac=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":1346953},"main":"build/oja.min.js","type":"module","module":"build/oja.esm.js","exports":{".":{"import":"./build/oja.esm.js","default":"./build/oja.min.js"},"./src/*":"./src/*","./src/js/*":"./src/js/*","./src/js/codecs/*":"./src/js/codecs/*"},"gitHead":"1036e6c30b7d78dbb810357e070c3459167d3056","scripts":{"test":"vitest run","build":"make all","check":"make check","clean":"make clean","watch":"make watch","test:watch":"vitest","test:coverage":"vitest run --coverage"},"_npmUser":{"name":"agbero","email":"agbero.hq@gmail.com"},"repository":{"url":"git+https://github.com/agberohq/oja.git","type":"git"},"_npmVersion":"10.8.2","description":"Minimal zero-build JavaScript framework for multi-app SPAs","directories":{},"_nodeVersion":"20.20.1","_hasShrinkwrap":false,"devDependencies":{"jsdom":"latest","vitest":"latest","esbuild":"latest","clean-css-cli":"latest","@vitest/coverage-v8":"latest","google-closure-compiler":"^20260316.0.0"},"_npmOperationalInternal":{"tmp":"tmp/oja_0.0.6_1774003672254_0.1635094142284692","host":"s3://npm-registry-packages-npm-production"}},"0.0.8":{"name":"@agberohq/oja","version":"0.0.8","keywords":["spa","framework","router","zero-build","vanilla-js","template","reactive"],"author":{"name":"Agbero HQ","email":"https://github.com/agberohq"},"license":"MIT","_id":"@agberohq/oja@0.0.8","maintainers":[{"name":"agbero","email":"agbero.hq@gmail.com"}],"homepage":"https://github.com/agberohq/oja","bugs":{"url":"https://github.com/agberohq/oja/issues"},"dist":{"shasum":"0d6bf3d76ff0903e44ebe45bb5a04212aafdb810","tarball":"https://registry.npmjs.org/@agberohq/oja/-/oja-0.0.8.tgz","fileCount":52,"integrity":"sha512-PebVNfcjUx2/WbrpCbRiuquEcHqSqFmv2gqcT/SylxoLRYI22h1wTBGFuuPunwZDYq7IKcZW774WntkxCnXLvw==","signatures":[{"sig":"MEQCIFz0zLZx2A+adTuauBNIbYhaU/bLmZdOhSY00y25R2QFAiB5SfAyoQI7L8H4+rUEzwyNeNlLmENJrfkyM7WZtRkHBw==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":1486770},"main":"build/oja.min.js","type":"module","module":"build/oja.esm.js","exports":{".":{"import":"./build/oja.esm.js","default":"./build/oja.min.js"},"./src/*":"./src/*","./src/js/*":"./src/js/*","./src/js/codecs/*":"./src/js/codecs/*"},"gitHead":"00fee66b0aba65948f3721e9efd42ecd2a8b542f","scripts":{"test":"vitest run","build":"make all","check":"make check","clean":"make clean","watch":"make watch","test:watch":"vitest","test:coverage":"vitest run --coverage"},"_npmUser":{"name":"agbero","email":"agbero.hq@gmail.com"},"repository":{"url":"git+https://github.com/agberohq/oja.git","type":"git"},"_npmVersion":"10.8.2","description":"Minimal zero-build JavaScript framework for multi-app SPAs","directories":{},"_nodeVersion":"20.20.1","_hasShrinkwrap":false,"devDependencies":{"jsdom":"latest","vitest":"latest","esbuild":"latest","clean-css-cli":"latest","@vitest/coverage-v8":"latest","google-closure-compiler":"^20260316.0.0"},"_npmOperationalInternal":{"tmp":"tmp/oja_0.0.8_1774023600638_0.46526993620099244","host":"s3://npm-registry-packages-npm-production"}},"0.0.9":{"name":"@agberohq/oja","version":"0.0.9","keywords":["spa","framework","router","zero-build","vanilla-js","template","reactive"],"author":{"name":"Agbero HQ","email":"https://github.com/agberohq"},"license":"MIT","_id":"@agberohq/oja@0.0.9","maintainers":[{"name":"agbero","email":"agbero.hq@gmail.com"}],"homepage":"https://github.com/agberohq/oja","bugs":{"url":"https://github.com/agberohq/oja/issues"},"dist":{"shasum":"13ccbd602c69b34208d32ee77bdde4983e903892","tarball":"https://registry.npmjs.org/@agberohq/oja/-/oja-0.0.9.tgz","fileCount":53,"integrity":"sha512-IJN6uRwWLzpqzD4rZDxrvF7Truvp7Bnnm3URiA9w1hNQR1FKs78tWx9pAN5N/rF/OJlSAuynSPz4yPAZsRPdQA==","signatures":[{"sig":"MEUCIQDh9rEBLwIW4Ab7InY7K4EpE1fx/dvGyXxe3FL8xQ9j6AIgCP6ZCaE4BCH4MXmVwmjN+JOuNFVFu3qgHUKDTU5w5fk=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":1515388},"main":"build/oja.min.js","type":"module","module":"build/oja.esm.js","exports":{".":{"import":"./build/oja.esm.js","default":"./build/oja.min.js"},"./src/*":"./src/*","./src/js/*":"./src/js/*","./src/js/codecs/*":"./src/js/codecs/*"},"gitHead":"061429363a967543c400b03ffcaf6a2a3371184c","scripts":{"test":"vitest run","build":"make all","check":"make check","clean":"make clean","watch":"make watch","test:watch":"vitest","test:coverage":"vitest run --coverage"},"_npmUser":{"name":"agbero","email":"agbero.hq@gmail.com"},"repository":{"url":"git+https://github.com/agberohq/oja.git","type":"git"},"_npmVersion":"10.8.2","description":"Minimal zero-build JavaScript framework for multi-app SPAs","directories":{},"_nodeVersion":"20.20.1","_hasShrinkwrap":false,"devDependencies":{"jsdom":"latest","vitest":"latest","esbuild":"latest","clean-css-cli":"latest","@vitest/coverage-v8":"latest","google-closure-compiler":"^20260316.0.0"},"_npmOperationalInternal":{"tmp":"tmp/oja_0.0.9_1774054904597_0.5985426691309783","host":"s3://npm-registry-packages-npm-production"}},"0.0.10":{"name":"@agberohq/oja","version":"0.0.10","keywords":["spa","framework","router","zero-build","vanilla-js","template","reactive"],"author":{"name":"Agbero HQ","email":"https://github.com/agberohq"},"license":"MIT","_id":"@agberohq/oja@0.0.10","maintainers":[{"name":"agbero","email":"agbero.hq@gmail.com"}],"homepage":"https://github.com/agberohq/oja","bugs":{"url":"https://github.com/agberohq/oja/issues"},"dist":{"shasum":"8bb9620b588fce380a5e17d375a52739b141b51a","tarball":"https://registry.npmjs.org/@agberohq/oja/-/oja-0.0.10.tgz","fileCount":62,"integrity":"sha512-EQo5sZmdn+OngIabVKX3N/DIksSaGktSnbpvcdT/zjp3FGhD5n3fzyvskxjERH9B8AUQDxtvWC0eya+bp9dgnA==","signatures":[{"sig":"MEYCIQCJdmCwyPHSejwDDqooYJAPgtLLom5aPyU9VZmWoWXhYwIhAJhM7iYDBA3YQegpbxfU6OjuW23ra+0hBJy9D0ZBm6Pz","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":1848154},"main":"build/oja.min.js","type":"module","module":"build/oja.esm.js","exports":{".":{"import":"./build/oja.esm.js","default":"./build/oja.min.js"},"./src/*":"./src/*","./src/js/*":"./src/js/*","./src/js/codecs/*":"./src/js/codecs/*"},"gitHead":"8f63798f07ec5de95368a82a3e9ca2e6ee93bdcd","scripts":{"test":"vitest run","build":"make all","check":"make check","clean":"make clean","watch":"make watch","test:watch":"vitest","test:coverage":"vitest run --coverage"},"_npmUser":{"name":"agbero","email":"agbero.hq@gmail.com"},"repository":{"url":"git+https://github.com/agberohq/oja.git","type":"git"},"_npmVersion":"10.8.2","description":"Minimal zero-build JavaScript framework for multi-app SPAs","directories":{},"_nodeVersion":"20.20.1","_hasShrinkwrap":false,"devDependencies":{"jsdom":"latest","vitest":"latest","esbuild":"latest","clean-css-cli":"latest","@vitest/coverage-v8":"latest","google-closure-compiler":"^20260316.0.0"},"_npmOperationalInternal":{"tmp":"tmp/oja_0.0.10_1774121164335_0.5461664926632006","host":"s3://npm-registry-packages-npm-production"}},"0.0.11":{"name":"@agberohq/oja","version":"0.0.11","keywords":["spa","framework","router","zero-build","vanilla-js","template","reactive"],"author":{"name":"Agbero HQ","email":"https://github.com/agberohq"},"license":"MIT","_id":"@agberohq/oja@0.0.11","maintainers":[{"name":"agbero","email":"agbero.hq@gmail.com"}],"homepage":"https://github.com/agberohq/oja","bugs":{"url":"https://github.com/agberohq/oja/issues"},"dist":{"shasum":"ccd1db1c6b7f0a64345b1d2ef34c43f7672c6460","tarball":"https://registry.npmjs.org/@agberohq/oja/-/oja-0.0.11.tgz","fileCount":64,"integrity":"sha512-2lqRz0sFSO8lcQrGW6GN43Kw7hZq38rgyC8YR8wiIPqzWoqjg5qXeRTnZNwcgv7UP9x3oMWziDSjif85BH7+Bw==","signatures":[{"sig":"MEYCIQDDmlSHt3UMMjze9Jen5HAj7SWSachMDurwCfPkm3NW5QIhAPPpK/KphRizEk/Zavdd3SCdfcNogHlMdlKDEwoVPw9H","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":1764813},"main":"build/oja.min.js","type":"module","module":"build/oja.esm.js","exports":{".":{"import":"./build/oja.esm.js","default":"./build/oja.min.js"},"./src/*":"./src/*","./src/js/*":"./src/js/*","./src/js/codecs/*":"./src/js/codecs/*"},"gitHead":"962d32f0ceff71ed7a78de32344ea7496101eb13","scripts":{"test":"vitest run","build":"make all","check":"make check","clean":"make clean","watch":"make watch","test:watch":"vitest","test:coverage":"vitest run --coverage"},"_npmUser":{"name":"agbero","email":"agbero.hq@gmail.com"},"repository":{"url":"git+https://github.com/agberohq/oja.git","type":"git"},"_npmVersion":"10.8.2","description":"Minimal zero-build JavaScript framework for multi-app SPAs","directories":{},"_nodeVersion":"20.20.1","_hasShrinkwrap":false,"devDependencies":{"jsdom":"latest","vitest":"latest","esbuild":"latest","clean-css-cli":"latest","@vitest/coverage-v8":"latest","google-closure-compiler":"^20260316.0.0"},"_npmOperationalInternal":{"tmp":"tmp/oja_0.0.11_1774219501906_0.21993263746475122","host":"s3://npm-registry-packages-npm-production"}},"0.1.0":{"name":"@agberohq/oja","version":"0.1.0","keywords":["spa","framework","router","zero-build","vanilla-js","template","reactive"],"author":{"name":"Agbero HQ","email":"https://github.com/agberohq"},"license":"MIT","_id":"@agberohq/oja@0.1.0","maintainers":[{"name":"agbero","email":"agbero.hq@gmail.com"}],"homepage":"https://github.com/agberohq/oja","bugs":{"url":"https://github.com/agberohq/oja/issues"},"dist":{"shasum":"2531f0f895c159226aa1eccc7814c449c76da401","tarball":"https://registry.npmjs.org/@agberohq/oja/-/oja-0.1.0.tgz","fileCount":71,"integrity":"sha512-9wA7W9h7dL9kxDM5Jsm9woCHccca3u/EnYNeXAmL+SsPelsnNMFMnKBDQyfEXqTvDYPV+e30k9JrLfN6DUg+nw==","signatures":[{"sig":"MEQCIEvJqBuJdKG8q2j2pF3A4QUWHDRQ8+mMCpEif+psjEr5AiAHdc09TU8AMRxJuFsxuDoUPqUmDggyDQJINPy06N8pTQ==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":1966862},"main":"build/oja.min.js","type":"module","module":"build/oja.esm.js","exports":{".":{"import":"./build/oja.esm.js","default":"./build/oja.min.js"},"./src/*":"./src/*","./src/js/*":"./src/js/*","./src/js/codecs/*":"./src/js/codecs/*"},"gitHead":"786508a8fd56f4bc4f52071a3d69d7a01d5415bc","scripts":{"test":"vitest run","build":"make all","check":"make check","clean":"make clean","watch":"make watch","test:watch":"vitest","test:coverage":"vitest run --coverage"},"_npmUser":{"name":"agbero","email":"agbero.hq@gmail.com"},"repository":{"url":"git+https://github.com/agberohq/oja.git","type":"git"},"_npmVersion":"10.9.4","description":"Minimal zero-build JavaScript framework for multi-app SPAs","directories":{},"_nodeVersion":"22.22.1","_hasShrinkwrap":false,"devDependencies":{"jsdom":"latest","vitest":"latest","esbuild":"latest","clean-css-cli":"latest","@vitest/coverage-v8":"latest","google-closure-compiler":"^20260316.0.0"},"_npmOperationalInternal":{"tmp":"tmp/oja_0.1.0_1774380417403_0.7864631324424716","host":"s3://npm-registry-packages-npm-production"}},"0.2.0":{"name":"@agberohq/oja","version":"0.2.0","keywords":["spa","framework","router","zero-build","vanilla-js","template","reactive"],"author":{"name":"Agbero HQ","email":"https://github.com/agberohq"},"license":"MIT","_id":"@agberohq/oja@0.2.0","maintainers":[{"name":"agbero","email":"agbero.hq@gmail.com"}],"homepage":"https://github.com/agberohq/oja","bugs":{"url":"https://github.com/agberohq/oja/issues"},"dist":{"shasum":"d5dc13b2410f8dad69789de4d91f8efa41758d8d","tarball":"https://registry.npmjs.org/@agberohq/oja/-/oja-0.2.0.tgz","fileCount":76,"integrity":"sha512-BVa0+uLD8mBP/vLO+N/vMeST+/2a6ik0JiRSHa+yFlhHHJwysHU0eC1OHFvPXXwdddRW3acEz5o8NuWbQLP7OA==","signatures":[{"sig":"MEQCICRZZo9HNzofHYV5GJ+qB2SZgUXFX+ZG+ecAv7UQJopKAiAP/PznmYO9KtvKjcN+DEfWBHVu14osc1hRaekmmU2oiQ==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":2241380},"main":"build/oja.min.js","type":"module","module":"build/oja.esm.js","exports":{".":{"import":"./build/oja.esm.js","default":"./build/oja.min.js"},"./src/*":"./src/*","./src/js/*":"./src/js/*","./src/js/codecs/*":"./src/js/codecs/*"},"gitHead":"8f1fefac86fb2c4bfa339871e5544d07d317c3e2","scripts":{"test":"vitest run","bench":"vitest bench --config vitest.bench.config.js","build":"make all","check":"make check","clean":"make clean","watch":"make watch","bench:run":"vitest bench --run --config vitest.bench.config.js","test:watch":"vitest","test:coverage":"vitest run --coverage"},"_npmUser":{"name":"agbero","email":"agbero.hq@gmail.com"},"repository":{"url":"git+https://github.com/agberohq/oja.git","type":"git"},"_npmVersion":"10.9.7","description":"Minimal zero-build JavaScript framework for multi-app SPAs","directories":{},"_nodeVersion":"22.22.2","_hasShrinkwrap":false,"devDependencies":{"jsdom":"latest","vitest":"latest","esbuild":"latest","clean-css-cli":"latest","@vitest/coverage-v8":"latest","google-closure-compiler":"^20260316.0.0"},"_npmOperationalInternal":{"tmp":"tmp/oja_0.2.0_1775206321365_0.9960508807026704","host":"s3://npm-registry-packages-npm-production"}},"0.2.1":{"name":"@agberohq/oja","version":"0.2.1","keywords":["spa","framework","router","zero-build","vanilla-js","template","reactive"],"author":{"name":"Agbero HQ","email":"https://github.com/agberohq"},"license":"MIT","_id":"@agberohq/oja@0.2.1","maintainers":[{"name":"agbero","email":"agbero.hq@gmail.com"}],"homepage":"https://github.com/agberohq/oja","bugs":{"url":"https://github.com/agberohq/oja/issues"},"dist":{"shasum":"a568d43e53009ff7e8d4a0ba1758c92cc354795e","tarball":"https://registry.npmjs.org/@agberohq/oja/-/oja-0.2.1.tgz","fileCount":76,"integrity":"sha512-xPe+BIDBcPODzslik6rA2fX85CUEL0JWt8CLGxsKP0fFHXOxLMAJ5Zr+Yjxe7y6DuznJINiFsG4WyLsMKQhdTA==","signatures":[{"sig":"MEUCIFMfdB7cf7fElsEtZpR1EA40cnWrol0YdKE80qBVa1VaAiEArj+nXGtMMC5sIbEAAj3GuPJBUKVGEd9zPgI3qNLM5GA=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":2255434},"main":"build/oja.min.js","type":"module","module":"build/oja.esm.js","exports":{".":{"import":"./build/oja.esm.js","default":"./build/oja.min.js"},"./src/*":"./src/*","./src/js/*":"./src/js/*","./src/js/codecs/*":"./src/js/codecs/*"},"gitHead":"821305ab0ac43ad98b6c433b9c708f895f87ed0d","scripts":{"test":"vitest run","bench":"vitest bench --config vitest.bench.config.js","build":"make all","check":"make check","clean":"make clean","watch":"make watch","bench:run":"vitest bench --run --config vitest.bench.config.js","test:watch":"vitest","test:coverage":"vitest run --coverage"},"_npmUser":{"name":"agbero","email":"agbero.hq@gmail.com"},"repository":{"url":"git+https://github.com/agberohq/oja.git","type":"git"},"_npmVersion":"10.9.4","description":"Minimal zero-build JavaScript framework for multi-app SPAs","directories":{},"_nodeVersion":"22.22.1","_hasShrinkwrap":false,"devDependencies":{"jsdom":"latest","vitest":"latest","esbuild":"latest","clean-css-cli":"latest","@vitest/coverage-v8":"latest","google-closure-compiler":"^20260316.0.0"},"_npmOperationalInternal":{"tmp":"tmp/oja_0.2.1_1775217763793_0.3503095767379183","host":"s3://npm-registry-packages-npm-production"}},"0.2.2":{"name":"@agberohq/oja","version":"0.2.2","keywords":["spa","framework","router","zero-build","vanilla-js","template","reactive"],"author":{"name":"Agbero HQ","email":"https://github.com/agberohq"},"license":"MIT","_id":"@agberohq/oja@0.2.2","maintainers":[{"name":"agbero","email":"agbero.hq@gmail.com"}],"homepage":"https://github.com/agberohq/oja","bugs":{"url":"https://github.com/agberohq/oja/issues"},"dist":{"shasum":"b22662c9b6ec3430e1bf9a522aef9b6ab3f21ec6","tarball":"https://registry.npmjs.org/@agberohq/oja/-/oja-0.2.2.tgz","fileCount":76,"integrity":"sha512-h6cZpWxjUEJMVJEJ9yCJHiDCcymsuRW2/LVQu/7p86gZbGgd+9+VC7WwOeVBnnLpnlkPuKIVTRoLgzhoAgHnIw==","signatures":[{"sig":"MEUCIQC0B31UuJSL1gAu+Asnb/QXca2JJuVnlVSbvKEv5ws6oAIgeLlhB4bUcKZS+tpX6D0bQ06qoxBUNhKVM9BDqs18jnw=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":2255465},"main":"build/oja.min.js","type":"module","module":"build/oja.esm.js","exports":{".":{"import":"./build/oja.esm.js","default":"./build/oja.min.js"},"./src/*":"./src/*","./src/js/*":"./src/js/*","./src/js/codecs/*":"./src/js/codecs/*"},"gitHead":"fd89498d95b128e4ce7fff5d5b4be734f8c9f60f","scripts":{"test":"vitest run","bench":"vitest bench --config vitest.bench.config.js","build":"make all","check":"make check","clean":"make clean","watch":"make watch","bench:run":"vitest bench --run --config vitest.bench.config.js","test:watch":"vitest","test:coverage":"vitest run --coverage"},"_npmUser":{"name":"agbero","email":"agbero.hq@gmail.com"},"repository":{"url":"git+https://github.com/agberohq/oja.git","type":"git"},"_npmVersion":"10.9.4","description":"Minimal zero-build JavaScript framework for multi-app SPAs","directories":{},"_nodeVersion":"22.22.1","_hasShrinkwrap":false,"devDependencies":{"jsdom":"latest","vitest":"latest","esbuild":"latest","clean-css-cli":"latest","@vitest/coverage-v8":"latest","google-closure-compiler":"^20260316.0.0"},"_npmOperationalInternal":{"tmp":"tmp/oja_0.2.2_1775225263051_0.06696635980657661","host":"s3://npm-registry-packages-npm-production"}},"0.2.3":{"name":"@agberohq/oja","version":"0.2.3","keywords":["spa","framework","router","zero-build","vanilla-js","template","reactive"],"author":{"name":"Agbero HQ","email":"https://github.com/agberohq"},"license":"MIT","_id":"@agberohq/oja@0.2.3","maintainers":[{"name":"agbero","email":"agbero.hq@gmail.com"}],"homepage":"https://github.com/agberohq/oja","bugs":{"url":"https://github.com/agberohq/oja/issues"},"dist":{"shasum":"119febbc3f1d5bc0840d2454dc18c6fa0168f98c","tarball":"https://registry.npmjs.org/@agberohq/oja/-/oja-0.2.3.tgz","fileCount":76,"integrity":"sha512-K/8gVMJtG2hMveId29sTZ/AtqInJ9qfO5Iac3Pu/VsD5yC56YSjILAXYWbKFEcWuEKS1ln8Sm2NDjegXLJve9g==","signatures":[{"sig":"MEUCID+Yfbl5OwLjZQsZUT4wCiA3jnqdDi88Xa3NnlkVdn4/AiEAoaZVHDqW57s/l+n7BG8sqQUObW964DzN2RMTAxa4U/4=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":2275095},"main":"build/oja.min.js","type":"module","module":"build/oja.esm.js","exports":{".":{"import":"./build/oja.esm.js","default":"./build/oja.min.js"},"./src/*":"./src/*","./src/js/*":"./src/js/*","./src/js/codecs/*":"./src/js/codecs/*"},"gitHead":"da013589197e4078985434f5a811d95641bbc191","scripts":{"test":"vitest run","bench":"vitest bench --config vitest.bench.config.js","build":"make all","check":"make check","clean":"make clean","watch":"make watch","bench:run":"vitest bench --run --config vitest.bench.config.js","test:watch":"vitest","test:coverage":"vitest run --coverage"},"_npmUser":{"name":"agbero","email":"agbero.hq@gmail.com"},"repository":{"url":"git+https://github.com/agberohq/oja.git","type":"git"},"_npmVersion":"10.9.7","description":"Minimal zero-build JavaScript framework for multi-app SPAs","directories":{},"_nodeVersion":"22.22.2","_hasShrinkwrap":false,"devDependencies":{"jsdom":"latest","vitest":"latest","esbuild":"latest","clean-css-cli":"latest","@vitest/coverage-v8":"latest","google-closure-compiler":"^20260316.0.0"},"_npmOperationalInternal":{"tmp":"tmp/oja_0.2.3_1775328727907_0.22269891296340427","host":"s3://npm-registry-packages-npm-production"}},"0.3.0":{"name":"@agberohq/oja","version":"0.3.0","description":"Minimal zero-build JavaScript framework for multi-app SPAs","author":{"name":"Agbero HQ","email":"https://github.com/agberohq"},"license":"MIT","homepage":"https://github.com/agberohq/oja","repository":{"type":"git","url":"git+https://github.com/agberohq/oja.git"},"keywords":["spa","framework","router","zero-build","vanilla-js","template","reactive"],"type":"module","main":"build/oja.min.js","module":"build/oja.esm.js","exports":{".":{"import":"./build/oja.esm.js","default":"./build/oja.min.js"},"./src/*":"./src/*","./src/js/*":"./src/js/*","./src/js/codecs/*":"./src/js/codecs/*"},"scripts":{"build":"make all","watch":"make watch","check":"make check","clean":"make clean","test":"vitest run","test:watch":"vitest","test:coverage":"vitest run --coverage","bench":"vitest bench --config vitest.bench.config.js","bench:run":"vitest bench --run --config vitest.bench.config.js"},"devDependencies":{"@vitest/coverage-v8":"latest","clean-css-cli":"latest","esbuild":"latest","google-closure-compiler":"^20260316.0.0","jsdom":"latest","vitest":"latest"},"_id":"@agberohq/oja@0.3.0","gitHead":"d8fdb33cd95d4bb8b212b9769f58f96ba878fb65","bugs":{"url":"https://github.com/agberohq/oja/issues"},"_nodeVersion":"22.22.2","_npmVersion":"10.9.7","dist":{"integrity":"sha512-NpmBbTrmJh7/9uzfbvykE536sWGFW1/vajbpSoNqRHdHVf5K3g8gyUZlH2V0uJ7GdfweYGHugfQP7JN02nhc+g==","shasum":"03bc525d2ac78a1f5f655c94040cefb41ce8a3fb","tarball":"https://registry.npmjs.org/@agberohq/oja/-/oja-0.3.0.tgz","fileCount":86,"unpackedSize":2609552,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQCoO2SgZXopfiJPEpEllCkkv/PfKHx3fQlNU+Etlyo8fgIgSUxV443TKbcmuADTdtSMyrKWArtc1+ygmSj533SFFPs="}]},"_npmUser":{"name":"agbero","email":"agbero.hq@gmail.com"},"directories":{},"maintainers":[{"name":"agbero","email":"agbero.hq@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/oja_0.3.0_1776343877913_0.11755847560303168"},"_hasShrinkwrap":false}},"time":{"created":"2026-03-20T10:47:52.181Z","modified":"2026-04-16T12:51:18.327Z","0.0.6":"2026-03-20T10:47:52.404Z","0.0.8":"2026-03-20T16:20:00.846Z","0.0.9":"2026-03-21T01:01:44.838Z","0.0.10":"2026-03-21T19:26:04.499Z","0.0.11":"2026-03-22T22:45:02.188Z","0.1.0":"2026-03-24T19:26:57.564Z","0.2.0":"2026-04-03T08:52:01.531Z","0.2.1":"2026-04-03T12:02:43.958Z","0.2.2":"2026-04-03T14:07:43.246Z","0.2.3":"2026-04-04T18:52:08.133Z","0.3.0":"2026-04-16T12:51:18.202Z"},"bugs":{"url":"https://github.com/agberohq/oja/issues"},"author":{"name":"Agbero HQ","email":"https://github.com/agberohq"},"license":"MIT","homepage":"https://github.com/agberohq/oja","keywords":["spa","framework","router","zero-build","vanilla-js","template","reactive"],"repository":{"type":"git","url":"git+https://github.com/agberohq/oja.git"},"description":"Minimal zero-build JavaScript framework for multi-app SPAs","maintainers":[{"name":"agbero","email":"agbero.hq@gmail.com"}],"readme":"> WARNING: This project is under active development.\n\n<p align=\"center\">\n  <img src=\"assets/oja_icon.png\" width=\"300\" alt=\"Oja Logo\">\n</p>\n\n<p align=\"left\">\n  <img src=\"assets/oja_name.png\" width=\"70\" alt=\"Oja\">\n</p>\n\n> *Oja* (Yoruba: *marketplace*) — a minimal, zero-build JavaScript framework for building multi-page SPAs.\n\nNo compiler. No virtual DOM. No node_modules. Drop files in, open a browser, it works.\n\n---\n\n## Why Oja exists\n\nMost frameworks make you choose between **simplicity** and **capability**.\n\nAlpine.js is simple but can't build a real SPA. React can build anything but requires a build step, a compiler, and forces HTML into JavaScript. Oja is the middle path — plain HTML files, plain JS files, one small framework layer.\n\nThe insight that shaped Oja: the real separation needed in a codebase is not just files — it is **people and roles**.\n\n```\nUI developer  →  opens .html and .css only, never touches .js\nJS developer  →  opens .js only, never writes HTML strings\n```\n\nA component is a plain `.html` file a UI developer can open in a browser, edit, and see results. The JS only supplies data.\n\n---\n\n## What Oja does not do\n\n- No build step — ever\n- No virtual DOM\n- No TypeScript (plain JS only)\n- No CSS-in-JS\n- No two-way data binding\n- No bundling\n- No server-side rendering\n\n---\n\n## Get started\n\n**[→ Learn Oja by building a real app — TUTORIAL.md](TUTORIAL.md)**\n\nThe tutorial builds a complete task board from scratch. Every concept is introduced exactly when it is needed — state, routing, components, layouts, forms, modals, keyboard shortcuts, auth guards, the engine, search, tables, offline support, and more. No abstractions in advance.\n\n---\n\n## Installation\n\nNo package manager required.\n\n### CDN (recommended)\n\n```html\n<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/@agberohq/oja@latest/build/oja.min.css\">\n\n<script type=\"importmap\">\n{\n    \"imports\": {\n        \"@agberohq/oja\": \"https://cdn.jsdelivr.net/npm/@agberohq/oja@latest/build/oja.full.esm.js\"\n    }\n}\n</script>\n```\n\nThe import map goes in `index.html` once. Every script on the page — including inline scripts inside your component `.html` files — uses the bare `@agberohq/oja` specifier. You never repeat it.\n\nPin to a specific version in production:\n```html\n\"@agberohq/oja\": \"https://cdn.jsdelivr.net/npm/@agberohq/oja@0.0.11/build/oja.full.esm.js\"\n```\n\n### npm\n\n```bash\nnpm install @agberohq/oja\n```\n\nPoint the import map at `./node_modules/@agberohq/oja/build/oja.full.esm.js`. Everything else is identical.\n\n### Self-hosted\n\n```bash\nnpm install --save-dev esbuild clean-css-cli\nmake        # → build/oja.full.esm.js + build/oja.min.css\nmake watch  # rebuild on save\n```\n\n### Direct source imports\n\nCopy `src/` and import directly — no build step ever. This is how the example apps work.\n\n```js\nimport { Router, Out, notify } from '../oja/src/oja.js';\n```\n\n---\n\n## Build variants\n\n| File | Contains | Use when |\n|------|----------|----------|\n| `oja.full.esm.js` | Everything | Default |\n| `oja.core.esm.js` | Core only | Size-sensitive apps |\n| `oja.core.min.js` | Core IIFE — `window.Oja` | No ES module support |\n| `oja.min.css` | All UI component styles | Always include |\n\n---\n\n## Four ideas, one framework\n\n### 1. Reactivity — state that drives the UI automatically\n\n```js\nimport { state, effect, derived, context, signal } from '@agberohq/oja';\n\nconst [count, setCount] = state(0);\nconst doubled = derived(() => count() * 2);\n\neffect(() => {\n    find('#counter').update({ text: `${count()} × 2 = ${doubled()}` });\n});\n\nsetCount(5); // counter updates automatically\n```\n\n`state` holds a value. `effect` reacts to it. `derived` computes from it. `context` shares it across the whole app. `signal` connects two components that have no common parent — a late subscriber always gets the current value immediately.\n\n#### Writing context values\n\n`context` returns a `[read, write]` pair. When you only need to write — without subscribing — use `context.set()`:\n\n```js\n// Full pair — for components that both subscribe and write:\nconst [notes, setNotes] = context('notes');\n\n// Write-only — no empty comma, clear intent:\ncontext.set('notes', updatedList);\n\n// Read current value once, no subscription:\nconst current = context.get('notes');\n```\n\n---\n\n### 2. `Out` — one primitive for all visible output\n\nNo raw `innerHTML`. No ad-hoc DOM writes. Every piece of visible output is an `Out` — composable, lazy, typed. It describes *what* to show without rendering it immediately.\n\n```js\nimport { Out } from '@agberohq/oja';\n\n// Render a component file\nOut.component('pages/dashboard.html', { user, metrics })\n\n// Conditional rendering — condition evaluated at render time\nOut.if(() => user.isAdmin, Out.c('admin.html'), Out.c('denied.html'))\n\n// List — one Out per item, keyed reconciliation\nOut.list(hosts, h => Out.c('components/host-row.html', h))\n\n// Async — three states in one call\nOut.promise(fetchUser(id), {\n    loading: Out.c('states/loading.html'),\n    success: (user) => Out.c('pages/user.html', user),\n    error:   Out.c('states/error.html'),\n})\n\n// Inline charts — zero dependencies\nOut.sparkline([12, 45, 23, 67], { color: '#00c770', fill: true })\n```\n\n`Out` is accepted everywhere Oja produces visible output — router, modal, notify, component, layout.\n\n---\n\n### 3. `find()` — DOM queries scoped to the current component\n\n`find()` returns an enhanced element. Inside a component script, it automatically searches within that component's container — multiple instances on the same page never interfere. Outside a component script, it falls back to `document`.\n\n```js\nimport { find, findAll } from '@agberohq/oja';\n\n// Inside a component script — scoped to this instance automatically\nconst btn = find('#save-btn');\n\n// Declarative patch — describe what the element should look like\nfind('#badge').update({\n    text:  'Online',\n    class: { add: 'badge-success', remove: 'badge-loading' },\n    attr:  { 'data-status': 'alive' },\n});\n\n// Reactive — re-runs automatically when signals change\nfind('#count').update({ text: () => `${tasks().length} tasks` });\n\n// Render any Out directly into an element\nfind('#detail-panel').update({ out: Out.c('components/detail.html', { host }) });\n\n// Keyed list reconciliation — only changed nodes are patched\nfind('#host-list').list(() => hosts(), {\n    key:    h => h.id,\n    render: h => Out.c('components/host-row.html', h),\n    empty:  Out.h('<p>No hosts yet</p>'),\n});\n```\n\n#### Async-safe DOM access\n\n`find()` resolves its scope from the active component context, which is only set during synchronous top-level execution. Inside `setTimeout`, async callbacks, or `effect()` handlers, the context is gone and `find()` falls back to `document`.\n\nTwo patterns to handle this:\n\n```js\nimport { find, scoped, ref } from '@agberohq/oja';\n\n// scoped() — capture bound query functions at top-level:\nconst { find: scopedFind, findAll: scopedFindAll } = scoped();\n\ncomponent.onMount(() => {\n    setTimeout(() => {\n        scopedFind('#status').textContent = 'ok';   // always correct\n        scopedFindAll('.item').forEach(el => el.classList.add('ready'));\n    }, 1000);\n});\n\n// ref() — capture a single element at top-level:\nconst syncDot = ref('#sync-dot');\n\nsetTimeout(async () => {\n    const quota = await getVfsQuota();\n    syncDot.el.title = `Saved · ${quota}`;   // always safe\n}, 600);\n```\n\n`scoped()` gives you bound `find`/`findAll` functions for anything inside the container. `ref()` captures one specific element. Both are permanently bound to the container at the moment they are called — they work correctly from any callback, async function, or effect.\n\n---\n\n### 4. `make()` — build DOM without HTML strings\n\n```js\nimport { make } from '@agberohq/oja';\n\n// Build, place, and update in one chain\nmake.div({ class: 'host-card', data: { id: host.id } },\n    make.h2({ class: 'hostname' }, host.name),\n    make.span({ class: 'badge', style: { color: 'green' } }, 'Online'),\n    make.button({\n        class: 'btn-danger',\n        on:    { click: () => deleteHost(host.id) },\n    }, 'Delete'),\n)\n.appendTo('#host-list')\n.update({ class: { add: 'loaded' } });\n\n// Placement methods — all return `this` so the chain never breaks\nmake.div({ class: 'toast' }, message).appendTo('#notifications');\nmake.div({ id: 'new' }).replace('#old');\nmake.li({ data: { id: '42' } }, 'New item').prependTo('#list');\n```\n\nAll placement methods: `.appendTo()` `.prependTo()` `.after()` `.before()` `.replace()`\n\n---\n\n## Component scripts — explicit imports, no magic\n\nEvery import in a component script is visible, IDE-friendly, and statically analysable. Nothing is injected automatically.\n\n```html\n<!-- components/status-badge.html -->\n<span class=\"badge\">Loading…</span>\n\n<script type=\"module\">\nimport { find, container, props, ready } from '@agberohq/oja';\n\n// find() is automatically scoped to this component instance\nconst badge = find('.badge');\nbadge.textContent = props().status;\n\n// container() gives you the raw DOM element this script is mounted into\nconsole.log(container().tagName); // → 'DIV'\n\n// ready() signals that async setup is complete (optional — fallback is automatic)\nready();\n</script>\n```\n\n| Import | What it returns |\n|--------|----------------|\n| `find(sel)` | Enhanced element scoped to this component, or `document` if called outside |\n| `findAll(sel)` | NodeList scoped to this component |\n| `scoped()` | `{ find, findAll, el }` — permanently bound to this container, safe in async callbacks |\n| `ref(sel)` | `{ el }` — captures one element at call time, safe in async callbacks |\n| `container()` | The DOM element this script is mounted into |\n| `props()` | Read-only object of the data passed at mount time |\n| `ready()` | Signals setup is complete — resolves the mount promise |\n\nSignals in props are unwrapped automatically — access `props().tasks` and it calls `tasks()` for you.\n\n---\n\n## Auth — session management\n\nOja's auth module handles JWTs, opaque tokens, role checks, and route protection without any external library.\n\n### Quick setup\n\n```js\nimport { auth, context } from '@agberohq/oja';\n\nexport const [currentUser, setCurrentUser] = context('user', null);\n\n// Define access levels — called on every route check\nauth.level('public',    () => true);\nauth.level('protected', () => auth.session.isActive());\nauth.level('admin',     () => auth.session.isActive() && auth.hasRole('admin'));\n\n// Hooks — fire at session lifecycle points\nauth.session.OnStart(async (token) => {\n    api.setToken(token);                          // set synchronously via tokenSync()\n    const dest = auth.session.intendedPath() || '/';\n    auth.session.clearIntendedPath();\n    router.navigate(dest);\n});\n\nauth.session.OnRenew((newToken) => api.setToken(newToken));\n\nauth.session.OnExpiry(() => {\n    setCurrentUser(null);\n    router.navigate('/login');\n    notify.warn('Session expired. Please sign in again.');\n});\n```\n\n### Starting a session after login\n\n```js\nform.on(loginForm, {\n    submit: async (data) => {\n        const user = await api.login(data.username, data.password);\n        await auth.session.start(user.token);  // stores token, fires OnStart\n        setCurrentUser(user);\n        return user;\n    },\n    success: () => notify.success('Welcome back!'),\n    error:   (err) => notify.error(err.message),\n});\n```\n\n### Session API\n\n| Method | Returns | Notes |\n|--------|---------|-------|\n| `auth.session.start(token, refreshToken?, options?)` | `Promise<void>` | Stores token, fires `OnStart` hooks |\n| `auth.session.end()` | `Promise<void>` | Clears token, fires no hooks — call before logout redirect |\n| `auth.session.renew(newToken, newRefreshToken?)` | `Promise<void>` | Replaces token, fires `OnRenew` hooks |\n| `auth.session.isActive()` | `boolean` | Synchronous — safe in middleware and effects |\n| `auth.session.token()` | `Promise<string\\|null>` | Async — decrypted token string |\n| `auth.session.tokenSync()` | `string\\|null` | **Synchronous** — returns `null` if inactive. Use in `OnStart` to set API headers immediately |\n| `auth.session.user()` | `object\\|null` | Decoded JWT payload — `sub`, `roles`, custom claims |\n| `auth.session.expiresIn()` | `number` | Milliseconds until expiry, `Infinity` if no expiry set |\n| `auth.session.intendedPath()` | `string\\|null` | Path the user was heading to before being redirected |\n| `auth.session.setIntendedPath(path)` | `void` | Set manually if needed |\n| `auth.session.clearIntendedPath()` | `void` | Call after navigating to the intended path |\n\n### `tokenSync()` — synchronous token access\n\n`tokenSync()` is the method added for cases where you need the token before any async operation resolves — for example, setting an API authorization header the moment a session starts:\n\n```js\n// In OnStart — synchronous, no await needed\nauth.session.OnStart((token) => {\n    // tokenSync() returns the same token immediately\n    const t = auth.session.tokenSync();\n    if (t) api.setToken(t);\n});\n\n// Also useful on app startup to restore a previous session\nconst savedToken = auth.session.tokenSync();\nif (savedToken) api.setToken(savedToken);\n```\n\n`tokenSync()` returns `null` when no session is active — it never throws.\n\n### Non-JWT tokens\n\nNot every API uses JWTs. Oja supports opaque tokens and Basic auth too:\n\n```js\n// Opaque token — pass { expires: null } for no-expiry session\nawait auth.session.start('Bearer sk-abc123', null, { expires: null });\nauth.session.isActive(); // → true\n\n// Fixed expiry — useful for session cookies or API keys with known TTL\nawait auth.session.start('api-key-xyz', null, { expires: Date.now() + 8 * 3600_000 });\n\n// No options — isActive() returns false (original behaviour, preserved for compatibility)\nawait auth.session.start('legacy-token');\nauth.session.isActive(); // → false\n```\n\n### Protecting routes\n\n```js\n// Middleware style — on individual routes\nrouter.Get('/admin', auth.middleware('admin'), Out.c('pages/admin.html'));\n\n// Group style — covers all routes in the group\nconst app = router.Group('/');\napp.Use(auth.middleware('protected', '/login'));\n\napp.Get('/dashboard', Out.c('pages/dashboard.html'));\napp.Get('/settings',  Out.c('pages/settings.html'));\n```\n\n`auth.middleware()` stores the attempted path before redirecting — call `auth.session.intendedPath()` in `OnStart` to send the user where they were going.\n\n### Role checks\n\n```js\n// In a JWT payload: { sub: 'u1', roles: ['editor', 'viewer'], email_verified: true }\n\nauth.hasRole('editor');         // → true\nauth.hasRole('admin');          // → false\n\n// Check arbitrary claims\nauth.hasClaim('email_verified');        // → true  (claim exists)\nauth.hasClaim('email_verified', true);  // → true  (claim equals value)\nauth.hasClaim('department', 'eng');     // → false\n\n// Use in level definitions\nauth.level('editor',   () => auth.session.isActive() && auth.hasRole('editor'));\nauth.level('verified', () => auth.session.isActive() && auth.hasClaim('email_verified', true));\n```\n\n---\n\n## Engine: lifecycle callbacks for lists\n\n`engine.list()` and `engine.listAsync()` accept lifecycle callbacks so imperative plugins can be initialized exactly once, without flags:\n\n```js\nengine.list('#notes', notes(), {\n    key:    n => n.id,\n    render: (note, existing) => { /* ... */ return el; },\n\n    // Runs once after the first render — use for plugin setup\n    onMount: (container) => {\n        dragdrop.reorder(container, {\n            handle:    '.drag-handle',\n            onReorder: els => saveOrder(els.map(el => el.dataset.id)),\n        });\n    },\n\n    // Runs for each newly inserted item — NOT for updated existing items\n    onItemMount: (itemEl, data, index) => {\n        initTooltip(itemEl);\n    },\n\n    // Runs before an item is removed from the DOM\n    onItemRemove: (itemEl) => {\n        destroyTooltip(itemEl);\n    },\n});\n```\n\n`onMount` fires exactly once regardless of how many times the reactive system calls `list()`. `onItemMount` only fires for genuinely new items — items that were reused from the previous render do not trigger it. This replaces the `if (!el._dragBound)` guard pattern entirely.\n\n---\n\n## Reactivity — which primitive to use\n\n```\n┌─────────────────────────────────────────────────────────────┐\n│                    REACTIVE SYSTEM                          │\n│  state() → derived() → effect()             batch()        │\n│      └──────────────────┘                                   │\n│              ↓                                              │\n│          context()   (global named reactive state)          │\n│              ↓                                              │\n│           watch()    (effect for a single value,            │\n│                       gives old + new value)                │\n└─────────────────────────────────────────────────────────────┘\n\n┌─────────────────────────────────────────────────────────────┐\n│                 IMPERATIVE EVENT BUS                         │\n│  emit() / on()     signal() — named bus with value cache   │\n└─────────────────────────────────────────────────────────────┘\n```\n\n| I want to… | Use |\n|---|---|\n| Reactive state shared across components | `context()` |\n| Local reactive state inside one component | `state()` |\n| A computed/derived value | `derived()` |\n| Run code when any signal I read changes | `effect()` |\n| Run code when one specific signal changes | `watch()` |\n| Fire a one-shot event | `emit()` / `on()` |\n| A named bus that remembers its last value | `signal()` |\n\n`signal()` is an imperative pub/sub bus — it is not tracked by `effect()`. Use `context()` for reactive state and `signal()` for \"current value + subscribe\" communication between distant components.\n\n---\n\n## What's in the box\n\n| Feature | Export | Build |\n|---------|--------|-------|\n| Reactive state (`state`, `effect`, `derived`, `batch`) | named | core + full |\n| Cross-module state (`context`) | named | core + full |\n| Reactive component communication (`signal`) | named | core + full |\n| DOM builder (`make`, `make.div`, `make.span` …) | named | core + full |\n| Enhanced queries (`find`, `query`, `findAll`, `queryAll`) | named | core + full |\n| Async-safe DOM capture (`scoped`, `ref`) | named | core + full |\n| Component context (`container`, `props`, `ready`) | named | core + full |\n| Router (hash + history, groups, middleware, named routes) | `Router` | core + full |\n| Layout (persistent shell, slots, `allSlotsReady`) | `layout` | core + full |\n| Component lifecycle (`onMount`, `onUnmount`, `interval`) | `component` | core + full |\n| Template syntax (`{{}}`, `data-if`, `data-each`, filters) | built-in | core + full |\n| Auth (levels, session, JWT, tokenSync, middleware, roles) | `auth` | core + full |\n| Notifications (toast, banner, progress, promise) | `notify` | core + full |\n| Modals (stack, confirm, prompt, beforeClose guard) | `modal` | core + full |\n| Forms (lifecycle, validation, dirty tracking, secret, radio) | `form` | core + full |\n| Events (delegated, emit/listen, keyboard shortcuts) | `on`, `emit`, `listen` | core + full |\n| Store (session/local/memory, encrypt, watch, TTL) | `Store` | core + full |\n| Encryption (Web Crypto, seal/open/rotate) | `encrypt` | core + full |\n| Engine (list reconcile, morph, `data-oja-bind`, lifecycle callbacks) | `engine` | core + full |\n| Progress (milestone hooks, reverse, bind, track) | `progress` | core + full |\n| Runtime unified event bus (`runtime.on/off/emit`) | `runtime` | core + full |\n| Animate (fade, slide, collapse, countUp, typewriter, shake) | `animate` | core + full |\n| Collapse + accordion | `collapse`, `accordion` | core + full |\n| Wizard (multi-step form, modal-compatible) | `wizard` | full |\n| Search + autocomplete (full-text, fuzzy, Trie) | `Search`, `Trie` | core + full |\n| Table (sort, pagination, row actions, remote data, resizable columns) | `table` | full |\n| Inline charts (sparkline, timeSeries) | `Out.sparkline` | core + full |\n| Clipboard | `clipboard` | core + full |\n| Drag and drop | `dragdrop` | full |\n| VFS (offline-first IndexedDB, encrypt, persist, quota) | `VFS` | core + full |\n| Config (`oja.config.json`) | `config` | core + full |\n| SSE + WebSocket (auto-reconnect) | `SSE`, `Socket` | full |\n| Channel + go (Go-style concurrency) | `Channel`, `go` | full |\n| Runner (long-lived background worker) | `Runner` | full |\n| Command palette / hotkeys | `hotkeys` | full |\n| Floating panels | `panel` | full |\n| Datepicker | `datepicker` | full |\n| Searchable select | `select` | full |\n| Context menu | `clickmenu` | full |\n| Offline request queue | `Queue` | full |\n| Multi-user presence + cursors | `Presence` | full |\n| Logging + debug | `logger`, `debug` | core + full |\n| Adapter bridge (D3, Chart.js, GSAP) | `adapter` | core + full |\n\n---\n\n## Design decisions\n\n| Decision | Choice | Reason |\n|----------|--------|--------|\n| Build step | None | Drop-in simplicity, no node_modules |\n| Virtual DOM | No | Direct DOM + targeted `effect()` |\n| Display primitive | `Out` everywhere | One type for all visible output — composable, typed, no raw strings |\n| DOM queries | `find()` reads execution context | Scoped automatically in component scripts, falls back to document outside — explicit import, no magic injection |\n| Async DOM access | `scoped()` and `ref()` | Permanently bound at capture time — safe in any callback, async function, or effect |\n| Component context | `container()`, `props()`, `ready()` | Named imports — IDE-visible, testable, statically analysable |\n| DOM creation | `make()` with placement chain | Build, place, and update in one expression |\n| List lifecycle | `onMount`, `onItemMount`, `onItemRemove` | One-time plugin setup without guard flags; new-items-only callbacks |\n| URL strategy | Hash default, path opt-in | Hash works everywhere without server config |\n| CSS ownership | App owns all styles, framework CSS in `oja.css` | No JS-injected styles — all component CSS lives in one file |\n| Auth | Declared at route level | Never check `isActive()` manually in page logic |\n| `tokenSync()` | Synchronous token read | Set API headers immediately in `OnStart` without awaiting |\n| Event bus | Single unified bus | All modules emit on `events.js`. `runtime.on()` is the public subscription point |\n| Component communication | `signal()` | Reactive, holds current value — unlike fire-and-forget emit/listen |\n| Context write shorthand | `context.set(name, value)` | Write without destructuring when you don't need the read signal |\n| Progress | Direction-aware + hooks | Milestone hooks, reverse animation, runtime binding |\n| Offline | VFS optional | Progressive enhancement — start without it, add it when needed |\n\n---\n\n## Known limitations\n\n- **Nested `{{range}}` loops**: inner `Index`/`First`/`Last` are list-absolute in chunked renders — access the outer variable by its `data-as` name.\n- **`Wasm` worker mode**: JS import callbacks are stubbed in the worker thread. Use non-worker mode for WASM modules that need JS callbacks.\n- **`Worker` scope isolation**: worker functions are serialised as strings — they cannot close over variables from the outer scope.\n- **`webrtc.js`**: WebRTC signaling is application-specific. Wire your own signaling server using `createPeer()` / `createOffer()` / `setLocalDescription()`.\n\n---\n\n## License\n\nMIT\n","readmeFilename":"README.md"}