[{"data":1,"prerenderedAt":746},["ShallowReactive",2],{"api:\u002Fapi\u002Fconfig:ru":3,"api:\u002Fapi\u002Fprofile:":8,"api:\u002Fapi\u002Fcategories:ru":19,"api:\u002Fapi\u002Fnotes\u002Flease-and-fencing:ru":171,"api:\u002Fapi\u002Fprojects:ru":179},{"email":4,"lang":5,"name":6,"role":7},"djrciv@gmail.com","ru","Ewsirdev","Веб-разработчик: сайты, лендинги и сервисы на Go",{"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","Лендинги","Одна страница, одна задача. Вёрстка без фреймворков, где это возможно, и один WebGL-эксперимент.","ink","cold","shots",6,[29,34,39],{"slug":30,"title":31,"shot":32,"hue":33},"landing-studio","Полутон: студия света","\u002Fshots\u002Flanding-studio-desktop-sm.webp",212,{"slug":35,"title":36,"shot":37,"hue":38},"landing-saas","Рубильник: трекер подписок","\u002Fshots\u002Flanding-saas-desktop-sm.webp",182,{"slug":40,"title":41,"shot":42,"hue":43},"landing-event","Малый масштаб: конференция","\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","Многостраничные сайты","Сайты со своей структурой, маршрутами и формами: от клиентского проекта до набора нишевых страниц.","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","Многостраничный сайт юридической компании","\u002Fshots\u002Flegal-multipage-site-desktop-sm.webp",250,{"slug":68,"title":69,"shot":70,"hue":71},"niche-landings","Каталог демо-лендингов под 10 ниш","\u002Fshots\u002Fniche-landings-desktop-sm.webp",32,{"slug":73,"title":74,"shot":75,"hue":76},"docs-engine","Сверло: генератор документации на 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","Веб-приложения","Интерфейс плюс сервер: авторизация, данные, письма. Приложения, в которые входят.","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: бронирование мест в игровом коворкинге","\u002Fshots\u002Fbad-club-booking-app-desktop-sm.webp",150,{"slug":97,"title":98,"shot":99,"hue":100},"typing-lab","typing-lab: тренажёр слепой печати","\u002Fshots\u002Ftyping-lab-desktop-sm.webp",45,{"slug":102,"title":103,"shot":104,"hue":100},"budget-split","budget-split: «Поровну», учёт общих расходов","\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","Бэкенды","Сервисы с API, базой и тестами: в основном на Go, есть и на Python и TypeScript. Фильтр по языку внутри раздела.","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","Янтарь: канбан-доска","\u002Fshots\u002Fkanban-tasks-desktop-sm.webp",344,{"slug":130,"title":131,"shot":132,"hue":133},"link-shortener","Короче: сократитель ссылок","\u002Fshots\u002Flink-shortener-desktop-sm.webp",111,{"slug":135,"title":136,"shot":137,"hue":138},"booking-slots","Запись на приём","\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-утилиты","Программы для терминала: один бинарник, понятные флаги, код выхода, который можно проверить в скрипте.","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-репозиториев","\u002Fshots\u002Fgitpulse-desktop-sm.webp",7,{"slug":159,"title":160,"shot":161,"hue":162},"logslice","logslice: анализ логов без зависимостей","\u002Fshots\u002Flogslice-desktop-sm.webp",160,{"slug":164,"title":165,"shot":166,"hue":167},"envguard","envguard: линтер и сейф .env-файлов","\u002Fshots\u002Fenvguard-desktop-sm.webp",35,[169,140,47,170],"CLI","stdlib only",{"slug":172,"lang":5,"title":173,"date":174,"summary":175,"project":176,"minutes":150,"body_md":177,"body_html":178},"lease-and-fencing","Lease и fencing: как очередь не теряет задачу и не даёт ей выполниться дважды «зомби»","2026-10-20","Воркер может упасть, зависнуть или проснуться, когда его задачу уже забрали. Разбираю, как lease и токен аренды в job-queue решают это в SQLite.","job-queue","У фоновой очереди две неприятные ситуации. Воркер упал посреди задачи, и она «зависла» в статусе running. Или воркер просто надолго замер (пауза GC, сеть), задачу отдали другому, а потом он очнулся и пишет результат поверх чужого.\n\n## Lease\n\nЗахват задачи выдаёт **аренду**: срок `lease_until` (по умолчанию 30 секунд). Пока обработчик работает, воркер продлевает аренду heartbeat-ом каждую треть срока. Если процесс упал, heartbeat прекращается, аренда истекает, и «reaper» возвращает задачу в очередь с backoff или, если попытки кончились, в dead.\n\n## Захват одним оператором\n\nВыбор задачи и выдача аренды делает один SQL-оператор:\n\n```sql\nUPDATE jobs SET status='running', attempts=attempts+1,\n  lease_until=?, worker=?, token=?, started_at=?\nWHERE id=(SELECT id FROM jobs WHERE status='pending' AND run_at\u003C=?\n          ORDER BY priority DESC, run_at ASC, id ASC LIMIT 1)\nRETURNING ...\n```\n\nПоэтому два воркера, даже из разных процессов на одном файле базы, не получат одну задачу.\n\n## Fencing\n\nКаждый захват записывает уникальный токен. Продление, завершение и ошибка выполняются с условием `WHERE status='running' AND token=?`. Если задачу уже перехватили, условие не совпадёт, и «зомби»-воркер получит `ErrLeaseLost` вместо того, чтобы испортить результат.\n\n## Что это не даёт\n\nГарантия остаётся **at-least-once**: после истечения аренды обработчик может быть вызван повторно, поэтому он должен быть идемпотентным. Exactly-once очередь не обещает. Подробности и ограничения в README проекта.\n","\u003Cp>У фоновой очереди две неприятные ситуации. Воркер упал посреди задачи, и она «зависла» в статусе running. Или воркер просто надолго замер (пауза GC, сеть), задачу отдали другому, а потом он очнулся и пишет результат поверх чужого.\u003C\u002Fp>\n\u003Ch2>Lease\u003C\u002Fh2>\n\u003Cp>Захват задачи выдаёт \u003Cstrong>аренду\u003C\u002Fstrong>: срок \u003Ccode>lease_until\u003C\u002Fcode> (по умолчанию 30 секунд). Пока обработчик работает, воркер продлевает аренду heartbeat-ом каждую треть срока. Если процесс упал, heartbeat прекращается, аренда истекает, и «reaper» возвращает задачу в очередь с backoff или, если попытки кончились, в dead.\u003C\u002Fp>\n\u003Ch2>Захват одним оператором\u003C\u002Fh2>\n\u003Cp>Выбор задачи и выдача аренды делает один SQL-оператор:\u003C\u002Fp>\n\u003Cpre tabindex=\"0\">\u003Ccode class=\"lang-sql\">UPDATE jobs SET status=&#39;running&#39;, attempts=attempts+1,\n  lease_until=?, worker=?, token=?, started_at=?\nWHERE id=(SELECT id FROM jobs WHERE status=&#39;pending&#39; AND run_at&lt;=?\n          ORDER BY priority DESC, run_at ASC, id ASC LIMIT 1)\nRETURNING ...\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>Поэтому два воркера, даже из разных процессов на одном файле базы, не получат одну задачу.\u003C\u002Fp>\n\u003Ch2>Fencing\u003C\u002Fh2>\n\u003Cp>Каждый захват записывает уникальный токен. Продление, завершение и ошибка выполняются с условием \u003Ccode>WHERE status=&#39;running&#39; AND token=?\u003C\u002Fcode>. Если задачу уже перехватили, условие не совпадёт, и «зомби»-воркер получит \u003Ccode>ErrLeaseLost\u003C\u002Fcode> вместо того, чтобы испортить результат.\u003C\u002Fp>\n\u003Ch2>Что это не даёт\u003C\u002Fh2>\n\u003Cp>Гарантия остаётся \u003Cstrong>at-least-once\u003C\u002Fstrong>: после истечения аренды обработчик может быть вызван повторно, поэтому он должен быть идемпотентным. Exactly-once очередь не обещает. Подробности и ограничения в README проекта.\u003C\u002Fp>\n",[180,199,213,226,241,255,268,288,306,327,351,374,399,427,454,478,499,518,538,558,574,588,607,623,640,660,680,699,715,729],{"slug":125,"title":126,"type":109,"category":109,"lang":118,"kind":181,"live_url":182,"stack":183,"description":184,"highlights":185,"run":190,"hue":128,"og":191,"shots":192},"api","https:\u002F\u002Fkanban-tasks.ewsir.space",[118,141,140],"Многопользовательская канбан-доска: регистрация, доски, колонки, карточки с перетаскиванием. Go + SQLite без CGO, фронт встроен в бинарник.",[186,187,188,189],"PBKDF2 и сессии в HttpOnly-cookie","Изоляция данных пользователей","Тесты API","Docker multi-stage","cd kanban-tasks && go run .","\u002Fog\u002Fkanban-tasks.jpg",{"desktop":193,"desktop_sm":127,"mobile":194,"dw":195,"dh":196,"mw":197,"mh":198},"\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":181,"live_url":200,"stack":201,"description":202,"highlights":203,"run":208,"hue":133,"og":209,"shots":210},"https:\u002F\u002Flink-shortener.ewsir.space",[118,141,140],"Сокращение ссылок с алиасами и сроком жизни, статистика кликов по дням, браузерам и устройствам, дашборд под токеном. IP не хранятся.",[204,205,206,207],"Редирект и учёт кликов","Лимит запросов","Приватная статистика (хэш с солью)","Тесты и Docker","cd link-shortener && set SEED_DEMO=1&& go run .\u002Fcmd\u002Fserver","\u002Fog\u002Flink-shortener.jpg",{"desktop":211,"desktop_sm":132,"mobile":212,"dw":195,"dh":196,"mw":197,"mh":198},"\u002Fshots\u002Flink-shortener-desktop.webp","\u002Fshots\u002Flink-shortener-mobile.webp",{"slug":135,"title":136,"type":109,"category":109,"lang":118,"kind":181,"live_url":214,"stack":215,"description":216,"highlights":217,"run":221,"hue":138,"og":222,"shots":223},"https:\u002F\u002Fbooking-slots.ewsir.space",[118,141,140],"Онлайн-запись на услуги: свободные слоты с учётом длительности и рабочих часов, защита от двойной записи, админ-панель. Вымышленная студия, демо.",[218,219,220,207],"Расчёт свободных слотов","Без двойных записей","Админка для услуг и расписания","cd booking-slots && go run .","\u002Fog\u002Fbooking-slots.jpg",{"desktop":224,"desktop_sm":137,"mobile":225,"dw":195,"dh":196,"mw":197,"mh":198},"\u002Fshots\u002Fbooking-slots-desktop.webp","\u002Fshots\u002Fbooking-slots-mobile.webp",{"slug":30,"title":31,"type":227,"category":21,"live_url":228,"stack":229,"description":230,"highlights":231,"run":236,"hue":33,"og":237,"shots":238},"landing","https:\u002F\u002Fewsirdev-studio.pages.dev",[46,45,48],"Лендинг вымышленной студии интерьерного света: тёплая палитра, засечный шрифт, SVG-иллюстрация, FAQ и демо-форма. Адаптивный, без внешних картинок.",[232,233,234,235],"Адаптив 390-1280","SVG вместо картинок","Доступные FAQ и форма","Честная демо-форма","cd landing-studio && python -m http.server 8091","\u002Fog\u002Flanding-studio.jpg",{"desktop":239,"desktop_sm":32,"mobile":240,"dw":195,"dh":196,"mw":197,"mh":198},"\u002Fshots\u002Flanding-studio-desktop.webp","\u002Fshots\u002Flanding-studio-mobile.webp",{"slug":35,"title":36,"type":227,"category":21,"live_url":242,"stack":243,"description":244,"highlights":245,"run":250,"hue":38,"og":251,"shots":252},"https:\u002F\u002Fewsirdev-saas.pages.dev",[46,45,48],"Лендинг вымышленного SaaS-продукта: тёмный градиентный hero, макет интерфейса на CSS, тарифы, FAQ, демо-форма. Все цены и названия придуманы.",[246,247,248,249],"Макет интерфейса на CSS","Тарифная сетка","Адаптив","Демо-форма без отправки данных","cd landing-saas && python -m http.server 8092","\u002Fog\u002Flanding-saas.jpg",{"desktop":253,"desktop_sm":37,"mobile":254,"dw":195,"dh":196,"mw":197,"mh":198},"\u002Fshots\u002Flanding-saas-desktop.webp","\u002Fshots\u002Flanding-saas-mobile.webp",{"slug":40,"title":41,"type":227,"category":21,"live_url":256,"stack":257,"description":258,"highlights":259,"run":263,"hue":43,"og":264,"shots":265},"https:\u002F\u002Fewsirdev-event.pages.dev",[46,45,48],"Лендинг вымышленной однодневной конференции: плакатная типографика, расписание, спикеры-заглушки, условия, демо-форма записи.",[260,261,248,262],"Расписание дня","Плакатная типографика","Без выдуманных имён спикеров","cd landing-event && python -m http.server 8093","\u002Fog\u002Flanding-event.jpg",{"desktop":266,"desktop_sm":42,"mobile":267,"dw":195,"dh":196,"mw":197,"mh":198},"\u002Fshots\u002Flanding-event-desktop.webp","\u002Fshots\u002Flanding-event-mobile.webp",{"slug":269,"title":270,"type":109,"category":109,"lang":118,"kind":271,"live_url":272,"stack":273,"description":276,"highlights":277,"run":281,"hue":282,"og":283,"shots":284},"uptime-monitor","Пульс: монитор доступности","infra","https:\u002F\u002Fuptime-monitor.ewsir.space",[118,141,274,275,140],"SSE","REST API","Монитор доступности: HTTP и TCP проверки по расписанию, аптайм, p50\u002Fp95, детектор инцидентов, живой дашборд на SSE и публичная статус-страница. Go + SQLite без CGO.",[278,279,280,207],"Горутина-воркер на каждую проверку","Инциденты после серии падений","Дашборд на SSE и публичный \u002Fstatus","cd uptime-monitor && set SEED_DEMO=1&& go run .\u002Fcmd\u002Fserver",101,"\u002Fog\u002Fuptime-monitor.jpg",{"desktop":285,"desktop_sm":286,"mobile":287,"dw":195,"dh":196,"mw":197,"mh":198},"\u002Fshots\u002Fuptime-monitor-desktop.webp","\u002Fshots\u002Fuptime-monitor-desktop-sm.webp","\u002Fshots\u002Fuptime-monitor-mobile.webp",{"slug":176,"title":289,"type":109,"category":109,"lang":118,"kind":271,"live_url":290,"stack":291,"description":293,"highlights":294,"run":299,"hue":300,"og":301,"shots":302},"Конвейер: очередь задач","https:\u002F\u002Fjob-queue.ewsir.space",[118,141,275,274,292,140],"Prometheus","Очередь фоновых задач: приоритеты, отложенные и cron-задачи, ретраи с backoff и jitter, dead-letter, идемпотентные ключи, lease с fencing, метрики Prometheus.",[295,296,297,298],"At-least-once с lease и fencing","Ретраи с backoff и jitter + dead-letter","Собственный cron-парсер","Метрики Prometheus и graceful shutdown","cd job-queue && set API_TOKEN=demo&& set SEED_DEMO=1&& go run .\u002Fcmd\u002Fserver",249,"\u002Fog\u002Fjob-queue.jpg",{"desktop":303,"desktop_sm":304,"mobile":305,"dw":195,"dh":196,"mw":197,"mh":198},"\u002Fshots\u002Fjob-queue-desktop.webp","\u002Fshots\u002Fjob-queue-desktop-sm.webp","\u002Fshots\u002Fjob-queue-mobile.webp",{"slug":307,"title":308,"type":109,"category":109,"lang":118,"kind":309,"live_url":310,"stack":311,"description":314,"highlights":315,"run":320,"hue":321,"og":322,"shots":323},"realtime-rooms","Комнаты: чат и доска в реальном времени","realtime","https:\u002F\u002Frealtime-rooms.ewsir.space",[118,312,141,313,140],"WebSocket","Canvas","Комнаты в реальном времени: чат с историей в SQLite, список «кто онлайн», индикатор «печатает», общая доска со штрихами и курсорами, анонимные ники. WebSocket (RFC 6455) написан на stdlib.",[316,317,318,319],"Свой слой WebSocket (RFC 6455) на stdlib","Отключение медленных клиентов и лимиты частоты","Graceful shutdown с кодом 1001","Нагрузочный тест: 360 соединений в 6 комнатах","cd realtime-rooms && set SEED_DEMO=1&& go run .\u002Fcmd\u002Fserver",46,"\u002Fog\u002Frealtime-rooms.jpg",{"desktop":324,"desktop_sm":325,"mobile":326,"dw":195,"dh":196,"mw":197,"mh":198},"\u002Fshots\u002Frealtime-rooms-desktop.webp","\u002Fshots\u002Frealtime-rooms-desktop-sm.webp","\u002Fshots\u002Frealtime-rooms-mobile.webp",{"slug":328,"title":329,"type":109,"category":109,"lang":118,"kind":271,"live_url":330,"stack":331,"description":338,"highlights":339,"run":344,"hue":345,"og":346,"shots":347},"api-gateway","Шлюз: API-gateway","https:\u002F\u002Fapi-gateway.ewsir.space",[118,170,332,333,334,274,292,335,336,337,140,47],"Reverse proxy","Circuit breaker","Token bucket","slog","Vanilla JS","embed","API-шлюз на чистой stdlib: маршруты по хосту\u002Fпрефиксу из JSON-конфига с горячей перезагрузкой без обрыва соединений, балансировка round-robin и least-conn, health-checks, circuit breaker на узел, ретраи идемпотентных запросов, rate limit, хэшированные API-ключи, метрики Prometheus, живой дашборд на SSE.",[340,341,342,343],"Ноль зависимостей: только stdlib","Горячая перезагрузка конфига без обрыва соединений","Circuit breaker на узел и ретраи идемпотентных запросов","Нагрузочный тест: 4000 запросов, 40 клиентов (демо-замер)","cd api-gateway && set SEED_DEMO=1&& go run .\u002Fcmd\u002Fgateway",200,"\u002Fog\u002Fapi-gateway.jpg",{"desktop":348,"desktop_sm":349,"mobile":350,"dw":195,"dh":196,"mw":197,"mh":198},"\u002Fshots\u002Fapi-gateway-desktop.webp","\u002Fshots\u002Fapi-gateway-desktop-sm.webp","\u002Fshots\u002Fapi-gateway-mobile.webp",{"slug":352,"title":353,"type":109,"category":109,"lang":118,"kind":354,"live_url":355,"stack":356,"description":362,"highlights":363,"run":368,"hue":162,"og":369,"shots":370},"notes-search","Картотека: заметки с поиском","data","https:\u002F\u002Fnotes-search.ewsir.space",[118,141,357,358,359,275,360,361,336,337,140,47],"FTS5","bm25","modernc.org\u002Fsqlite (без CGO)","Markdown (свой безопасный рендер)","SVG force-layout","База Markdown-заметок на Go и SQLite: вики-ссылки [[заметка]] с обратными ссылками и графом связей, полнотекстовый поиск FTS5 (bm25, подсветка, упрощённая русская морфология), история версий с собственным diff, автосохранение с разрешением конфликтов, экспорт и импорт .md.",[364,365,366,367],"Поиск FTS5 с bm25 и подсветкой","Вики-ссылки, backlinks и граф связей","История версий с собственным diff","Свой безопасный Markdown-рендер с тестом на XSS","cd notes-search && set SEED_DEMO=1&& go run .\u002Fcmd\u002Fserver","\u002Fog\u002Fnotes-search.jpg",{"desktop":371,"desktop_sm":372,"mobile":373,"dw":195,"dh":196,"mw":197,"mh":198},"\u002Fshots\u002Fnotes-search-desktop.webp","\u002Fshots\u002Fnotes-search-desktop-sm.webp","\u002Fshots\u002Fnotes-search-mobile.webp",{"slug":375,"title":376,"type":227,"category":21,"live_url":377,"stack":378,"description":385,"highlights":386,"run":392,"hue":393,"og":394,"shots":395},"landing-3d","ОКСО Т1: 3D-лендинг","https:\u002F\u002Fewsirdev-3d.pages.dev",[379,380,336,381,382,383,384,47],"HTML5","CSS3","WebGL2","GLSL ES 3.00","Raymarching\u002FSDF","Scroll choreography","Лендинг вымышленного датчика воздуха (демо): объект рисуется одним фрагментным шейдером на чистом WebGL2 без библиотек, четыре SDF-формы перетекают по скроллу, есть SVG-фолбэк, reduced-motion и пауза вне экрана.",[387,388,389,390,391],"Один фрагментный шейдер","WebGL2 без библиотек","Четыре SDF-формы с морфингом по скроллу","SVG-фолбэк и prefers-reduced-motion","Пауза отрисовки вне экрана","cd landing-3d && python -m http.server 8094",90,"\u002Fog\u002Flanding-3d.jpg",{"desktop":396,"desktop_sm":397,"mobile":398,"dw":195,"dh":196,"mw":197,"mh":198},"\u002Fshots\u002Flanding-3d-desktop.webp","\u002Fshots\u002Flanding-3d-desktop-sm.webp","\u002Fshots\u002Flanding-3d-mobile.webp",{"slug":400,"title":401,"type":109,"category":109,"lang":118,"kind":354,"live_url":402,"stack":403,"description":412,"highlights":413,"run":420,"hue":421,"og":422,"shots":423},"raft-kv","Плот: KV на Raft","https:\u002F\u002Fraft-kv.ewsir.space",[118,404,405,406,170,407,274,408,409,410,411,336,140,47],"Raft","Distributed systems","Consensus","HTTP\u002FJSON","WAL + fsync","Snapshots","Linearizability","Deterministic simulation testing","Учебная реализация Raft с нуля на чистой stdlib: выборы лидера, репликация лога, коммит по кворуму, лог и term\u002Fvote на диске с fsync, снапшоты с компакцией, линеаризуемые чтения (read-index), редирект фолловера на лидера. Тестируется детерминированным симулятором сети и времени с проверкой линеаризуемости; дашборд умеет убивать лидера и рвать сеть (демо).",[414,415,416,417,418,419],"Raft с нуля на stdlib","без raft-библиотек","Детерминированный симулятор сети и времени + проверка линеаризуемости","Лог и term\u002Fvote с fsync","снапшоты и компакция","Дашборд: убить лидера и разорвать сеть (демо)","cd raft-kv && set SEED_DEMO=1&& go run .\u002Fcmd\u002Fraft-kv",290,"\u002Fog\u002Fraft-kv.jpg",{"desktop":424,"desktop_sm":425,"mobile":426,"dw":195,"dh":196,"mw":197,"mh":198},"\u002Fshots\u002Fraft-kv-desktop.webp","\u002Fshots\u002Fraft-kv-desktop-sm.webp","\u002Fshots\u002Fraft-kv-mobile.webp",{"slug":428,"title":429,"type":109,"category":109,"lang":118,"kind":430,"live_url":431,"stack":432,"description":441,"highlights":442,"run":447,"hue":448,"og":449,"shots":450},"auth-service","Ключница: сервис аутентификации","auth","https:\u002F\u002Fauth-service.ewsir.space",[118,433,141,359,434,435,436,437,438,439,440,336,337,140,47],"stdlib crypto","JWT EdDSA\u002FEd25519","JWKS","PBKDF2","TOTP","RBAC","REST","OpenAPI 3","Сервис аутентификации и авторизации на Go: EdDSA-JWT с ротацией ключей и JWKS, refresh-токены с ротацией и обнаружением кражи, TOTP-2FA (RFC 6238) с резервными кодами, RBAC, блокировка при переборе, сброс пароля по одноразовому токену, журнал аудита, список устройств, openapi.yaml. Не прошёл независимый аудит, не production-IdP.",[443,444,445,446],"JWT EdDSA и ротация ключей с JWKS","Refresh-токены с ротацией и reuse-detection","TOTP по RFC 6238 с проверкой на векторах RFC","Журнал аудита только на дозапись (триггеры БД)","cd auth-service && set SEED_DEMO=1&& go run .\u002Fcmd\u002Fserver",20,"\u002Fog\u002Fauth-service.jpg",{"desktop":451,"desktop_sm":452,"mobile":453,"dw":195,"dh":196,"mw":197,"mh":198},"\u002Fshots\u002Fauth-service-desktop.webp","\u002Fshots\u002Fauth-service-desktop-sm.webp","\u002Fshots\u002Fauth-service-mobile.webp",{"slug":455,"title":456,"type":109,"category":109,"lang":118,"kind":181,"live_url":457,"stack":458,"description":465,"highlights":466,"run":471,"hue":472,"og":473,"shots":474},"finance-tracker","Книга учёта: личные финансы","https:\u002F\u002Ffinance-tracker.ewsir.space",[118,141,359,275,459,78,59,460,461,462,463,140,47,436,464],"Nuxt 4","Pinia","SVG-графики","vitest","ESLint","CSV import\u002Fexport","Учёт личных финансов: счета в разных валютах с ручными курсами, доходы, расходы и переводы, бюджеты, платежи по cron-подобному расписанию, импорт CSV с превью, отчёты. Деньги только в целых минорных единицах, данные пользователей изолированы, графики нарисованы на SVG без библиотек. Go + SQLite и фронтенд на Nuxt 4.",[467,468,469,470],"Деньги только целыми минорными единицами","Мультивалютность с ручными курсами","Импорт CSV с превью и пробным прогоном","Свои SVG-графики без библиотек","cd finance-tracker && set SEED_DEMO=1&& go run .",140,"\u002Fog\u002Ffinance-tracker.jpg",{"desktop":475,"desktop_sm":476,"mobile":477,"dw":195,"dh":196,"mw":197,"mh":198},"\u002Fshots\u002Ffinance-tracker-desktop.webp","\u002Fshots\u002Ffinance-tracker-desktop-sm.webp","\u002Fshots\u002Ffinance-tracker-mobile.webp",{"slug":479,"title":480,"type":109,"category":109,"lang":118,"kind":309,"live_url":481,"stack":482,"description":486,"highlights":487,"run":492,"hue":493,"og":494,"shots":495},"pixel-wall","Пиксельная стена: общий холст","https:\u002F\u002Fpixel-wall.ewsir.space",[118,170,312,483,336,337,484,485,140,47],"Canvas 2D","Binary protocol","Snapshot + journal","Совместный холст 256×256 в духе r\u002Fplace: 16 цветов, кулдаун на сессию и IP, свой WebSocket-слой на stdlib (RFC 6455) с бинарным протоколом (4 бита на пиксель), снимок и дельты, снапшот плюс журнал с восстановлением, таймлапс, зум и пан, мини-карта, экспорт PNG, админ-инструменты. Пет-проект.",[488,489,490,491],"Свой WebSocket (RFC 6455) на stdlib","Бинарный протокол: 4 бита на пиксель","Снапшот + журнал с восстановлением после падения","Таймлапс по журналу изменений","cd pixel-wall && set SEED_DEMO=1&& set ADMIN_TOKEN=secret&& go run .\u002Fcmd\u002Fserver",320,"\u002Fog\u002Fpixel-wall.jpg",{"desktop":496,"desktop_sm":497,"mobile":498,"dw":195,"dh":196,"mw":197,"mh":198},"\u002Fshots\u002Fpixel-wall-desktop.webp","\u002Fshots\u002Fpixel-wall-desktop-sm.webp","\u002Fshots\u002Fpixel-wall-mobile.webp",{"slug":154,"title":155,"type":143,"category":143,"lang":118,"live_url":500,"stack":501,"description":507,"highlights":508,"run":513,"hue":157,"og":514,"shots":515},"https:\u002F\u002Fgitpulse.ewsir.space",[118,170,169,502,503,504,505,506,47,140],"git","html\u002Ftemplate","SVG","ANSI","os\u002Fexec","CLI на Go для анализа git-репозиториев без внешних зависимостей (нужен только git): stats с цветами и спарклайнами в терминале, report — самодостаточный HTML-отчёт с тепловой картой активности, churn и горячими файлами, вкладом авторов, bus factor по каталогам, ростом кода по языкам и периодами тишины (светлая и тёмная темы), diff-hotspots между ревизиями и локальный serve на 127.0.0.1. История читается потоком через git log -z --numstat -M. Пет-проект.",[509,510,511,512],"Только stdlib Go, внешний бинарник один: git","Поток git log -z --numstat -M с учётом .mailmap и переименований","Самодостаточный HTML-отчёт: тепловая карта, churn, bus factor, светлая и тёмная темы","Синтетическая история из 100 000 коммитов анализируется около 5 секунд","cd gitpulse && go run .\u002Fcmd\u002Fgitpulse stats .","\u002Fog\u002Fgitpulse.jpg",{"desktop":516,"desktop_sm":156,"mobile":517,"dw":195,"dh":196,"mw":197,"mh":198},"\u002Fshots\u002Fgitpulse-desktop.webp","\u002Fshots\u002Fgitpulse-mobile.webp",{"slug":63,"title":64,"type":519,"category":50,"stack":520,"description":525,"highlights":526,"run":533,"hue":66,"og":534,"shots":535},"site",[459,78,59,521,522,523,524,140,47],"Bootstrap 5","Zod","Vitest","Playwright","Реализованный сайт юридической и экспертной компании на Nuxt 4: 65 страниц с серверным рендерингом, JSON-LD разметка и 301-редиректы со старого сайта. Заявки уходят через серверный эндпоинт с проверкой Origin, лимитом размера тела и ограничением частоты запросов.",[527,528,529,530,531,532],"65 маршрутов с SSR, sitemap.xml и robots.txt; индексация включается явным флагом окружения","Форма заявок: серверный эндпоинт, проверка Origin, лимит тела 10 КБ, 3 запроса за 10 минут с одного IP, honeypot, опциональная капча","JSON-LD: LegalService, WebSite, BreadcrumbList","Одношаговые 301-редиректы со старых URL и со слешей на конце, заголовки безопасности (CSP, HSTS)","CI\u002FCD: lint, typecheck, Vitest, сборка, Playwright; затем Docker-образ и выкладка на сервер","210 модульных тестов в 13 файлах и 4 набора e2e-тестов","npm install && npm run build && node .output\u002Fserver\u002Findex.mjs","\u002Fog\u002Flegal-multipage-site.jpg",{"desktop":536,"desktop_sm":65,"mobile":537,"dw":195,"dh":196,"mw":197,"mh":198},"\u002Fshots\u002Flegal-multipage-site-desktop.webp","\u002Fshots\u002Flegal-multipage-site-mobile.webp",{"slug":92,"title":93,"type":539,"category":81,"stack":540,"description":547,"highlights":548,"run":553,"hue":95,"og":554,"shots":555},"app",[78,106,460,541,542,543,544,545,546],"Vue Router","GSAP","Express","node:sqlite","JWT (httpOnly cookie)","Resend","Демо-приложение на Vue 3 и Express с SQLite: интерактивная карта зала с бронированием мест, личный кабинет и админ-панель с редактором расстановки. Вход без паролей, по одноразовому коду на e-mail.",[549,550,551,552],"Карта зала с зонами (обычные, тихая, VIP, два монитора), фильтром по типу и статусом мест","Админ-панель: обзор, брони, столы, компьютеры, пользователи и перетаскиваемая расстановка зала","Вход по OTP с ограничением попыток и частоты запросов, роль admin задаётся через переменную окружения","Данные для демо создаются сид-скриптом при первом запуске","npm install && npm run dev","\u002Fog\u002Fbad-club-booking-app.jpg",{"desktop":556,"desktop_sm":94,"mobile":557,"dw":195,"dh":196,"mw":197,"mh":198},"\u002Fshots\u002Fbad-club-booking-app-desktop.webp","\u002Fshots\u002Fbad-club-booking-app-mobile.webp",{"slug":559,"title":560,"type":227,"category":21,"live_url":561,"stack":562,"description":563,"highlights":564,"run":553,"hue":568,"og":569,"shots":570},"dovod-concept","Довод: концепт лендинга экспертной организации","https:\u002F\u002Fewsirdev-dovod.pages.dev",[459,78,59,45,47],"Концепт лендинга на Nuxt 4 для вымышленной экспертной организации: направления услуг, форма консультации с маской телефона, карусель отзывов и страница политики. Компания, отзывы, цифры и контакты придуманы; форма ничего не отправляет.",[565,566,567],"Форма с маской телефона, валидацией и подсказками по ошибкам; в демо показывает «данные не отправляются»","Бесконечная карусель отзывов со свайпом и автопрокруткой","Мобильная панель действий, адаптивная вёрстка, скелетон поверх серверного HTML",215,"\u002Fog\u002Fdovod-concept.jpg",{"desktop":571,"desktop_sm":572,"mobile":573,"dw":195,"dh":196,"mw":197,"mh":198},"\u002Fshots\u002Fdovod-concept-desktop.webp","\u002Fshots\u002Fdovod-concept-desktop-sm.webp","\u002Fshots\u002Fdovod-concept-mobile.webp",{"slug":68,"title":69,"type":519,"category":50,"live_url":575,"stack":576,"description":579,"highlights":580,"run":553,"hue":71,"og":584,"shots":585},"https:\u002F\u002Fewsirdev-niche.pages.dev",[78,577,578,47],"Vue Router 4","Vite 8","Vue-каталог из десяти самостоятельных лендингов под разные ниши малого бизнеса: у каждого своя палитра, шрифты и структура секций. Каждый лендинг загружается отдельным чанком; весь контент демонстрационный.",[581,582,583],"10 ниш: медицина, мебель, ремонт, онлайн-школа, глэмпинг, юристы, автосервис, салон красоты, кафе, фитнес","Код-сплиттинг по маршрутам, общий набор SVG-иконок","Плашка «Демо-контент» на каталоге и каждом лендинге","\u002Fog\u002Fniche-landings.jpg",{"desktop":586,"desktop_sm":70,"mobile":587,"dw":195,"dh":196,"mw":197,"mh":198},"\u002Fshots\u002Fniche-landings-desktop.webp","\u002Fshots\u002Fniche-landings-mobile.webp",{"slug":589,"title":590,"type":227,"category":21,"live_url":591,"stack":592,"description":595,"highlights":596,"run":600,"hue":601,"og":602,"shots":603},"dental-concept","Кварц: концепт лендинга стоматологии","https:\u002F\u002Fewsirdev-dental.pages.dev",[46,45,593,483,594,47],"JavaScript","htmlhint","Концепт одностраничного лендинга вымышленной стоматологии в стиле Liquid Chrome Dark: стеклянные карточки, хромированные заголовки и 3D-блоб в виде зуба на canvas. Один HTML-файл без сборки и зависимостей.",[597,598,599],"Анимированный блоб на canvas с учётом prefers-reduced-motion и видимости на экране","Форма записи с маской телефона и доступными сообщениями об ошибках","CI: htmlhint и проверка локальных ссылок и якорей","python -m http.server 8080",190,"\u002Fog\u002Fdental-concept.jpg",{"desktop":604,"desktop_sm":605,"mobile":606,"dw":195,"dh":196,"mw":197,"mh":198},"\u002Fshots\u002Fdental-concept-desktop.webp","\u002Fshots\u002Fdental-concept-desktop-sm.webp","\u002Fshots\u002Fdental-concept-mobile.webp",{"slug":159,"title":160,"type":143,"category":143,"lang":121,"stack":608,"description":612,"highlights":613,"run":618,"hue":162,"og":619,"shots":620},[121,170,169,504,609,610,611,47,140],"pytest","ruff","mypy","CLI на Python 3.11+ только на стандартной библиотеке: потоково разбирает логи nginx\u002Fapache (combined), JSON-lines, logfmt и syslog (gz, bz2, xz тоже), считает статистику и топы, кластеризует похожие ошибки, ищет скачки нагрузки по z-score и EWMA и собирает один HTML-отчёт с SVG-графиками и тепловой картой. Пет-проект.",[614,615,616,617],"Только стандартная библиотека, pip install ничего не тянет","Кластеры ошибок по шаблону и аномалии двумя методами (z-score и EWMA)","Один самодостаточный HTML-отчёт: SVG, тепловая карта, светлая и тёмная темы","53 теста, ruff и mypy --strict; 1 млн строк combined за 8,2 с (один прогон, Windows, CPython 3.12.10)","logslice report access.log -o report.html","\u002Fog\u002Flogslice.jpg",{"desktop":621,"desktop_sm":161,"mobile":622,"dw":195,"dh":196,"mw":197,"mh":198},"\u002Fshots\u002Flogslice-desktop.webp","\u002Fshots\u002Flogslice-mobile.webp",{"slug":164,"title":165,"type":143,"category":143,"lang":118,"stack":624,"description":629,"highlights":630,"run":635,"hue":167,"og":636,"shots":637},[118,170,169,625,436,626,627,628,47,140],"AES-256-GCM","fuzzing","govulncheck","gitleaks","CLI на Go без внешних модулей: check сверяет .env со схемой, scan ищет секреты в дереве и git-истории, diff и doctor проверяют окружения и git-гигиену, seal и unseal шифруют .env в .env.sealed (AES-256-GCM, PBKDF2 600 тыс. итераций), run запускает команду с чистым окружением, report и serve строят отчёт. Значение секрета не попадает ни в вывод, ни в лог, ни в HTML. Пет-проект.",[631,632,633,634],"Значения секретов не печатаются нигде: только тип находки, файл, строка и маска из 4 символов","seal и unseal: AES-256-GCM, PBKDF2 600 тыс. итераций, подмена любого бита файла отвергается","Fuzz-тесты: 4 цели по 20 секунд без падений; govulncheck и gitleaks без находок","Сборки под windows, linux и darwin, только стандартная библиотека","go run .\u002Fcmd\u002Fenvguard check","\u002Fog\u002Fenvguard.jpg",{"desktop":638,"desktop_sm":166,"mobile":639,"dw":195,"dh":196,"mw":197,"mh":198},"\u002Fshots\u002Fenvguard-desktop.webp","\u002Fshots\u002Fenvguard-mobile.webp",{"slug":641,"title":642,"type":109,"category":109,"lang":121,"live_url":643,"stack":644,"description":647,"highlights":648,"run":653,"hue":654,"og":655,"shots":656},"catalog-api","catalog-api: API каталога с поиском","https:\u002F\u002Fcatalog-api.ewsir.space",[121,645,141,357,609,610,611,646,140,47],"FastAPI","bandit","API каталога товаров на Python 3.12 и FastAPI: SQLite с полнотекстовым поиском FTS5 (ранжирование bm25), фасеты по категории, цене, наличию, тегам и характеристикам, курсорная пагинация, ETag, импорт и экспорт CSV, история цен, API-ключи с ролями и аудит-лог. Витрина и админка без фреймворка отдаются самим приложением. Пет-проект.",[649,650,651,652],"Поиск FTS5 с bm25, фасеты и курсорная пагинация","API-ключи с ролями admin, editor и reader; в базе только хэши ключей","511 тестов, покрытие 96%; ruff, mypy --strict, bandit, pip-audit и gitleaks без замечаний","Нагрузка: 339 запросов\u002Fс, p95 50 мс при 8 соединениях (Windows, один процесс)","SEED_DEMO=1 python -m app   # http:\u002F\u002F127.0.0.1:8110",265,"\u002Fog\u002Fcatalog-api.jpg",{"desktop":657,"desktop_sm":658,"mobile":659,"dw":195,"dh":196,"mw":197,"mh":198},"\u002Fshots\u002Fcatalog-api-desktop.webp","\u002Fshots\u002Fcatalog-api-desktop-sm.webp","\u002Fshots\u002Fcatalog-api-mobile.webp",{"slug":661,"title":662,"type":109,"category":109,"lang":59,"live_url":663,"stack":664,"description":669,"highlights":670,"run":553,"hue":568,"og":675,"shots":676},"paste-vault","Конверт: записки с шифрованием в браузере","https:\u002F\u002Fewsirdev-paste.ewsirdev.workers.dev",[59,665,666,667,106,462,668,47],"Cloudflare Workers","D1","Web Crypto","fast-check","Сервис одноразовых записок на TypeScript, Cloudflare Workers и D1: текст шифруется в браузере (AES-256-GCM), ключ лежит только во фрагменте URL и на сервер не попадает. У записки есть срок жизни, пароль и сгорание после первого прочтения. Пет-проект.",[671,672,673,674],"Шифрование в браузере: AES-256-GCM, ключ только во фрагменте URL","Сгорание после прочтения атомарно: ровно один успех из 40 параллельных чтений","52 теста в рантайме workerd, включая property-тесты и фаззинг схемы","Живая версия на Cloudflare Workers + D1","\u002Fog\u002Fpaste-vault.jpg",{"desktop":677,"desktop_sm":678,"mobile":679,"dw":195,"dh":196,"mw":197,"mh":198},"\u002Fshots\u002Fpaste-vault-desktop.webp","\u002Fshots\u002Fpaste-vault-desktop-sm.webp","\u002Fshots\u002Fpaste-vault-mobile.webp",{"slug":73,"title":74,"type":519,"category":50,"live_url":681,"stack":682,"description":688,"highlights":689,"run":694,"hue":76,"og":695,"shots":696},"https:\u002F\u002Fewsirdev-docs.pages.dev",[683,684,685,686,687,47],"Node.js","JavaScript (ESM)","zero dependencies","Markdown","Cloudflare Pages","Генератор статических сайтов документации, написанный с нуля на Node.js без зависимостей: собственный Markdown-парсер, подсветка кода и офлайн-поиск. Демо-сайт про вымышленную библиотеку «Сверло» собран из 43 страниц на двух языках (RU и EN) и двух версиях (v1 и v2). Пет-проект.",[690,691,692,693],"Свой Markdown-парсер, подсветка кода и клиентский поиск без библиотек","43 страницы, RU и EN, версии v1 и v2, hreflang, sitemap и RSS","131 тест; в CI проверка битых ссылок и якорей","Сайт читается без JavaScript, поиск работает офлайн","npm test && npm run build","\u002Fog\u002Fdocs-engine.jpg",{"desktop":697,"desktop_sm":75,"mobile":698,"dw":195,"dh":196,"mw":197,"mh":198},"\u002Fshots\u002Fdocs-engine-desktop.webp","\u002Fshots\u002Fdocs-engine-mobile.webp",{"slug":97,"title":98,"type":539,"category":81,"live_url":700,"stack":701,"description":705,"highlights":706,"run":553,"hue":100,"og":711,"shots":712},"https:\u002F\u002Fewsirdev-typing.pages.dev",[459,78,59,702,703,704,504,462,107,687],"IndexedDB","WebAudio","PWA","Тренажёр слепой печати, который целиком работает в браузере и не обращается к серверу: Nuxt 4, история в IndexedDB, звук клавиш на WebAudio, офлайн через PWA. Режимы времени, слов, цитат, своего текста, кода и «слабых мест», шесть раскладок, метрики WPM, точность и консистентность, графики на чистом SVG. Пет-проект.",[707,708,709,710],"Без сервера и аккаунтов: история в IndexedDB, экспорт и импорт JSON","Шесть раскладок, режимы вплоть до адаптивных упражнений из ваших ошибок","59 unit-тестов и 65 e2e-проверок, axe без нарушений, CLS 0,000","PWA: работает офлайн после первого открытия","\u002Fog\u002Ftyping-lab.jpg",{"desktop":713,"desktop_sm":99,"mobile":714,"dw":195,"dh":196,"mw":197,"mh":198},"\u002Fshots\u002Ftyping-lab-desktop.webp","\u002Fshots\u002Ftyping-lab-mobile.webp",{"slug":102,"title":103,"type":539,"category":81,"lang":59,"live_url":716,"stack":717,"description":719,"highlights":720,"run":553,"hue":100,"og":725,"shots":726},"https:\u002F\u002Fewsirdev-budget.ewsirdev.workers.dev",[59,106,665,666,718,462,668,107,47],"WebGL","Веб-приложение учёта общих расходов группы без регистрации: комната по секретной ссылке, деление поровну, по долям, суммам и процентам, расчёт «кто кому» не более n−1 переводов. Пет-проект.",[721,722,723,724],"Без аккаунтов: комната по секретной ссылке в 128 бит, отдельные ссылки «просмотр» и «редактирование»","Четыре способа деления и мультивалюта с ручным курсом; расчёт «кто кому» не более n−1 переводов","Оптимистичная блокировка: из 4 одновременных правок одного расхода проходит одна","60 тестов (vitest в workerd, property-тесты fast-check), CI зелёный, npm audit 0, axe 0 нарушений","\u002Fog\u002Fbudget-split.jpg",{"desktop":727,"desktop_sm":104,"mobile":728,"dw":195,"dh":196,"mw":197,"mh":198},"\u002Fshots\u002Fbudget-split-desktop.webp","\u002Fshots\u002Fbudget-split-mobile.webp",{"slug":730,"title":731,"type":519,"category":50,"lang":59,"live_url":732,"stack":733,"description":735,"highlights":736,"run":553,"hue":100,"og":741,"shots":742},"kladka-site","kladka-site: «Кладка», демо-сайт студии каркасного строительства","https:\u002F\u002Fewsirdev-kladka.pages.dev",[79,59,734,504,718,107,594,47],"Content collections","Демо-сайт вымышленной студии каркасного строительства: 5 проектов с чертежами, услуги, цены с калькулятором-ориентиром, журнал. Компания, цены и сроки выдуманы, на каждой странице есть пометка.",[737,738,739,740],"24 страницы (19 RU, 4 EN, 404) на Astro 7 со статической сборкой и content collections","Все 20 чертежей рисует код: SVG по данным проекта; 24 картинки OG рендерятся скриптом","Строгий CSP без inline, robots, sitemap с hreflang, llms.txt, security.txt, JSON-LD Organization\u002FService\u002FArticle\u002FFAQPage","756 ссылок и якорей без ошибок, htmlhint 0, axe 0 по всем страницам на 390 и 1280, npm audit 0","\u002Fog\u002Fkladka-site.jpg",{"desktop":743,"desktop_sm":744,"mobile":745,"dw":195,"dh":196,"mw":197,"mh":198},"\u002Fshots\u002Fkladka-site-desktop.webp","\u002Fshots\u002Fkladka-site-desktop-sm.webp","\u002Fshots\u002Fkladka-site-mobile.webp",1793297827182]