[{"data":1,"prerenderedAt":577},["ShallowReactive",2],{"api:\u002Fapi\u002Fconfig:en":3,"api:\u002Fapi\u002Fprofile:":8,"api:\u002Fapi\u002Fcategories:en":19,"api:\u002Fapi\u002Fstats:":171,"api:\u002Fapi\u002Fprojects:en:{\"category\":\"backend\"}":289},{"email":4,"lang":5,"name":6,"role":7},"djrciv@gmail.com","en","Ewsirdev","Web developer: websites, landing pages and Go services",{"name":6,"handle":6,"contacts":9,"note":18},{"email":10,"github":12,"telegram":15},{"url":11,"label":4},"mailto:djrciv@gmail.com",{"url":13,"label":14},"https:\u002F\u002Fgithub.com\u002Fewsir247","github.com\u002Fewsir247",{"url":16,"label":17},"https:\u002F\u002Ft.me\u002FEwsirdev","@Ewsirdev","Contacts: Telegram, GitHub, email. This file is the single source of contacts for the site, the contact block, JSON-LD sameAs, security.txt and llms.txt.",[20,49,80,108,142],{"slug":21,"title":22,"blurb":23,"variant":24,"palette":25,"layout":26,"count":27,"preview":28,"stack":44},"landings","Landing pages","One page, one job. Hand-written markup where possible, plus one WebGL experiment.","ink","cold","shots",6,[29,34,39],{"slug":30,"title":31,"shot":32,"hue":33},"landing-studio","Halftone: lighting studio","\u002Fshots\u002Flanding-studio-desktop-sm.webp",212,{"slug":35,"title":36,"shot":37,"hue":38},"landing-saas","Kill Switch: subscription tracker","\u002Fshots\u002Flanding-saas-desktop-sm.webp",182,{"slug":40,"title":41,"shot":42,"hue":43},"landing-event","Small Scale: conference","\u002Fshots\u002Flanding-event-desktop-sm.webp",124,[45,46,47,48],"CSS","HTML","GitHub Actions","JS",{"slug":50,"title":51,"blurb":52,"variant":53,"palette":54,"layout":55,"count":56,"langs":57,"preview":61,"stack":77},"sites","Multi-page sites","Sites with their own structure, routing and forms: from a client project to a set of niche pages.","honey","mono","frames",4,[58],{"lang":59,"count":60},"TypeScript",1,[62,67,72],{"slug":63,"title":64,"shot":65,"hue":66},"legal-multipage-site","Multi-page legal services website","\u002Fshots\u002Flegal-multipage-site-desktop-sm.webp",250,{"slug":68,"title":69,"shot":70,"hue":71},"niche-landings","Catalog of 10 niche demo landing pages","\u002Fshots\u002Fniche-landings-desktop-sm.webp",32,{"slug":73,"title":74,"shot":75,"hue":76},"docs-engine","Sverlo: a documentation generator in Node.js","\u002Fshots\u002Fdocs-engine-desktop-sm.webp",25,[47,59,78,79],"Vue 3","Astro",{"slug":81,"title":82,"blurb":83,"variant":84,"palette":85,"layout":86,"count":87,"langs":88,"preview":90,"stack":105},"apps","Web apps","Interface plus server: auth, data, e-mail. Apps you sign in to.","beads","warm","phone",3,[89],{"lang":59,"count":60},[91,96,101],{"slug":92,"title":93,"shot":94,"hue":95},"bad-club-booking-app","Bad Club: seat booking for a gaming coworking space","\u002Fshots\u002Fbad-club-booking-app-desktop-sm.webp",150,{"slug":97,"title":98,"shot":99,"hue":100},"typing-lab","typing-lab: a touch-typing trainer","\u002Fshots\u002Ftyping-lab-desktop-sm.webp",45,{"slug":102,"title":103,"shot":104,"hue":100},"budget-split","budget-split: shared expense tracker","\u002Fshots\u002Fbudget-split-desktop-sm.webp",[59,106,78,107],"Vite","axe",{"slug":109,"title":110,"blurb":111,"variant":112,"palette":113,"layout":114,"count":115,"langs":116,"preview":123,"stack":139},"backend","Backends","Services with an API, a database and tests: mostly Go, with some in Python and TypeScript. A language filter sits inside the section.","ferro","acid","list",14,[117,120,122],{"lang":118,"count":119},"Go",12,{"lang":121,"count":60},"Python",{"lang":59,"count":60},[124,129,134],{"slug":125,"title":126,"shot":127,"hue":128},"kanban-tasks","Amber: kanban board","\u002Fshots\u002Fkanban-tasks-desktop-sm.webp",344,{"slug":130,"title":131,"shot":132,"hue":133},"link-shortener","Short: link shortener","\u002Fshots\u002Flink-shortener-desktop-sm.webp",111,{"slug":135,"title":136,"shot":137,"hue":138},"booking-slots","Appointment booking","\u002Fshots\u002Fbooking-slots-desktop-sm.webp",65,[140,118,141,47],"Docker","SQLite",{"slug":143,"title":144,"blurb":145,"variant":146,"palette":25,"layout":147,"count":87,"langs":148,"preview":152,"stack":168},"cli","CLI tools","Terminal programs: one binary, clear flags, an exit code you can check in a script.","film","terminal",[149,151],{"lang":118,"count":150},2,{"lang":121,"count":60},[153,158,163],{"slug":154,"title":155,"shot":156,"hue":157},"gitpulse","GitPulse: git repository analytics","\u002Fshots\u002Fgitpulse-desktop-sm.webp",7,{"slug":159,"title":160,"shot":161,"hue":162},"logslice","logslice: log analysis with no dependencies","\u002Fshots\u002Flogslice-desktop-sm.webp",160,{"slug":164,"title":165,"shot":166,"hue":167},"envguard","envguard: a linter and a safe for .env files","\u002Fshots\u002Fenvguard-desktop-sm.webp",35,[169,140,47,170],"CLI","stdlib only",{"generated_at":172,"tests_method":173,"note":174,"totals":175,"projects":185},"2026-10-22","go test -list","Lines are non-blank lines. Endpoints are HandleFunc\u002FHandle route patterns (without \u002Fdemo\u002F*). Tests are top-level Test* functions.",{"projects":176,"go_projects":177,"go_lines":178,"go_test_lines":179,"tests":180,"endpoints":181,"html":182,"css":183,"js":184},17,13,26503,12027,418,230,1973,3525,4815,[186,196,204,211,219,223,231,237,243,246,249,252,255,261,269,276,282],{"slug":187,"kind":188,"go_lines":189,"go_test_lines":190,"tests":191,"endpoints":192,"html":193,"css":194,"js":195},"api-gateway","go",2334,1179,48,11,115,139,163,{"slug":197,"kind":188,"go_lines":198,"go_test_lines":199,"tests":200,"endpoints":167,"html":201,"css":202,"js":203},"auth-service",2826,1393,50,23,166,500,{"slug":135,"kind":188,"go_lines":205,"go_test_lines":206,"tests":119,"endpoints":207,"html":208,"css":209,"js":210},891,338,18,146,123,309,{"slug":212,"kind":188,"go_lines":213,"go_test_lines":214,"tests":215,"endpoints":100,"html":216,"css":217,"js":218},"finance-tracker",3741,1371,30,24,1148,72,{"slug":154,"kind":188,"go_lines":220,"go_test_lines":221,"tests":71,"endpoints":87,"html":222,"css":222,"js":222},2133,735,0,{"slug":224,"kind":188,"go_lines":225,"go_test_lines":226,"tests":76,"endpoints":227,"html":228,"css":229,"js":230},"job-queue",2161,833,19,138,188,331,{"slug":125,"kind":188,"go_lines":232,"go_test_lines":233,"tests":157,"endpoints":207,"html":234,"css":235,"js":236},871,272,102,156,328,{"slug":238,"kind":239,"go_lines":222,"go_test_lines":222,"tests":222,"endpoints":222,"html":240,"css":241,"js":242},"landing-3d","static",201,162,377,{"slug":40,"kind":239,"go_lines":222,"go_test_lines":222,"tests":222,"endpoints":222,"html":208,"css":244,"js":245},122,59,{"slug":35,"kind":239,"go_lines":222,"go_test_lines":222,"tests":222,"endpoints":222,"html":247,"css":248,"js":245},172,136,{"slug":30,"kind":239,"go_lines":222,"go_test_lines":222,"tests":222,"endpoints":222,"html":250,"css":251,"js":245},187,109,{"slug":130,"kind":188,"go_lines":205,"go_test_lines":236,"tests":119,"endpoints":192,"html":253,"css":241,"js":254},119,317,{"slug":256,"kind":188,"go_lines":257,"go_test_lines":232,"tests":71,"endpoints":227,"html":258,"css":259,"js":260},"notes-search",2027,174,192,517,{"slug":262,"kind":188,"go_lines":263,"go_test_lines":264,"tests":265,"endpoints":266,"html":267,"css":259,"js":268},"pixel-wall",2715,1910,77,16,106,625,{"slug":270,"kind":188,"go_lines":271,"go_test_lines":272,"tests":273,"endpoints":177,"html":274,"css":258,"js":275},"raft-kv",2646,895,27,91,213,{"slug":277,"kind":188,"go_lines":278,"go_test_lines":279,"tests":191,"endpoints":157,"html":280,"css":281,"js":260},"realtime-rooms",1822,1313,103,184,{"slug":283,"kind":188,"go_lines":284,"go_test_lines":285,"tests":207,"endpoints":286,"html":287,"css":247,"js":288},"uptime-monitor",1445,589,15,126,369,[290,309,323,336,355,373,393,416,438,463,489,512,532,555],{"slug":125,"title":126,"type":109,"category":109,"lang":118,"kind":291,"live_url":292,"stack":293,"description":294,"highlights":295,"run":300,"hue":128,"og":301,"shots":302},"api","https:\u002F\u002Fkanban-tasks.ewsir.space",[118,141,140],"A multi-user kanban board: sign-up, boards, columns, drag-and-drop cards. Go + SQLite without CGO, the front end is embedded in the binary.",[296,297,298,299],"PBKDF2 and sessions in an HttpOnly cookie","Per-user data isolation","API tests","Multi-stage Docker","cd kanban-tasks && go run .","\u002Fog\u002Fen\u002Fkanban-tasks.jpg",{"desktop":303,"desktop_sm":127,"mobile":304,"dw":305,"dh":306,"mw":307,"mh":308},"\u002Fshots\u002Fkanban-tasks-desktop.webp","\u002Fshots\u002Fkanban-tasks-mobile.webp",1280,800,390,844,{"slug":130,"title":131,"type":109,"category":109,"lang":118,"kind":291,"live_url":310,"stack":311,"description":312,"highlights":313,"run":318,"hue":133,"og":319,"shots":320},"https:\u002F\u002Flink-shortener.ewsir.space",[118,141,140],"Link shortening with aliases and expiry, click statistics by day, browser and device, a token-protected dashboard. IP addresses are not stored.",[314,315,316,317],"Redirect and click tracking","Rate limiting","Private statistics (salted hash)","Tests and Docker","cd link-shortener && set SEED_DEMO=1&& go run .\u002Fcmd\u002Fserver","\u002Fog\u002Fen\u002Flink-shortener.jpg",{"desktop":321,"desktop_sm":132,"mobile":322,"dw":305,"dh":306,"mw":307,"mh":308},"\u002Fshots\u002Flink-shortener-desktop.webp","\u002Fshots\u002Flink-shortener-mobile.webp",{"slug":135,"title":136,"type":109,"category":109,"lang":118,"kind":291,"live_url":324,"stack":325,"description":326,"highlights":327,"run":331,"hue":138,"og":332,"shots":333},"https:\u002F\u002Fbooking-slots.ewsir.space",[118,141,140],"Online booking: free slots computed from service duration and working hours, double-booking protection, an admin panel. A fictional studio, demo.",[328,329,330,317],"Free slot calculation","No double bookings","Admin for services and schedule","cd booking-slots && go run .","\u002Fog\u002Fen\u002Fbooking-slots.jpg",{"desktop":334,"desktop_sm":137,"mobile":335,"dw":305,"dh":306,"mw":307,"mh":308},"\u002Fshots\u002Fbooking-slots-desktop.webp","\u002Fshots\u002Fbooking-slots-mobile.webp",{"slug":283,"title":337,"type":109,"category":109,"lang":118,"kind":338,"live_url":339,"stack":340,"description":343,"highlights":344,"run":348,"hue":349,"og":350,"shots":351},"Pulse: uptime monitor","infra","https:\u002F\u002Fuptime-monitor.ewsir.space",[118,141,341,342,140],"SSE","REST API","An uptime monitor: scheduled HTTP and TCP checks, uptime, p50\u002Fp95, an incident detector, a live SSE dashboard and a public status page. Go + SQLite without CGO.",[345,346,347,317],"A worker goroutine per check","Incidents after a series of failures","SSE dashboard and a public \u002Fstatus","cd uptime-monitor && set SEED_DEMO=1&& go run .\u002Fcmd\u002Fserver",101,"\u002Fog\u002Fen\u002Fuptime-monitor.jpg",{"desktop":352,"desktop_sm":353,"mobile":354,"dw":305,"dh":306,"mw":307,"mh":308},"\u002Fshots\u002Fuptime-monitor-desktop.webp","\u002Fshots\u002Fuptime-monitor-desktop-sm.webp","\u002Fshots\u002Fuptime-monitor-mobile.webp",{"slug":224,"title":356,"type":109,"category":109,"lang":118,"kind":338,"live_url":357,"stack":358,"description":360,"highlights":361,"run":366,"hue":367,"og":368,"shots":369},"Conveyor: job queue","https:\u002F\u002Fjob-queue.ewsir.space",[118,141,342,341,359,140],"Prometheus","A background job queue: priorities, delayed and cron jobs, retries with backoff and jitter, dead-letter, idempotency keys, leases with fencing, Prometheus metrics.",[362,363,364,365],"At-least-once with lease and fencing","Retries with backoff and jitter + dead-letter","Custom cron parser","Prometheus metrics and graceful shutdown","cd job-queue && set API_TOKEN=demo&& set SEED_DEMO=1&& go run .\u002Fcmd\u002Fserver",249,"\u002Fog\u002Fen\u002Fjob-queue.jpg",{"desktop":370,"desktop_sm":371,"mobile":372,"dw":305,"dh":306,"mw":307,"mh":308},"\u002Fshots\u002Fjob-queue-desktop.webp","\u002Fshots\u002Fjob-queue-desktop-sm.webp","\u002Fshots\u002Fjob-queue-mobile.webp",{"slug":277,"title":374,"type":109,"category":109,"lang":118,"kind":375,"live_url":376,"stack":377,"description":380,"highlights":381,"run":386,"hue":387,"og":388,"shots":389},"Rooms: realtime chat and board","realtime","https:\u002F\u002Frealtime-rooms.ewsir.space",[118,378,141,379,140],"WebSocket","Canvas","Realtime rooms: chat with SQLite history, an online list, a typing indicator, a shared board with strokes and cursors, anonymous nicknames. WebSocket (RFC 6455) written on the stdlib.",[382,383,384,385],"Own WebSocket layer (RFC 6455) on the stdlib","Slow-client disconnects and rate limits","Graceful shutdown with close code 1001","Load test: 360 connections in 6 rooms","cd realtime-rooms && set SEED_DEMO=1&& go run .\u002Fcmd\u002Fserver",46,"\u002Fog\u002Fen\u002Frealtime-rooms.jpg",{"desktop":390,"desktop_sm":391,"mobile":392,"dw":305,"dh":306,"mw":307,"mh":308},"\u002Fshots\u002Frealtime-rooms-desktop.webp","\u002Fshots\u002Frealtime-rooms-desktop-sm.webp","\u002Fshots\u002Frealtime-rooms-mobile.webp",{"slug":187,"title":394,"type":109,"category":109,"lang":118,"kind":338,"live_url":395,"stack":396,"description":403,"highlights":404,"run":409,"hue":410,"og":411,"shots":412},"Sluice: API gateway","https:\u002F\u002Fapi-gateway.ewsir.space",[118,170,397,398,399,341,359,400,401,402,140,47],"Reverse proxy","Circuit breaker","Token bucket","slog","Vanilla JS","embed","An API gateway on the pure stdlib: host\u002Fprefix routes from a JSON config with hot reload that keeps connections alive, round-robin and least-conn balancing, health checks, a per-node circuit breaker, retries of idempotent requests, rate limiting, hashed API keys, Prometheus metrics and a live SSE dashboard.",[405,406,407,408],"Zero dependencies: stdlib only","Config hot reload without dropping connections","Per-node circuit breaker and retries of idempotent requests","Load test: 4000 requests, 40 clients (a demo run)","cd api-gateway && set SEED_DEMO=1&& go run .\u002Fcmd\u002Fgateway",200,"\u002Fog\u002Fen\u002Fapi-gateway.jpg",{"desktop":413,"desktop_sm":414,"mobile":415,"dw":305,"dh":306,"mw":307,"mh":308},"\u002Fshots\u002Fapi-gateway-desktop.webp","\u002Fshots\u002Fapi-gateway-desktop-sm.webp","\u002Fshots\u002Fapi-gateway-mobile.webp",{"slug":256,"title":417,"type":109,"category":109,"lang":118,"kind":418,"live_url":419,"stack":420,"description":426,"highlights":427,"run":432,"hue":162,"og":433,"shots":434},"Card Index: notes with search","data","https:\u002F\u002Fnotes-search.ewsir.space",[118,141,421,422,423,342,424,425,401,402,140,47],"FTS5","bm25","modernc.org\u002Fsqlite (no CGO)","Markdown (custom safe renderer)","SVG force-layout","A Markdown notes base on Go and SQLite: [[wiki]] links with backlinks and a link graph, FTS5 full-text search (bm25, highlighting, simplified Russian morphology), version history with its own diff, autosave with conflict resolution, .md export and import.",[428,429,430,431],"FTS5 search with bm25 and highlighting","Wiki links, backlinks and a link graph","Version history with its own diff","A custom safe Markdown renderer with an XSS test","cd notes-search && set SEED_DEMO=1&& go run .\u002Fcmd\u002Fserver","\u002Fog\u002Fen\u002Fnotes-search.jpg",{"desktop":435,"desktop_sm":436,"mobile":437,"dw":305,"dh":306,"mw":307,"mh":308},"\u002Fshots\u002Fnotes-search-desktop.webp","\u002Fshots\u002Fnotes-search-desktop-sm.webp","\u002Fshots\u002Fnotes-search-mobile.webp",{"slug":270,"title":439,"type":109,"category":109,"lang":118,"kind":418,"live_url":440,"stack":441,"description":450,"highlights":451,"run":456,"hue":457,"og":458,"shots":459},"Raft KV: a key-value store on Raft","https:\u002F\u002Fraft-kv.ewsir.space",[118,442,443,444,170,445,341,446,447,448,449,401,140,47],"Raft","Distributed systems","Consensus","HTTP\u002FJSON","WAL + fsync","Snapshots","Linearizability","Deterministic simulation testing","A from-scratch teaching implementation of Raft on the pure stdlib: leader election, log replication, quorum commit, a log and term\u002Fvote on disk with fsync, snapshots with compaction, linearizable reads (read-index) and follower-to-leader redirects. It is tested with a deterministic network and time simulator that checks linearizability; the dashboard can kill the leader and cut the network (demo).",[452,453,454,455],"Raft from scratch on the stdlib, no raft libraries","A deterministic network and time simulator + a linearizability check","Log and term\u002Fvote on disk with fsync, snapshots and compaction","A dashboard to kill the leader and cut the network (demo)","cd raft-kv && set SEED_DEMO=1&& go run .\u002Fcmd\u002Fraft-kv",290,"\u002Fog\u002Fen\u002Fraft-kv.jpg",{"desktop":460,"desktop_sm":461,"mobile":462,"dw":305,"dh":306,"mw":307,"mh":308},"\u002Fshots\u002Fraft-kv-desktop.webp","\u002Fshots\u002Fraft-kv-desktop-sm.webp","\u002Fshots\u002Fraft-kv-mobile.webp",{"slug":197,"title":464,"type":109,"category":109,"lang":118,"kind":465,"live_url":466,"stack":467,"description":476,"highlights":477,"run":482,"hue":483,"og":484,"shots":485},"Keyring: an authentication service","auth","https:\u002F\u002Fauth-service.ewsir.space",[118,468,141,423,469,470,471,472,473,474,475,401,402,140,47],"stdlib crypto","JWT EdDSA\u002FEd25519","JWKS","PBKDF2","TOTP","RBAC","REST","OpenAPI 3","An authentication and authorization service in Go: EdDSA JWTs with key rotation and JWKS, refresh tokens with rotation and theft detection, TOTP 2FA (RFC 6238) with backup codes, RBAC, lockout on brute force, password reset by a one-time token, an audit log, a device list, openapi.yaml. Not independently audited, not a production IdP.",[478,479,480,481],"EdDSA JWTs with key rotation and JWKS","Refresh tokens with rotation and reuse detection","TOTP per RFC 6238, checked against the RFC test vectors","An append-only audit log (database triggers)","cd auth-service && set SEED_DEMO=1&& go run .\u002Fcmd\u002Fserver",20,"\u002Fog\u002Fen\u002Fauth-service.jpg",{"desktop":486,"desktop_sm":487,"mobile":488,"dw":305,"dh":306,"mw":307,"mh":308},"\u002Fshots\u002Fauth-service-desktop.webp","\u002Fshots\u002Fauth-service-desktop-sm.webp","\u002Fshots\u002Fauth-service-mobile.webp",{"slug":212,"title":490,"type":109,"category":109,"lang":118,"kind":291,"live_url":491,"stack":492,"description":499,"highlights":500,"run":505,"hue":506,"og":507,"shots":508},"Ledger: personal finance tracker","https:\u002F\u002Ffinance-tracker.ewsir.space",[118,141,423,342,493,78,59,494,495,496,497,140,47,471,498],"Nuxt 4","Pinia","SVG charts","vitest","ESLint","CSV import\u002Fexport","Personal finance tracking: accounts in different currencies with manual exchange rates, income, expenses and transfers, budgets, payments on a cron-like schedule, CSV import with a preview, reports. Money is stored only in integer minor units, users' data is isolated, and the charts are drawn in SVG with no library. Go + SQLite with a Nuxt 4 front end.",[501,502,503,504],"Money only in integer minor units","Multi-currency with manual exchange rates","CSV import with a preview and a dry run","Hand-made SVG charts, no library","cd finance-tracker && set SEED_DEMO=1&& go run .",140,"\u002Fog\u002Fen\u002Ffinance-tracker.jpg",{"desktop":509,"desktop_sm":510,"mobile":511,"dw":305,"dh":306,"mw":307,"mh":308},"\u002Fshots\u002Ffinance-tracker-desktop.webp","\u002Fshots\u002Ffinance-tracker-desktop-sm.webp","\u002Fshots\u002Ffinance-tracker-mobile.webp",{"slug":262,"title":513,"type":109,"category":109,"lang":118,"kind":375,"live_url":514,"stack":515,"description":519,"highlights":520,"run":525,"hue":526,"og":527,"shots":528},"Pixel Wall: a shared canvas","https:\u002F\u002Fpixel-wall.ewsir.space",[118,170,378,516,401,402,517,518,140,47],"Canvas 2D","Binary protocol","Snapshot + journal","A shared 256x256 canvas in the spirit of r\u002Fplace: 16 colors, a cooldown per session and IP, an own WebSocket layer on the stdlib (RFC 6455) with a binary protocol (4 bits per pixel), a snapshot plus deltas, a snapshot with a journal and recovery, a timelapse, zoom and pan, a minimap, PNG export and admin tools. A side project.",[521,522,523,524],"Own WebSocket (RFC 6455) on the stdlib","A binary protocol: 4 bits per pixel","Snapshot + journal with crash recovery","A timelapse over the change journal","cd pixel-wall && set SEED_DEMO=1&& set ADMIN_TOKEN=secret&& go run .\u002Fcmd\u002Fserver",320,"\u002Fog\u002Fen\u002Fpixel-wall.jpg",{"desktop":529,"desktop_sm":530,"mobile":531,"dw":305,"dh":306,"mw":307,"mh":308},"\u002Fshots\u002Fpixel-wall-desktop.webp","\u002Fshots\u002Fpixel-wall-desktop-sm.webp","\u002Fshots\u002Fpixel-wall-mobile.webp",{"slug":533,"title":534,"type":109,"category":109,"lang":121,"live_url":535,"stack":536,"description":542,"highlights":543,"run":548,"hue":549,"og":550,"shots":551},"catalog-api","catalog-api: a catalog API with search","https:\u002F\u002Fcatalog-api.ewsir.space",[121,537,141,421,538,539,540,541,140,47],"FastAPI","pytest","ruff","mypy","bandit","A product catalog API on Python 3.12 and FastAPI: SQLite with FTS5 full-text search (bm25 ranking), facets by category, price, availability, tags and attributes, cursor pagination, ETag, CSV import and export, price history, API keys with roles and an audit log. The storefront and the admin panel are served by the app itself, with no framework. A side project.",[544,545,546,547],"FTS5 search with bm25, facets and cursor pagination","API keys with the roles admin, editor and reader; only key hashes are stored","511 tests, 96% coverage; ruff, mypy --strict, bandit, pip-audit and gitleaks are clean","Load: 339 requests\u002Fs, p95 50 ms at 8 connections (Windows, a single process)","SEED_DEMO=1 python -m app   # http:\u002F\u002F127.0.0.1:8110",265,"\u002Fog\u002Fen\u002Fcatalog-api.jpg",{"desktop":552,"desktop_sm":553,"mobile":554,"dw":305,"dh":306,"mw":307,"mh":308},"\u002Fshots\u002Fcatalog-api-desktop.webp","\u002Fshots\u002Fcatalog-api-desktop-sm.webp","\u002Fshots\u002Fcatalog-api-mobile.webp",{"slug":556,"title":557,"type":109,"category":109,"lang":59,"live_url":558,"stack":559,"description":564,"highlights":565,"run":570,"hue":571,"og":572,"shots":573},"paste-vault","Konvert: notes encrypted in the browser","https:\u002F\u002Fewsirdev-paste.ewsirdev.workers.dev",[59,560,561,562,106,496,563,47],"Cloudflare Workers","D1","Web Crypto","fast-check","A one-time note service on TypeScript, Cloudflare Workers and D1: the text is encrypted in the browser (AES-256-GCM), the key lives only in the URL fragment and never reaches the server. A note has a lifetime, an optional password and burns after the first read. A side project.",[566,567,568,569],"Encryption in the browser: AES-256-GCM, the key only in the URL fragment","Burn after reading is atomic: exactly one success out of 40 parallel reads","52 tests in the workerd runtime, including property tests and schema fuzzing","A live version on Cloudflare Workers + D1","npm install && npm run dev",215,"\u002Fog\u002Fen\u002Fpaste-vault.jpg",{"desktop":574,"desktop_sm":575,"mobile":576,"dw":305,"dh":306,"mw":307,"mh":308},"\u002Fshots\u002Fpaste-vault-desktop.webp","\u002Fshots\u002Fpaste-vault-desktop-sm.webp","\u002Fshots\u002Fpaste-vault-mobile.webp",1793297826879]