[{"data":1,"prerenderedAt":1143},["ShallowReactive",2],{"api:\u002Fapi\u002Fconfig:en":3,"api:\u002Fapi\u002Fprofile:":8,"api:\u002Fapi\u002Fcategories:en":19,"api:\u002Fapi\u002Fnotes\u002Fhash-chain-tail-truncation:en":177,"api:\u002Fapi\u002Fprojects:en":186},{"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,144],{"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.","drop","ice","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.","merge","violet","frames",8,[58],{"lang":59,"count":60},"TypeScript",5,[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],"Cloudflare Pages","axe",{"slug":81,"title":82,"blurb":83,"variant":84,"palette":85,"layout":86,"count":27,"langs":87,"preview":90,"stack":105},"apps","Web apps","Apps you use: with a server, auth and e-mail, or offline-first PWAs where the data stays in the browser.","torus","mint","phone",[88],{"lang":59,"count":89},4,[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,79,107],"Vite","vitest",{"slug":109,"title":110,"blurb":111,"variant":112,"palette":113,"layout":114,"count":115,"langs":116,"preview":125,"stack":141},"backend","Backends","Services with an API, a database and tests: in Go, Rust, Python and TypeScript. A language filter sits inside the section.","crystal","steel","list",24,[117,120,122,124],{"lang":118,"count":119},"Go",12,{"lang":121,"count":89},"Python",{"lang":123,"count":89},"Rust",{"lang":59,"count":89},[126,131,136],{"slug":127,"title":128,"shot":129,"hue":130},"kanban-tasks","Amber: kanban board","\u002Fshots\u002Fkanban-tasks-desktop-sm.webp",344,{"slug":132,"title":133,"shot":134,"hue":135},"link-shortener","Short: link shortener","\u002Fshots\u002Flink-shortener-desktop-sm.webp",111,{"slug":137,"title":138,"shot":139,"hue":140},"booking-slots","Appointment booking","\u002Fshots\u002Fbooking-slots-desktop-sm.webp",65,[142,47,143,118],"Docker","SQLite",{"slug":145,"title":146,"blurb":147,"variant":148,"palette":149,"layout":150,"count":27,"langs":151,"preview":158,"stack":174},"cli","CLI tools","Terminal programs: one binary, clear flags, an exit code you can check in a script.","swarm","champagne","terminal",[152,154,156,157],{"lang":118,"count":153},3,{"lang":121,"count":155},1,{"lang":123,"count":155},{"lang":59,"count":155},[159,164,169],{"slug":160,"title":161,"shot":162,"hue":163},"gitpulse","GitPulse: git repository analytics","\u002Fshots\u002Fgitpulse-desktop-sm.webp",7,{"slug":165,"title":166,"shot":167,"hue":168},"logslice","logslice: log analysis with no dependencies","\u002Fshots\u002Flogslice-desktop-sm.webp",160,{"slug":170,"title":171,"shot":172,"hue":173},"envguard","envguard: a linter and a safe for .env files","\u002Fshots\u002Fenvguard-desktop-sm.webp",35,[47,175,142,176],"CLI","stdlib only",{"slug":178,"lang":5,"title":179,"date":180,"summary":181,"project":182,"minutes":183,"body_md":184,"body_html":185},"hash-chain-tail-truncation","A hash-chained journal: what it catches and what it does not","2026-10-30","How ledger-core builds a SHA-256 chain and checks it through \u002Fv1\u002Fverify, why tampering in the middle shows but tail truncation does not.","ledger-core",2,"In ledger-core (Rust, SQLite) the posting journal is immutable: triggers forbid UPDATE and DELETE. But anyone with the database file can drop a trigger. So the real defence is the chain check.\n\n## How the chain works\n\nEach record stores `prev_hash` and its own `hash = SHA-256(version, prev_hash, seq, key, time, currency, memo, postings)`. Fields carry a length prefix, so \"ab\"+\"c\" and \"a\"+\"bc\" give different bytes and concatenation is never ambiguous.\n\n`GET \u002Fv1\u002Fverify` recomputes everything: numbering continuity, that `prev_hash` matches the previous record, and the hash of every record from its fields. If a record was altered, the check names exactly that one.\n\n## What is caught\n\nProperty tests (proptest) change an amount, a memo or an account and require the check to name the altered record. Deleting a record in the middle, the triggers, idempotency and overflow are tested separately.\n\n## What is not caught\n\n**Tail truncation.** Delete the last few records and the remaining chain stays valid: every record still points at its predecessor. From inside the journal this cannot be seen. You need an anchor outside: store the `head_hash` of the last record somewhere else periodically and compare on verification.\n\nAnyone reading this as \"a blockchain protects against everything\" should remember: a chain proves the integrity of what is left, not completeness.\n\n## Measured\n\nAn 11.4 MB image; 3.5 MiB RAM idle and 4.5 MiB after 200 requests (100 parallel postings and 100 reads), the chain stayed intact. 19 tests.\n","\u003Cp>In ledger-core (Rust, SQLite) the posting journal is immutable: triggers forbid UPDATE and DELETE. But anyone with the database file can drop a trigger. So the real defence is the chain check.\u003C\u002Fp>\n\u003Ch2>How the chain works\u003C\u002Fh2>\n\u003Cp>Each record stores \u003Ccode>prev_hash\u003C\u002Fcode> and its own \u003Ccode>hash = SHA-256(version, prev_hash, seq, key, time, currency, memo, postings)\u003C\u002Fcode>. Fields carry a length prefix, so &#34;ab&#34;+&#34;c&#34; and &#34;a&#34;+&#34;bc&#34; give different bytes and concatenation is never ambiguous.\u003C\u002Fp>\n\u003Cp>\u003Ccode>GET \u002Fv1\u002Fverify\u003C\u002Fcode> recomputes everything: numbering continuity, that \u003Ccode>prev_hash\u003C\u002Fcode> matches the previous record, and the hash of every record from its fields. If a record was altered, the check names exactly that one.\u003C\u002Fp>\n\u003Ch2>What is caught\u003C\u002Fh2>\n\u003Cp>Property tests (proptest) change an amount, a memo or an account and require the check to name the altered record. Deleting a record in the middle, the triggers, idempotency and overflow are tested separately.\u003C\u002Fp>\n\u003Ch2>What is not caught\u003C\u002Fh2>\n\u003Cp>\u003Cstrong>Tail truncation.\u003C\u002Fstrong> Delete the last few records and the remaining chain stays valid: every record still points at its predecessor. From inside the journal this cannot be seen. You need an anchor outside: store the \u003Ccode>head_hash\u003C\u002Fcode> of the last record somewhere else periodically and compare on verification.\u003C\u002Fp>\n\u003Cp>Anyone reading this as &#34;a blockchain protects against everything&#34; should remember: a chain proves the integrity of what is left, not completeness.\u003C\u002Fp>\n\u003Ch2>Measured\u003C\u002Fh2>\n\u003Cp>An 11.4 MB image; 3.5 MiB RAM idle and 4.5 MiB after 200 requests (100 parallel postings and 100 reads), the chain stayed intact. 19 tests.\u003C\u002Fp>\n",[187,206,220,233,248,262,275,295,314,335,359,382,406,432,459,483,504,523,543,563,579,593,612,628,645,665,685,703,719,733,751,773,793,811,829,850,868,888,907,927,947,969,988,1007,1025,1045,1064,1084,1103,1123],{"slug":127,"title":128,"type":109,"category":109,"lang":118,"kind":188,"live_url":189,"stack":190,"description":191,"highlights":192,"run":197,"hue":130,"og":198,"shots":199},"api","https:\u002F\u002Fkanban-tasks.ewsir.space",[118,143,142],"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.",[193,194,195,196],"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":200,"desktop_sm":129,"mobile":201,"dw":202,"dh":203,"mw":204,"mh":205},"\u002Fshots\u002Fkanban-tasks-desktop.webp","\u002Fshots\u002Fkanban-tasks-mobile.webp",1280,800,390,844,{"slug":132,"title":133,"type":109,"category":109,"lang":118,"kind":188,"live_url":207,"stack":208,"description":209,"highlights":210,"run":215,"hue":135,"og":216,"shots":217},"https:\u002F\u002Flink-shortener.ewsir.space",[118,143,142],"Link shortening with aliases and expiry, click statistics by day, browser and device, a token-protected dashboard. IP addresses are not stored.",[211,212,213,214],"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":218,"desktop_sm":134,"mobile":219,"dw":202,"dh":203,"mw":204,"mh":205},"\u002Fshots\u002Flink-shortener-desktop.webp","\u002Fshots\u002Flink-shortener-mobile.webp",{"slug":137,"title":138,"type":109,"category":109,"lang":118,"kind":188,"live_url":221,"stack":222,"description":223,"highlights":224,"run":228,"hue":140,"og":229,"shots":230},"https:\u002F\u002Fbooking-slots.ewsir.space",[118,143,142],"Online booking: free slots computed from service duration and working hours, double-booking protection, an admin panel. A fictional studio, demo.",[225,226,227,214],"Free slot calculation","No double bookings","Admin for services and schedule","cd booking-slots && go run .","\u002Fog\u002Fen\u002Fbooking-slots.jpg",{"desktop":231,"desktop_sm":139,"mobile":232,"dw":202,"dh":203,"mw":204,"mh":205},"\u002Fshots\u002Fbooking-slots-desktop.webp","\u002Fshots\u002Fbooking-slots-mobile.webp",{"slug":30,"title":31,"type":234,"category":21,"live_url":235,"stack":236,"description":237,"highlights":238,"run":243,"hue":33,"og":244,"shots":245},"landing","https:\u002F\u002Fewsirdev-studio.pages.dev",[46,45,48],"A landing page for a fictional interior lighting studio: warm palette, serif type, SVG illustration, FAQ and a demo form. Responsive, no external images.",[239,240,241,242],"Responsive 390-1280","SVG instead of images","Accessible FAQ and form","An honest demo form","cd landing-studio && python -m http.server 8091","\u002Fog\u002Fen\u002Flanding-studio.jpg",{"desktop":246,"desktop_sm":32,"mobile":247,"dw":202,"dh":203,"mw":204,"mh":205},"\u002Fshots\u002Flanding-studio-desktop.webp","\u002Fshots\u002Flanding-studio-mobile.webp",{"slug":35,"title":36,"type":234,"category":21,"live_url":249,"stack":250,"description":251,"highlights":252,"run":257,"hue":38,"og":258,"shots":259},"https:\u002F\u002Fewsirdev-saas.pages.dev",[46,45,48],"A landing page for a fictional SaaS product: dark gradient hero, a UI mock-up in CSS, pricing, FAQ, a demo form. All prices and names are made up.",[253,254,255,256],"UI mock-up in CSS","Pricing grid","Responsive","Demo form that sends no data","cd landing-saas && python -m http.server 8092","\u002Fog\u002Fen\u002Flanding-saas.jpg",{"desktop":260,"desktop_sm":37,"mobile":261,"dw":202,"dh":203,"mw":204,"mh":205},"\u002Fshots\u002Flanding-saas-desktop.webp","\u002Fshots\u002Flanding-saas-mobile.webp",{"slug":40,"title":41,"type":234,"category":21,"live_url":263,"stack":264,"description":265,"highlights":266,"run":270,"hue":43,"og":271,"shots":272},"https:\u002F\u002Fewsirdev-event.pages.dev",[46,45,48],"A landing page for a fictional one-day conference: poster typography, schedule, placeholder speakers, terms, a demo registration form.",[267,268,255,269],"Day schedule","Poster typography","No invented speaker names","cd landing-event && python -m http.server 8093","\u002Fog\u002Fen\u002Flanding-event.jpg",{"desktop":273,"desktop_sm":42,"mobile":274,"dw":202,"dh":203,"mw":204,"mh":205},"\u002Fshots\u002Flanding-event-desktop.webp","\u002Fshots\u002Flanding-event-mobile.webp",{"slug":276,"title":277,"type":109,"category":109,"lang":118,"kind":278,"live_url":279,"stack":280,"description":283,"highlights":284,"run":288,"hue":289,"og":290,"shots":291},"uptime-monitor","Pulse: uptime monitor","infra","https:\u002F\u002Fuptime-monitor.ewsir.space",[118,143,281,282,142],"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.",[285,286,287,214],"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":292,"desktop_sm":293,"mobile":294,"dw":202,"dh":203,"mw":204,"mh":205},"\u002Fshots\u002Fuptime-monitor-desktop.webp","\u002Fshots\u002Fuptime-monitor-desktop-sm.webp","\u002Fshots\u002Fuptime-monitor-mobile.webp",{"slug":296,"title":297,"type":109,"category":109,"lang":118,"kind":278,"live_url":298,"stack":299,"description":301,"highlights":302,"run":307,"hue":308,"og":309,"shots":310},"job-queue","Conveyor: job queue","https:\u002F\u002Fjob-queue.ewsir.space",[118,143,282,281,300,142],"Prometheus","A background job queue: priorities, delayed and cron jobs, retries with backoff and jitter, dead-letter, idempotency keys, leases with fencing, Prometheus metrics.",[303,304,305,306],"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":311,"desktop_sm":312,"mobile":313,"dw":202,"dh":203,"mw":204,"mh":205},"\u002Fshots\u002Fjob-queue-desktop.webp","\u002Fshots\u002Fjob-queue-desktop-sm.webp","\u002Fshots\u002Fjob-queue-mobile.webp",{"slug":315,"title":316,"type":109,"category":109,"lang":118,"kind":317,"live_url":318,"stack":319,"description":322,"highlights":323,"run":328,"hue":329,"og":330,"shots":331},"realtime-rooms","Rooms: realtime chat and board","realtime","https:\u002F\u002Frealtime-rooms.ewsir.space",[118,320,143,321,142],"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.",[324,325,326,327],"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":332,"desktop_sm":333,"mobile":334,"dw":202,"dh":203,"mw":204,"mh":205},"\u002Fshots\u002Frealtime-rooms-desktop.webp","\u002Fshots\u002Frealtime-rooms-desktop-sm.webp","\u002Fshots\u002Frealtime-rooms-mobile.webp",{"slug":336,"title":337,"type":109,"category":109,"lang":118,"kind":278,"live_url":338,"stack":339,"description":346,"highlights":347,"run":352,"hue":353,"og":354,"shots":355},"api-gateway","Sluice: API gateway","https:\u002F\u002Fapi-gateway.ewsir.space",[118,176,340,341,342,281,300,343,344,345,142,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.",[348,349,350,351],"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":356,"desktop_sm":357,"mobile":358,"dw":202,"dh":203,"mw":204,"mh":205},"\u002Fshots\u002Fapi-gateway-desktop.webp","\u002Fshots\u002Fapi-gateway-desktop-sm.webp","\u002Fshots\u002Fapi-gateway-mobile.webp",{"slug":360,"title":361,"type":109,"category":109,"lang":118,"kind":362,"live_url":363,"stack":364,"description":370,"highlights":371,"run":376,"hue":168,"og":377,"shots":378},"notes-search","Card Index: notes with search","data","https:\u002F\u002Fnotes-search.ewsir.space",[118,143,365,366,367,282,368,369,344,345,142,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.",[372,373,374,375],"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":379,"desktop_sm":380,"mobile":381,"dw":202,"dh":203,"mw":204,"mh":205},"\u002Fshots\u002Fnotes-search-desktop.webp","\u002Fshots\u002Fnotes-search-desktop-sm.webp","\u002Fshots\u002Fnotes-search-mobile.webp",{"slug":383,"title":384,"type":234,"category":21,"live_url":385,"stack":386,"description":393,"highlights":394,"run":399,"hue":400,"og":401,"shots":402},"landing-3d","OKSO T1: 3D landing page","https:\u002F\u002Fewsirdev-3d.pages.dev",[387,388,344,389,390,391,392,47],"HTML5","CSS3","WebGL2","GLSL ES 3.00","Raymarching\u002FSDF","Scroll choreography","A landing page for a fictional air sensor (demo): the object is drawn by one fragment shader on plain WebGL2 with no libraries, four SDF shapes morph on scroll, with an SVG fallback, reduced-motion support and a pause when off screen.",[395,396,397,398],"One fragment shader, WebGL2 without libraries","Four SDF shapes morphing on scroll","SVG fallback and prefers-reduced-motion","Rendering pauses off screen","cd landing-3d && python -m http.server 8094",90,"\u002Fog\u002Fen\u002Flanding-3d.jpg",{"desktop":403,"desktop_sm":404,"mobile":405,"dw":202,"dh":203,"mw":204,"mh":205},"\u002Fshots\u002Flanding-3d-desktop.webp","\u002Fshots\u002Flanding-3d-desktop-sm.webp","\u002Fshots\u002Flanding-3d-mobile.webp",{"slug":407,"title":408,"type":109,"category":109,"lang":118,"kind":362,"live_url":409,"stack":410,"description":419,"highlights":420,"run":425,"hue":426,"og":427,"shots":428},"raft-kv","Raft KV: a key-value store on Raft","https:\u002F\u002Fraft-kv.ewsir.space",[118,411,412,413,176,414,281,415,416,417,418,344,142,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).",[421,422,423,424],"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":429,"desktop_sm":430,"mobile":431,"dw":202,"dh":203,"mw":204,"mh":205},"\u002Fshots\u002Fraft-kv-desktop.webp","\u002Fshots\u002Fraft-kv-desktop-sm.webp","\u002Fshots\u002Fraft-kv-mobile.webp",{"slug":433,"title":434,"type":109,"category":109,"lang":118,"kind":435,"live_url":436,"stack":437,"description":446,"highlights":447,"run":452,"hue":453,"og":454,"shots":455},"auth-service","Keyring: an authentication service","auth","https:\u002F\u002Fauth-service.ewsir.space",[118,438,143,367,439,440,441,442,443,444,445,344,345,142,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.",[448,449,450,451],"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":456,"desktop_sm":457,"mobile":458,"dw":202,"dh":203,"mw":204,"mh":205},"\u002Fshots\u002Fauth-service-desktop.webp","\u002Fshots\u002Fauth-service-desktop-sm.webp","\u002Fshots\u002Fauth-service-mobile.webp",{"slug":460,"title":461,"type":109,"category":109,"lang":118,"kind":188,"live_url":462,"stack":463,"description":470,"highlights":471,"run":476,"hue":477,"og":478,"shots":479},"finance-tracker","Ledger: personal finance tracker","https:\u002F\u002Ffinance-tracker.ewsir.space",[118,143,367,282,464,465,59,466,467,107,468,142,47,441,469],"Nuxt 4","Vue 3","Pinia","SVG charts","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.",[472,473,474,475],"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":480,"desktop_sm":481,"mobile":482,"dw":202,"dh":203,"mw":204,"mh":205},"\u002Fshots\u002Ffinance-tracker-desktop.webp","\u002Fshots\u002Ffinance-tracker-desktop-sm.webp","\u002Fshots\u002Ffinance-tracker-mobile.webp",{"slug":484,"title":485,"type":109,"category":109,"lang":118,"kind":317,"live_url":486,"stack":487,"description":491,"highlights":492,"run":497,"hue":498,"og":499,"shots":500},"pixel-wall","Pixel Wall: a shared canvas","https:\u002F\u002Fpixel-wall.ewsir.space",[118,176,320,488,344,345,489,490,142,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.",[493,494,495,496],"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":501,"desktop_sm":502,"mobile":503,"dw":202,"dh":203,"mw":204,"mh":205},"\u002Fshots\u002Fpixel-wall-desktop.webp","\u002Fshots\u002Fpixel-wall-desktop-sm.webp","\u002Fshots\u002Fpixel-wall-mobile.webp",{"slug":160,"title":161,"type":145,"category":145,"lang":118,"live_url":505,"stack":506,"description":512,"highlights":513,"run":518,"hue":163,"og":519,"shots":520},"https:\u002F\u002Fgitpulse.ewsir.space",[118,176,175,507,508,509,510,511,47,142],"git","html\u002Ftemplate","SVG","ANSI","os\u002Fexec","A Go CLI that analyzes git repositories with no external dependencies (only git is needed): stats with colors and sparklines in the terminal, report, a self-contained HTML report with an activity heatmap, churn and hot files, author contributions, bus factor per directory, code growth by language and quiet periods (light and dark themes), diff-hotspots between revisions and a local serve on 127.0.0.1. History is read as a stream through git log -z --numstat -M. A side project.",[514,515,516,517],"Go stdlib only, a single external binary: git","A git log -z --numstat -M stream with .mailmap and renames handled","A self-contained HTML report: heatmap, churn, bus factor, light and dark themes","A synthetic history of 100,000 commits is analyzed in about 5 seconds","cd gitpulse && go run .\u002Fcmd\u002Fgitpulse stats .","\u002Fog\u002Fen\u002Fgitpulse.jpg",{"desktop":521,"desktop_sm":162,"mobile":522,"dw":202,"dh":203,"mw":204,"mh":205},"\u002Fshots\u002Fgitpulse-desktop.webp","\u002Fshots\u002Fgitpulse-mobile.webp",{"slug":63,"title":64,"type":524,"category":50,"stack":525,"description":530,"highlights":531,"run":538,"hue":66,"og":539,"shots":540},"site",[464,465,59,526,527,528,529,142,47],"Bootstrap 5","Zod","Vitest","Playwright","A delivered website for a legal and expert-appraisal firm built with Nuxt 4: 65 server-rendered pages, JSON-LD markup and 301 redirects from the previous site. Lead requests go through a server endpoint that checks Origin, caps body size and rate-limits clients.",[532,533,534,535,536,537],"65 SSR routes, generated sitemap.xml and robots.txt; indexing is switched on by an explicit env flag","Lead form: server endpoint with Origin check, 10 KB body cap, 3 requests per 10 minutes per IP, honeypot and optional captcha","JSON-LD: LegalService, WebSite, BreadcrumbList","Single-hop 301 redirects from legacy URLs and trailing slashes, security headers (CSP, HSTS)","CI\u002FCD: lint, typecheck, Vitest, build, Playwright; then a Docker image and server deploy","210 unit tests in 13 files and 4 e2e spec files","npm install && npm run build && node .output\u002Fserver\u002Findex.mjs","\u002Fog\u002Fen\u002Flegal-multipage-site.jpg",{"desktop":541,"desktop_sm":65,"mobile":542,"dw":202,"dh":203,"mw":204,"mh":205},"\u002Fshots\u002Flegal-multipage-site-desktop.webp","\u002Fshots\u002Flegal-multipage-site-mobile.webp",{"slug":92,"title":93,"type":544,"category":81,"stack":545,"description":552,"highlights":553,"run":558,"hue":95,"og":559,"shots":560},"app",[465,106,466,546,547,548,549,550,551],"Vue Router","GSAP","Express","node:sqlite","JWT (httpOnly cookie)","Resend","A demo app on Vue 3, Express and SQLite: an interactive hall map with seat booking, a user account and an admin panel with a layout editor. Passwordless sign-in via a one-time e-mail code.",[554,555,556,557],"Hall map with zones (standard, quiet, VIP, dual-monitor), a type filter and seat status","Admin panel: overview, bookings, seats, computers, users and a drag-and-drop layout editor","OTP sign-in with attempt and rate limits; the admin role is bootstrapped via an env variable","Demo data is created by a seed script on first start","npm install && npm run dev","\u002Fog\u002Fen\u002Fbad-club-booking-app.jpg",{"desktop":561,"desktop_sm":94,"mobile":562,"dw":202,"dh":203,"mw":204,"mh":205},"\u002Fshots\u002Fbad-club-booking-app-desktop.webp","\u002Fshots\u002Fbad-club-booking-app-mobile.webp",{"slug":564,"title":565,"type":234,"category":21,"live_url":566,"stack":567,"description":568,"highlights":569,"run":558,"hue":573,"og":574,"shots":575},"dovod-concept","Dovod: expert-firm landing concept","https:\u002F\u002Fewsirdev-dovod.pages.dev",[464,465,59,45,47],"A Nuxt 4 landing concept for a fictional expert-appraisal firm: service areas, a consultation form with a phone mask, a reviews carousel and a policy page. The company, reviews, figures and contacts are made up; the form sends nothing.",[570,571,572],"Form with a phone mask, validation and error hints; in the demo it states that nothing is sent","Endless reviews carousel with swipe and autoplay","Mobile action bar, responsive layout, skeleton overlay on top of server-rendered HTML",215,"\u002Fog\u002Fen\u002Fdovod-concept.jpg",{"desktop":576,"desktop_sm":577,"mobile":578,"dw":202,"dh":203,"mw":204,"mh":205},"\u002Fshots\u002Fdovod-concept-desktop.webp","\u002Fshots\u002Fdovod-concept-desktop-sm.webp","\u002Fshots\u002Fdovod-concept-mobile.webp",{"slug":68,"title":69,"type":524,"category":50,"live_url":580,"stack":581,"description":584,"highlights":585,"run":558,"hue":71,"og":589,"shots":590},"https:\u002F\u002Fewsirdev-niche.pages.dev",[465,582,583,47],"Vue Router 4","Vite 8","A Vue catalog of ten standalone landing pages for different small-business niches, each with its own palette, fonts and section structure. Every landing loads as a separate chunk; all content is demonstration content.",[586,587,588],"10 niches: medicine, furniture, renovation, online school, glamping, legal, auto service, beauty, cafe, fitness","Route-level code splitting, a shared SVG icon set","A \"demo content\" badge on the catalog and every landing","\u002Fog\u002Fen\u002Fniche-landings.jpg",{"desktop":591,"desktop_sm":70,"mobile":592,"dw":202,"dh":203,"mw":204,"mh":205},"\u002Fshots\u002Fniche-landings-desktop.webp","\u002Fshots\u002Fniche-landings-mobile.webp",{"slug":594,"title":595,"type":234,"category":21,"live_url":596,"stack":597,"description":600,"highlights":601,"run":605,"hue":606,"og":607,"shots":608},"dental-concept","Kvarts: dental clinic landing concept","https:\u002F\u002Fewsirdev-dental.pages.dev",[46,45,598,488,599,47],"JavaScript","htmlhint","A single-page landing concept for a fictional dental clinic in the Liquid Chrome Dark style: glass cards, chrome headings and a canvas-drawn 3D tooth blob. One HTML file with no build step or dependencies.",[602,603,604],"Animated canvas blob that respects prefers-reduced-motion and viewport visibility","Booking form with a phone mask and accessible error messages","CI: htmlhint and a local links and anchors check","python -m http.server 8080",190,"\u002Fog\u002Fen\u002Fdental-concept.jpg",{"desktop":609,"desktop_sm":610,"mobile":611,"dw":202,"dh":203,"mw":204,"mh":205},"\u002Fshots\u002Fdental-concept-desktop.webp","\u002Fshots\u002Fdental-concept-desktop-sm.webp","\u002Fshots\u002Fdental-concept-mobile.webp",{"slug":165,"title":166,"type":145,"category":145,"lang":121,"stack":613,"description":617,"highlights":618,"run":623,"hue":168,"og":624,"shots":625},[121,176,175,509,614,615,616,47,142],"pytest","ruff","mypy","A Python 3.11+ CLI on the standard library only: it streams nginx\u002Fapache (combined), JSON-lines, logfmt and syslog logs (gz, bz2, xz too), computes statistics and tops, clusters similar errors, finds load spikes by z-score and EWMA and builds a single HTML report with SVG charts and a heatmap. A side project.",[619,620,621,622],"Standard library only, pip install pulls nothing","Error clusters by template and anomalies with two methods (z-score and EWMA)","One self-contained HTML report: SVG, heatmap, light and dark themes","53 tests, ruff and mypy --strict; 1 million combined lines in 8.2 s (a single run, Windows, CPython 3.12.10)","logslice report access.log -o report.html","\u002Fog\u002Fen\u002Flogslice.jpg",{"desktop":626,"desktop_sm":167,"mobile":627,"dw":202,"dh":203,"mw":204,"mh":205},"\u002Fshots\u002Flogslice-desktop.webp","\u002Fshots\u002Flogslice-mobile.webp",{"slug":170,"title":171,"type":145,"category":145,"lang":118,"stack":629,"description":634,"highlights":635,"run":640,"hue":173,"og":641,"shots":642},[118,176,175,630,441,631,632,633,47,142],"AES-256-GCM","fuzzing","govulncheck","gitleaks","A Go CLI with no external modules: check compares .env with a schema, scan looks for secrets in the tree and git history, diff and doctor check environments and git hygiene, seal and unseal encrypt .env into .env.sealed (AES-256-GCM, PBKDF2 with 600k iterations), run starts a command with a clean environment, report and serve build a report. A secret value never reaches the output, the log or the HTML. A side project.",[636,637,638,639],"Secret values are never printed: only the finding type, file, line and a 4-character mask","seal and unseal: AES-256-GCM, PBKDF2 with 600k iterations, any flipped bit of the file is rejected","Fuzz tests: 4 targets of 20 seconds each without crashes; govulncheck and gitleaks are clean","Builds for windows, linux and darwin, standard library only","go run .\u002Fcmd\u002Fenvguard check","\u002Fog\u002Fen\u002Fenvguard.jpg",{"desktop":643,"desktop_sm":172,"mobile":644,"dw":202,"dh":203,"mw":204,"mh":205},"\u002Fshots\u002Fenvguard-desktop.webp","\u002Fshots\u002Fenvguard-mobile.webp",{"slug":646,"title":647,"type":109,"category":109,"lang":121,"live_url":648,"stack":649,"description":652,"highlights":653,"run":658,"hue":659,"og":660,"shots":661},"catalog-api","catalog-api: a catalog API with search","https:\u002F\u002Fcatalog-api.ewsir.space",[121,650,143,365,614,615,616,651,142,47],"FastAPI","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.",[654,655,656,657],"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":662,"desktop_sm":663,"mobile":664,"dw":202,"dh":203,"mw":204,"mh":205},"\u002Fshots\u002Fcatalog-api-desktop.webp","\u002Fshots\u002Fcatalog-api-desktop-sm.webp","\u002Fshots\u002Fcatalog-api-mobile.webp",{"slug":666,"title":667,"type":109,"category":109,"lang":59,"live_url":668,"stack":669,"description":674,"highlights":675,"run":558,"hue":573,"og":680,"shots":681},"paste-vault","Konvert: notes encrypted in the browser","https:\u002F\u002Fewsirdev-paste.ewsirdev.workers.dev",[59,670,671,672,106,107,673,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.",[676,677,678,679],"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","\u002Fog\u002Fen\u002Fpaste-vault.jpg",{"desktop":682,"desktop_sm":683,"mobile":684,"dw":202,"dh":203,"mw":204,"mh":205},"\u002Fshots\u002Fpaste-vault-desktop.webp","\u002Fshots\u002Fpaste-vault-desktop-sm.webp","\u002Fshots\u002Fpaste-vault-mobile.webp",{"slug":73,"title":74,"type":524,"category":50,"live_url":686,"stack":687,"description":692,"highlights":693,"run":698,"hue":76,"og":699,"shots":700},"https:\u002F\u002Fewsirdev-docs.pages.dev",[688,689,690,691,78,47],"Node.js","JavaScript (ESM)","zero dependencies","Markdown","A static documentation site generator written from scratch in Node.js with no dependencies: its own Markdown parser, code highlighting and offline search. The demo site about a fictional library, Sverlo, is built from 43 pages in two languages (RU and EN) and two versions (v1 and v2). A side project.",[694,695,696,697],"Own Markdown parser, code highlighting and client-side search with no libraries","43 pages, RU and EN, versions v1 and v2, hreflang, sitemap and RSS","131 tests; broken links and anchors are checked in CI","The site reads without JavaScript, the search works offline","npm test && npm run build","\u002Fog\u002Fen\u002Fdocs-engine.jpg",{"desktop":701,"desktop_sm":75,"mobile":702,"dw":202,"dh":203,"mw":204,"mh":205},"\u002Fshots\u002Fdocs-engine-desktop.webp","\u002Fshots\u002Fdocs-engine-mobile.webp",{"slug":97,"title":98,"type":544,"category":81,"live_url":704,"stack":705,"description":709,"highlights":710,"run":558,"hue":100,"og":715,"shots":716},"https:\u002F\u002Fewsirdev-typing.pages.dev",[464,465,59,706,707,708,509,107,79,78],"IndexedDB","WebAudio","PWA","A touch-typing trainer that runs entirely in the browser and makes no server requests: Nuxt 4, history in IndexedDB, key sounds on WebAudio, offline through a PWA. Time, words, quotes, custom text, code and \"weak spots\" modes, six layouts, WPM, accuracy and consistency metrics, charts in plain SVG. A side project.",[711,712,713,714],"No server and no accounts: history in IndexedDB, JSON export and import","Six layouts, modes up to adaptive drills built from your mistakes","59 unit tests and 65 e2e checks, axe without violations, CLS 0.000","A PWA: works offline after the first visit","\u002Fog\u002Fen\u002Ftyping-lab.jpg",{"desktop":717,"desktop_sm":99,"mobile":718,"dw":202,"dh":203,"mw":204,"mh":205},"\u002Fshots\u002Ftyping-lab-desktop.webp","\u002Fshots\u002Ftyping-lab-mobile.webp",{"slug":102,"title":103,"type":544,"category":81,"lang":59,"live_url":720,"stack":721,"description":723,"highlights":724,"run":558,"hue":100,"og":729,"shots":730},"https:\u002F\u002Fewsirdev-budget.ewsirdev.workers.dev",[59,106,670,671,722,107,673,79,47],"WebGL","A web app for a group's shared expenses without sign-up: a room behind a secret link, four ways to split, and a \"who pays whom\" settlement in at most n-1 transfers. A side project.",[725,726,727,728],"No accounts: a room lives behind a 128-bit secret link, with separate view and edit links","Four ways to split and multi-currency with a manual rate; settlement in at most n-1 transfers","Optimistic locking: of 4 simultaneous edits to one expense exactly one wins","60 tests (vitest in workerd, fast-check property tests), green CI, npm audit 0, axe 0 violations","\u002Fog\u002Fen\u002Fbudget-split.jpg",{"desktop":731,"desktop_sm":104,"mobile":732,"dw":202,"dh":203,"mw":204,"mh":205},"\u002Fshots\u002Fbudget-split-desktop.webp","\u002Fshots\u002Fbudget-split-mobile.webp",{"slug":734,"title":735,"type":524,"category":50,"lang":59,"live_url":736,"stack":737,"description":740,"highlights":741,"run":558,"hue":100,"og":746,"shots":747},"kladka-site","kladka-site: Kladka, a demo site for a timber-frame studio","https:\u002F\u002Fewsirdev-kladka.pages.dev",[738,59,739,509,722,79,599,47],"Astro","Content collections","A demo multi-page site for a fictional timber-frame studio: 5 projects with generated drawings, services, prices with an estimator, a journal. Company, prices and timelines are made up.",[742,743,744,745],"24 pages (19 RU, 4 EN, 404) on Astro 7 with static output and content collections","All 20 drawings are generated by code as SVG from project data; 24 OG images are rendered by a script","Strict CSP with no inline code, robots, sitemap with hreflang, llms.txt, security.txt, JSON-LD Organization\u002FService\u002FArticle\u002FFAQPage","756 links and anchors without errors, htmlhint 0, axe 0 on every page at 390 and 1280, npm audit 0","\u002Fog\u002Fen\u002Fkladka-site.jpg",{"desktop":748,"desktop_sm":749,"mobile":750,"dw":202,"dh":203,"mw":204,"mh":205},"\u002Fshots\u002Fkladka-site-desktop.webp","\u002Fshots\u002Fkladka-site-desktop-sm.webp","\u002Fshots\u002Fkladka-site-mobile.webp",{"slug":752,"title":753,"type":109,"category":109,"lang":123,"live_url":754,"stack":755,"description":761,"highlights":762,"run":767,"hue":119,"og":768,"shots":769},"rate-gate","rate-gate: a request rate limiter","https:\u002F\u002Frate-gate.ewsir.space",[123,756,757,758,759,760,300,142,47],"axum","tokio","proptest","musl","distroless","An HTTP service in Rust: one POST \u002Fv1\u002Fcheck call says whether an action may pass. Two algorithms: a token bucket on integer milli-tokens (bursts) and a sliding window over two counters (strict limits). JSON rules chosen by key prefix, Prometheus metrics, a state snapshot, a demo page. State lives in the memory of a single instance: replicas do not share it.",[763,764,765,766],"A token bucket without floats: milli-tokens, cost above 1, retry_after_ms and reset_ms computed","Property tests (proptest): over any period the bucket grants at most capacity + refill * elapsed","20 tests; an 8.2 MB image, 3.8 MiB RAM idle and 3.9 MiB after 200 requests","Container uid 10001, read-only, cap-drop ALL, CSP script-src 'self'","cd rate-gate && cargo run --release","\u002Fog\u002Fen\u002Frate-gate.jpg",{"desktop":770,"desktop_sm":771,"mobile":772,"dw":202,"dh":203,"mw":204,"mh":205},"\u002Fshots\u002Frate-gate-desktop.webp","\u002Fshots\u002Frate-gate-desktop-sm.webp","\u002Fshots\u002Frate-gate-mobile.webp",{"slug":774,"title":775,"type":109,"category":109,"lang":123,"live_url":776,"stack":777,"description":780,"highlights":781,"run":786,"hue":787,"og":788,"shots":789},"hook-relay","hook-relay: webhook delivery","https:\u002F\u002Fhook-relay.ewsir.space",[123,756,757,143,778,779,758,759,760,142,47],"HMAC-SHA256","SSRF","A webhook delivery service in Rust: it accepts an event, signs it with HMAC-SHA256, sends it to the receiver and does not lose it. On failure it retries with exponential backoff and jitter, after the attempt limit it parks the delivery in a dead-letter state where it can be requeued by hand. The receiver address is checked for SSRF after DNS: every resolved address must be public. Idempotent by Idempotency-Key, state in SQLite.",[782,783,784,785],"SSRF check after DNS resolution: loopback, private, link-local, CGNAT, ULA, IPv4-mapped, NAT64 and 6to4 are blocked","An HMAC signature over \"t.body\" with a secret per receiver; retries with jitter, dead-letter, manual retry","20 tests, including proptest: the signature fails after changing any byte, any private IPv4 is blocked","A 14.4 MB image; 3.5 MiB idle, 5.5 MiB after 200 events with real delivery","cd hook-relay && cargo run --release",28,"\u002Fog\u002Fen\u002Fhook-relay.jpg",{"desktop":790,"desktop_sm":791,"mobile":792,"dw":202,"dh":203,"mw":204,"mh":205},"\u002Fshots\u002Fhook-relay-desktop.webp","\u002Fshots\u002Fhook-relay-desktop-sm.webp","\u002Fshots\u002Fhook-relay-mobile.webp",{"slug":794,"title":795,"type":109,"category":109,"lang":123,"live_url":796,"stack":797,"description":799,"highlights":800,"run":805,"hue":168,"og":806,"shots":807},"flag-store","flag-store: feature flags with percentage rollout","https:\u002F\u002Fflag-store.ewsir.space",[123,756,757,143,281,798,758,759,760,142,47],"SHA-256","A feature flag service in Rust: per-environment flags, rules on user attributes, a percentage rollout with no randomness, an audit log and an SSE stream of changes. A user bucket is the first 8 bytes of SHA-256 over the flag and the id, modulo 10000, so the answer is stable and raising the percentage never switches anyone off. Shares are in basis points, there are no floats.",[801,802,803,804],"A deterministic rollout: SHA-256(flag, id) modulo 10000; one user always gets one answer","3 property tests: stability, monotonicity in the percentage, uniformity over 20,000 users within 2 pp","15 tests; an 11.6 MB image, 2.7 MiB RAM idle and 3.5 MiB after 200 evaluate calls","An SSE stream of changes with hello, change and resync; an audit log with the author and the before and after state","cd flag-store && cargo run --release","\u002Fog\u002Fen\u002Fflag-store.jpg",{"desktop":808,"desktop_sm":809,"mobile":810,"dw":202,"dh":203,"mw":204,"mh":205},"\u002Fshots\u002Fflag-store-desktop.webp","\u002Fshots\u002Fflag-store-desktop-sm.webp","\u002Fshots\u002Fflag-store-mobile.webp",{"slug":182,"title":812,"type":109,"category":109,"lang":123,"live_url":813,"stack":814,"description":816,"highlights":817,"run":822,"hue":823,"og":824,"shots":825},"ledger-core: double entry and a hash-chained journal","https:\u002F\u002Fledger-core.ewsir.space",[123,756,143,798,758,759,760,142,815,47],"cargo-audit","A double-entry accounting core in Rust: accounts, postings, an immutable journal with a hash chain, an integrity check and a trial balance. Money is integers only (i64, minor units), a transaction sums to exactly 0, arithmetic is checked. Each record stores prev_hash and a SHA-256 of its fields; GET \u002Fv1\u002Fverify recomputes the chain and names the tampered record. Idempotent by Idempotency-Key.",[818,819,820,821],"The posting invariant: lines sum to 0, 2 to 100 lines, one currency; overflow gives 422, not a panic","The hash chain: any change to an amount, memo or account is caught and the tampered record is named","19 tests, property tests: a posting and the balance of all accounts always sum to 0","An 11.4 MB image; 3.5 MiB idle and 4.5 MiB after 200 requests, the chain stayed intact","cd ledger-core && cargo run --release",52,"\u002Fog\u002Fen\u002Fledger-core.jpg",{"desktop":826,"desktop_sm":827,"mobile":828,"dw":202,"dh":203,"mw":204,"mh":205},"\u002Fshots\u002Fledger-core-desktop.webp","\u002Fshots\u002Fledger-core-desktop-sm.webp","\u002Fshots\u002Fledger-core-mobile.webp",{"slug":830,"title":831,"type":109,"category":109,"lang":121,"live_url":832,"stack":833,"description":838,"highlights":839,"run":844,"hue":477,"og":845,"shots":846},"csv-doctor","csv-doctor: CSV profiling and cleaning","https:\u002F\u002Fcsv-doctor.ewsir.space",[121,650,834,835,836,615,837,142,47],"uvicorn","hypothesis","mypy --strict","pip-audit","A CSV check-and-clean service in Python (FastAPI, no pandas). Upload a file up to 5 MB: the encoding (utf-8, cp1251, koi8-r, UTF-16 with BOM) and delimiter are detected, you get a column profile and a list of problems with line numbers and hints, then download a cleaned CSV. Dangerous cells (=, +, -, @) are caught as CSV injection. The file is parsed in memory and never stored.",[840,841,842,843],"Encoding and delimiter autodetection, a column profile, outliers by 1.5·IQR, ambiguous dates","CSV-injection protection on input (an error) and on cleaning output (an apostrophe); +7 phone numbers are left alone","43 tests; hypothesis: an encoding round trip, idempotent cleaning clean(clean(x)) == clean(x)","CI: ruff, mypy --strict, pytest --cov, pip-audit over a hash-pinned requirements.lock; a 3.8 MB file (95,000 rows) is parsed in 1.5 s","cd csv-doctor && python -m app","\u002Fog\u002Fen\u002Fcsv-doctor.jpg",{"desktop":847,"desktop_sm":848,"mobile":849,"dw":202,"dh":203,"mw":204,"mh":205},"\u002Fshots\u002Fcsv-doctor-desktop.webp","\u002Fshots\u002Fcsv-doctor-desktop-sm.webp","\u002Fshots\u002Fcsv-doctor-mobile.webp",{"slug":851,"title":852,"type":109,"category":109,"lang":121,"live_url":853,"stack":854,"description":856,"highlights":857,"run":862,"hue":353,"og":863,"shots":864},"cron-clock","cron-clock: cron explained and next runs","https:\u002F\u002Fcron-clock.ewsir.space",[121,650,834,855,835,836,615,837,142,47],"zoneinfo","A cron-expression service in Python (FastAPI, standard library and zoneinfo only). It explains an expression in Russian and English, computes the next runs in a given time zone with daylight saving transitions, warns about \"dangerous\" schedules and compares two expressions. L, W, # and six fields with seconds are supported.",[858,859,860,861],"Next runs across DST: the Europe\u002FBerlin boundaries of 2024-03-31 and 2024-10-27, Lord Howe with its half-hour shift","64 tests; hypothesis with 150 examples per property: a run satisfies the expression, no matching minute is skipped (checked by brute force)","Comparing `* * * * * *` against `* * * * *` over 400 days in 0.6 s","\u002Fapi\u002Fparse: about 800 requests per second, p95 35 ms; 37 MiB RAM idle","cd cron-clock && python -m app","\u002Fog\u002Fen\u002Fcron-clock.jpg",{"desktop":865,"desktop_sm":866,"mobile":867,"dw":202,"dh":203,"mw":204,"mh":205},"\u002Fshots\u002Fcron-clock-desktop.webp","\u002Fshots\u002Fcron-clock-desktop-sm.webp","\u002Fshots\u002Fcron-clock-mobile.webp",{"slug":869,"title":870,"type":109,"category":109,"lang":121,"live_url":871,"stack":872,"description":875,"highlights":876,"run":881,"hue":882,"og":883,"shots":884},"ical-forge","ical-forge: iCalendar (RFC 5545), RRULE and subscription","https:\u002F\u002Fical-forge.ewsir.space",[121,650,834,143,873,874,835,836,615,142,47],"RFC 5545","ETag","An iCalendar service in Python (FastAPI + SQLite, no third-party calendar libraries). It builds a valid .ics (line folding, escaping, VTIMEZONE from zoneinfo), validates a foreign file with line numbers, expands recurring events by RRULE and publishes subscription feeds by link with ETag and 304 responses.",[877,878,879,880],"28 recurrence examples from RFC 5545 §3.8.5.3 are checked against date lists (BYSETPOS, \"second-to-last Monday\", Friday the 13th with EXDATE)","119 tests; hypothesis: an event → .ics → event round trip over arbitrary Unicode, unfold(fold(x)) == x, lines of at most 75 octets","POST \u002Fapi\u002Fexpand of 500 occurrences in 0.2 s; a feed at about 640 requests per second, p95 45 ms","The edit key is stored only as a SHA-256, compared in constant time","cd ical-forge && python -m app",280,"\u002Fog\u002Fen\u002Fical-forge.jpg",{"desktop":885,"desktop_sm":886,"mobile":887,"dw":202,"dh":203,"mw":204,"mh":205},"\u002Fshots\u002Fical-forge-desktop.webp","\u002Fshots\u002Fical-forge-desktop-sm.webp","\u002Fshots\u002Fical-forge-mobile.webp",{"slug":889,"title":890,"type":109,"category":109,"lang":59,"live_url":891,"stack":892,"description":894,"highlights":895,"run":900,"hue":901,"og":902,"shots":903},"poll-board","poll-board: real-time polls","https:\u002F\u002Fpoll-board.ewsir.space",[59,893,281,320,673,107,79,142,47],"Node.js 24","A polling service in TypeScript with no runtime dependencies. A poll of 2 to 10 options with a lifetime, a link, results growing for everyone in real time over SSE or WebSocket (WebSocket frames are parsed by hand). Repeat votes and stuffing are cut by a signed token and an IP hash; the CSV export is protected from formula cells.",[896,897,898,899],"SSE and WebSocket over one event stream; order is monotonic: seq grows by 1, closed comes last","43 tests; fast-check: the vote total equals the accepted ballots, WebSocket frames under any stream chunking","200 requests (20 in parallel): p50 6.4 ms, p95 21.7 ms; about 25 MiB RAM","Run read-only, cap-drop ALL, uid 10001; axe 0 in Firefox at 390 and 1280","cd poll-board && npm ci && npm run build && npm start",330,"\u002Fog\u002Fen\u002Fpoll-board.jpg",{"desktop":904,"desktop_sm":905,"mobile":906,"dw":202,"dh":203,"mw":204,"mh":205},"\u002Fshots\u002Fpoll-board-desktop.webp","\u002Fshots\u002Fpoll-board-desktop-sm.webp","\u002Fshots\u002Fpoll-board-mobile.webp",{"slug":908,"title":909,"type":109,"category":109,"lang":59,"live_url":910,"stack":911,"description":914,"highlights":915,"run":920,"hue":921,"og":922,"shots":923},"md-safe","md-safe: a Markdown parser and sanitizer","https:\u002F\u002Fmd-safe.ewsir.space",[59,893,691,912,913,673,107,79,142,47],"XSS","allowlist","An API and web demo for safe Markdown-to-HTML rendering in TypeScript. The CommonMark-subset parser is written from scratch and the result always passes an allowlist sanitizer (also own): there is no markdown-it, marked or dompurify in the project. Verification: a corpus of 242 XSS vectors and an independent output checker that does not use the sanitizer code.",[916,917,918,919],"242 XSS vectors (OWASP, PortSwigger, HTML5 Security Cheatsheet) run as HTML and as Markdown in several modes","An independent output checker: a strict template, any extra tag, attribute or scheme counts as a violation","829 tests, including fast-check: the output passes the checker, the sanitizer is idempotent","Heavy inputs (60 thousand \"[\", 100 thousand \"\u003Cb>\") finish in under 3 seconds: parsing is linear","cd md-safe && npm ci && npm run build && npm start",240,"\u002Fog\u002Fen\u002Fmd-safe.jpg",{"desktop":924,"desktop_sm":925,"mobile":926,"dw":202,"dh":203,"mw":204,"mh":205},"\u002Fshots\u002Fmd-safe-desktop.webp","\u002Fshots\u002Fmd-safe-desktop-sm.webp","\u002Fshots\u002Fmd-safe-mobile.webp",{"slug":928,"title":929,"type":109,"category":109,"lang":59,"live_url":930,"stack":931,"description":934,"highlights":935,"run":940,"hue":941,"og":942,"shots":943},"qr-svg","qr-svg: QR codes from scratch","https:\u002F\u002Fqr-svg.ewsir.space",[59,893,932,933,509,673,107,79,142,47],"Reed-Solomon","GF(256)","A QR-code generator that outputs SVG, in TypeScript with zero runtime dependencies: the whole ISO\u002FIEC 18004 standard for versions 1 to 40 is written in src\u002Fqr\u002F (the GF(256) field, Reed-Solomon, block interleaving, 8 masks with penalties). The API returns SVG, the web demo previews, downloads SVG and PNG and checks colour contrast against WCAG.",[936,937,938,939],"Versions 1 to 40, levels L, M, Q, H; the ISO examples \"01234567\" and \"HELLO WORLD\" match byte for byte in the correction codewords","Matrices are compared bit for bit with the independent qrcode-generator on 12 texts × 4 levels and 80 random strings","All 160 version × level combinations are read by the third-party decoder jsQR except 23-L (the reference is not read either); an own decoder exists too","A 244 MB image; run read-only, cap-drop ALL, uid 10001","cd qr-svg && npm ci && npm run build && npm start",100,"\u002Fog\u002Fen\u002Fqr-svg.jpg",{"desktop":944,"desktop_sm":945,"mobile":946,"dw":202,"dh":203,"mw":204,"mh":205},"\u002Fshots\u002Fqr-svg-desktop.webp","\u002Fshots\u002Fqr-svg-desktop-sm.webp","\u002Fshots\u002Fqr-svg-mobile.webp",{"slug":948,"title":949,"type":524,"category":50,"lang":59,"live_url":950,"stack":951,"description":956,"highlights":957,"run":962,"hue":963,"og":964,"shots":965},"bike-workshop-react","bike-workshop-react: \"Os\", a bike workshop site concept","https:\u002F\u002Fewsirdev-bike.pages.dev",[952,953,59,106,954,955,79,78,47],"React 19","React Router","Prerender","CSP","A concept for a multi-page site of a fictional bike workshop on React 19, with every page prerendered to static HTML: services, a demo price list with a calculator, booking with a schedule, a three-article journal. The workshop, clients and real prices do not exist; the booking form works in the browser and sends nothing.",[958,959,960,961],"React 19 + React Router + Vite, every page prerendered to a flat .html (slash-less URLs without a redirect on Pages)","CSP script-src 'self' and style-src 'self' with no unsafe-inline: check:security fails the build on inline code","Signature devices: a ruler as a divider, a tag with a tear-off stub for estimates and bookings","Firefox at 390, 768, 1280, 1920: axe 0, CLS 0, no horizontal scroll and no console errors","cd bike-workshop-react && npm ci && npm run dev",18,"\u002Fog\u002Fen\u002Fbike-workshop-react.jpg",{"desktop":966,"desktop_sm":967,"mobile":968,"dw":202,"dh":203,"mw":204,"mh":205},"\u002Fshots\u002Fbike-workshop-react-desktop.webp","\u002Fshots\u002Fbike-workshop-react-desktop-sm.webp","\u002Fshots\u002Fbike-workshop-react-mobile.webp",{"slug":970,"title":971,"type":524,"category":50,"lang":59,"live_url":972,"stack":973,"description":975,"highlights":976,"run":981,"hue":982,"og":983,"shots":984},"landscape-bureau-nuxt","landscape-bureau-nuxt: \"Izolinia\", a landscape bureau site concept","https:\u002F\u002Fewsirdev-landscape.pages.dev",[464,465,59,974,509,955,79,78,47],"Static generation","A concept for a multi-page site of a fictional landscape bureau on Nuxt 4 with static generation: six project pages on a dynamic route, a plant filter by light, moisture and shape, a brief form. Data lives in JSON, the site plans are drawn by code. The bureau, clients and areas are made up.",[977,978,979,980],"Nuxt 4 generate: every dynamic route is prerendered","CSP without unsafe-inline on Nuxt: entryImportMap and inline styles are off, window.__NUXT__ is moved into _nuxt\u002Finline-\u003Chash>.js, check-security fails the build on an inline script","Site plans are generated by code (SVG from project data)","Firefox at 390, 768, 1280, 1920: axe, CLS, horizontal scroll and console checked","cd landscape-bureau-nuxt && npm ci && npm run dev",130,"\u002Fog\u002Fen\u002Flandscape-bureau-nuxt.jpg",{"desktop":985,"desktop_sm":986,"mobile":987,"dw":202,"dh":203,"mw":204,"mh":205},"\u002Fshots\u002Flandscape-bureau-nuxt-desktop.webp","\u002Fshots\u002Flandscape-bureau-nuxt-desktop-sm.webp","\u002Fshots\u002Flandscape-bureau-nuxt-mobile.webp",{"slug":989,"title":990,"type":524,"category":50,"lang":59,"live_url":991,"stack":992,"description":995,"highlights":996,"run":1001,"hue":173,"og":1002,"shots":1003},"letterpress-ts","letterpress-ts: \"Reglet\", a letterpress workshop site concept","https:\u002F\u002Fewsirdev-letterpress.pages.dev",[59,106,993,994,45,79,78,47],"Multi-page","DOM","A concept for a multi-page site of a fictional letterpress workshop in plain TypeScript with no framework: Vite multi-page with 8 HTML entries, components as typed functions returning DOM. A type composer turns text into sorts and an impression, a calculator prices a run, state is kept in the link. Do not trust the workshop or the prices: sums come from demo formulas.",[997,998,999,1000],"No framework: 8 Vite HTML entries, components as functions returning DOM","A type composer: typeface, ink, paper, size, spacing; state in the link with parameter validation","The price table on the home page is computed at build time with the same formulas as the calculator","User text enters the DOM through textContent, innerHTML is banned by the linter","cd letterpress-ts && npm ci && npm run dev","\u002Fog\u002Fen\u002Fletterpress-ts.jpg",{"desktop":1004,"desktop_sm":1005,"mobile":1006,"dw":202,"dh":203,"mw":204,"mh":205},"\u002Fshots\u002Fletterpress-ts-desktop.webp","\u002Fshots\u002Fletterpress-ts-desktop-sm.webp","\u002Fshots\u002Fletterpress-ts-mobile.webp",{"slug":1008,"title":1009,"type":524,"category":50,"lang":59,"live_url":1010,"stack":1011,"description":1013,"highlights":1014,"run":1019,"hue":453,"og":1020,"shots":1021},"ceramics-vue","ceramics-vue: \"Muffle\", a ceramics studio site concept","https:\u002F\u002Fewsirdev-ceramics.pages.dev",[465,546,59,106,1012,509,955,79,78,47],"vite-ssg","A concept for a fictional ceramics studio on Vue 3 with every page prerendered to static HTML (vite-ssg): 7 pages, 5 dynamic course pages and a 404. A schedule with filters, a trial-class booking via mailto, a kiln calculator with an SVG curve. The studio, reviews and prices do not exist; formulas are marked \"demo\".",[1015,1016,1017,1018],"Vue 3 + vite-ssg: 7 pages and 5 course pages prerendered to HTML","A kiln calculator: volume, weight, drying, programme, duration and an SVG curve from demo formulas","CSP script-src 'self' with no inline scripts; check-security fails the build on inline code or style","Firefox at 390, 768, 1280, 1920: axe 0, CLS 0, clean console; CI: lint, typecheck, test, build, check:links, npm audit, gitleaks","cd ceramics-vue && npm ci && npm run dev","\u002Fog\u002Fen\u002Fceramics-vue.jpg",{"desktop":1022,"desktop_sm":1023,"mobile":1024,"dw":202,"dh":203,"mw":204,"mh":205},"\u002Fshots\u002Fceramics-vue-desktop.webp","\u002Fshots\u002Fceramics-vue-desktop-sm.webp","\u002Fshots\u002Fceramics-vue-mobile.webp",{"slug":1026,"title":1027,"type":544,"category":81,"lang":59,"live_url":1028,"stack":1029,"description":1032,"highlights":1033,"run":1038,"hue":1039,"og":1040,"shots":1041},"habit-tracker","habit-tracker: \"Habit\", an offline habit tracker (demo)","https:\u002F\u002Fewsirdev-habit.pages.dev",[1030,59,106,708,1031,673,107,79,78,47],"Svelte 5","Service Worker","A demo app: a habit tracker on Svelte 5 that works offline (PWA) with no account and no server; data stays in the browser. Streaks with a grace day, daily, weekday or N-times-a-week frequency, a year heatmap in SVG, reminders through the Notification API, JSON export and import with strict schema checks. A day is stored as a string in the user's time zone, so travel and daylight saving do not change history.",[1034,1035,1036,1037],"Streaks with a grace day: one isolated miss does not break a streak, at most once in 7 days","Days as YYYY-MM-DD strings in the user's zone; arithmetic through UTC midnight; the day boundary can be shifted","Offline: a service worker with a versioned cache; CSP connect-src 'self', no external requests","29 vitest tests, including fast-check; targets of at least 44 px, axe 0","cd habit-tracker && npm ci && npm run dev",75,"\u002Fog\u002Fen\u002Fhabit-tracker.jpg",{"desktop":1042,"desktop_sm":1043,"mobile":1044,"dw":202,"dh":203,"mw":204,"mh":205},"\u002Fshots\u002Fhabit-tracker-desktop.webp","\u002Fshots\u002Fhabit-tracker-desktop-sm.webp","\u002Fshots\u002Fhabit-tracker-mobile.webp",{"slug":1046,"title":1047,"type":544,"category":81,"lang":59,"live_url":1048,"stack":1049,"description":1051,"highlights":1052,"run":1057,"hue":1058,"og":1059,"shots":1060},"flashcards-sm2","flashcards-sm2: \"Cards\", a flashcard box with SM-2 spaced repetition","https:\u002F\u002Fewsirdev-cards.pages.dev",[465,59,106,706,708,1050,673,107,79,78,47],"SM-2","A concept app on Vue 3: decks of cards and review by the SM-2 algorithm, offline and with no backend, data in IndexedDB. CSV\u002FTSV import detects the delimiter and encoding itself (UTF-8, UTF-16 by BOM, Windows-1251), export is protected from CSV injection, and there is a full JSON backup with strict validation. SVG statistics: a 30-day forecast, card maturity, retention.",[1053,1054,1055,1056],"SM-2 as in the SuperMemo-2 description: EF from 1.3, intervals 1, 6, then round(prev·EF); differences from the original are documented","The schedule is stored as dates, not timestamps: time zones and daylight saving shift nothing; the day boundary can be moved","CSV import with encoding and delimiter autodetection, limits of 1 MB and 5,000 rows","30 vitest tests + fast-check; a card flip in pure CSS, keyboard (space, 1 to 4), axe 0","cd flashcards-sm2 && npm ci && npm run dev",300,"\u002Fog\u002Fen\u002Fflashcards-sm2.jpg",{"desktop":1061,"desktop_sm":1062,"mobile":1063,"dw":202,"dh":203,"mw":204,"mh":205},"\u002Fshots\u002Fflashcards-sm2-desktop.webp","\u002Fshots\u002Fflashcards-sm2-desktop-sm.webp","\u002Fshots\u002Fflashcards-sm2-mobile.webp",{"slug":1065,"title":1066,"type":544,"category":81,"lang":59,"live_url":1067,"stack":1068,"description":1071,"highlights":1072,"run":1077,"hue":1078,"og":1079,"shots":1080},"scratch-notes","scratch-notes: \"Draft\", an offline Markdown note editor","https:\u002F\u002Fewsirdev-scratch.pages.dev",[1069,59,106,706,1070,708,673,107,79,78,47],"SolidJS","WebCrypto","A demo concept of an offline Markdown note editor on SolidJS: data in IndexedDB, no server and no accounts. A live preview, an own full-text search (BM25, RU\u002FEN stemming, keyboard-layout correction), edit history with a line diff, per-note password encryption (PBKDF2 with 600,000 iterations + AES-256-GCM, WebCrypto), .md and .zip import and export with an own ZIP writer.",[1073,1074,1075,1076],"The Markdown parser and sanitizer are copied from md-safe together with the XSS vector corpus and the independent checker","Encryption: PBKDF2-HMAC-SHA-256 with 600,000 iterations, AES-256-GCM, a fresh IV on every save, history snapshots are encrypted too; the threat model is written down","Search: an own in-memory index, BM25 with field weights, prefix, \"phrases\", #tags","777 tests (including the XSS corpus); hotkeys by e.code on any layout; axe 0","cd scratch-notes && npm ci && npm run dev",55,"\u002Fog\u002Fen\u002Fscratch-notes.jpg",{"desktop":1081,"desktop_sm":1082,"mobile":1083,"dw":202,"dh":203,"mw":204,"mh":205},"\u002Fshots\u002Fscratch-notes-desktop.webp","\u002Fshots\u002Fscratch-notes-desktop-sm.webp","\u002Fshots\u002Fscratch-notes-mobile.webp",{"slug":1085,"title":1086,"type":145,"category":145,"lang":123,"stack":1087,"description":1090,"highlights":1091,"run":1096,"hue":1097,"og":1098,"shots":1099},"dupfind","dupfind: fast duplicate file finder",[123,1088,1089,758,175,47,142],"BLAKE3","rayon","A Rust CLI that finds duplicate files. Files are grouped by size, then by a partial hash (the first and last 4 KiB) and only then by a full BLAKE3; walking and hashing are parallel. By default it only shows the result; deleting, hard-linking and moving are enabled with --apply and re-check the hash before acting. Hard links to one file are not counted as duplicates, symlinks are not followed.",[1092,1093,1094,1095],"A demo directory of 10,000 files (307 MB, a third are copies): about 120 ms on 20 threads, about 200 ms on one; a find + sha256sum pipeline takes about 1130 ms","Size, partial hash, full BLAKE3: unique sizes are dropped without reading","--delete-newer, --hardlink and --move-to are dry-run by default; exit code 3 for CI","18 integration tests and proptest: groups are exactly the classes of identical content","cd dupfind && cargo build --release && .\u002Ftarget\u002Frelease\u002Fdupfind --help",22,"\u002Fog\u002Fen\u002Fdupfind.jpg",{"desktop":1100,"desktop_sm":1101,"mobile":1102,"dw":202,"dh":203,"mw":204,"mh":205},"\u002Fshots\u002Fdupfind-desktop.webp","\u002Fshots\u002Fdupfind-desktop-sm.webp","\u002Fshots\u002Fdupfind-mobile.webp",{"slug":1104,"title":1105,"type":145,"category":145,"lang":118,"stack":1106,"description":1110,"highlights":1111,"run":1116,"hue":1117,"og":1118,"shots":1119},"sqlcsv","sqlcsv: SQL queries over CSV from the command line",[118,176,1107,1108,631,1109,47],"SQL","CSV","SQLite (oracle)","A Go CLI for SQL queries over CSV and TSV. One static binary, standard library only: an own lexer, parser and interpreter. Streaming processing: SELECT, WHERE, GROUP BY, HAVING, ORDER BY, LIMIT, a two-file JOIN (hash join), \"did you mean\" hints in errors, --explain and output protection from CSV injection (--csv-safe).",[1112,1113,1114,1115],"A demo CSV of 1,000,000 rows (39 MB): a GROUP BY aggregate in 0.23 s and 13 MB of memory; an early stop on LIMIT in 0.03 s","An oracle test: 600 random queries are compared with SQLite (a separate module), numbers to 1e-9","Fuzzing of the parser and engine: any string as a query does not panic","Errors with a position and a column-name hint","cd sqlcsv && go run . \"SELECT * FROM testdata\u002Fdemo-sales.csv\"",195,"\u002Fog\u002Fen\u002Fsqlcsv.jpg",{"desktop":1120,"desktop_sm":1121,"mobile":1122,"dw":202,"dh":203,"mw":204,"mh":205},"\u002Fshots\u002Fsqlcsv-desktop.webp","\u002Fshots\u002Fsqlcsv-desktop-sm.webp","\u002Fshots\u002Fsqlcsv-mobile.webp",{"slug":1124,"title":1125,"type":145,"category":145,"lang":59,"stack":1126,"description":1130,"highlights":1131,"run":1136,"hue":1137,"og":1138,"shots":1139},"lockweigh","lockweigh: \"weighs\" a lock file",[59,688,1127,1128,1129,107,47],"esbuild","Node SEA","YAML parser","A TypeScript CLI with no runtime dependencies: it reads package-lock.json (v2, v3), pnpm-lock.yaml (v9) and yarn.lock (classic v1) and shows how many packages a project has, who pulls duplicate versions, which subtrees are heaviest, where install scripts are and which licences, plus why a package is in the project (why) and what changed between two lock files (diff). The parsers, including a YAML subset, are written from scratch. It works offline and executes nothing.",[1132,1133,1134,1135],"npm, pnpm and yarn in one report; on the express fixture: 73 packages, graph depth 7, 2 duplicate versions","The why command (every path from the root) and diff of two lock files; exit codes for CI with thresholds","One esbuild bundle and a single executable through Node SEA","77 vitest tests; no runtime dependencies, so the supply chain is dev tooling only","cd lockweigh && npm ci && npm run build && node dist\u002Flockweigh.cjs --help",340,"\u002Fog\u002Fen\u002Flockweigh.jpg",{"desktop":1140,"desktop_sm":1141,"mobile":1142,"dw":202,"dh":203,"mw":204,"mh":205},"\u002Fshots\u002Flockweigh-desktop.webp","\u002Fshots\u002Flockweigh-desktop-sm.webp","\u002Fshots\u002Flockweigh-mobile.webp",1793371809595]