Гео-аналитическая платформа вдвоём за 2,5 месяца: история spec-driven разработки
Автор поделился опытом старта проекта с чистого листа. Перед ним стояла амбициозная задача, не было легаси и была полная свобода выбора подхода. Он решил максимально довериться AI и больше не трогать код. Изначально были сомнения, что такой подход выдержит реальный масштаб, так как опыт подсказывал, что чем больше проект, тем быстрее AI путается в контексте и упирается в лимиты.
Через 2,5 месяца работы вдвоём удалось запустить гео-аналитическую платформу. Автор отмечает, что в до-AI эпоху такой проект строили бы годами. Этот опыт изменил его представление о разработке.
Читать далее
Автор поделился опытом старта проекта с чистого листа. Перед ним стояла амбициозная задача, не было легаси и была полная свобода выбора подхода. Он решил максимально довериться AI и больше не трогать код. Изначально были сомнения, что такой подход выдержит реальный масштаб, так как опыт подсказывал, что чем больше проект, тем быстрее AI путается в контексте и упирается в лимиты.
Через 2,5 месяца работы вдвоём удалось запустить гео-аналитическую платформу. Автор отмечает, что в до-AI эпоху такой проект строили бы годами. Этот опыт изменил его представление о разработке.
Читать далее
Маск внезапно выкатил Grok 4.5 — первую версию, заточенную под код и агентов 😨
Обучали её в связке с Cursor, и по бенчмаркам она уже перепрыгивает GPT-5.5, заодно держится на уровне Opus 4.8. Terminal-Bench: у неё 83%, а у Opus 4.8 — 78,9%.
Прайсинг: $2/$6 за миллион токенов. «Opus-уровень, но шустрее, меньше жрет токенов и дешевле» — написал Маск в X. В Cursor она уже есть на всех тарифах. Завтра ждем ответочку от Альтмана😊
👉 Node.JS [ru]
Обучали её в связке с Cursor, и по бенчмаркам она уже перепрыгивает GPT-5.5, заодно держится на уровне Opus 4.8. Terminal-Bench: у неё 83%, а у Opus 4.8 — 78,9%.
Прайсинг: $2/$6 за миллион токенов. «Opus-уровень, но шустрее, меньше жрет токенов и дешевле» — написал Маск в X. В Cursor она уже есть на всех тарифах. Завтра ждем ответочку от Альтмана
Please open Telegram to view this post
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM
Грейсфул шатдаун: как SIGTERM и postMessage ломают порядок сигналов
Каждый middle/senior разработчик Node.js сталкивался с грейсфул шатдауном, но редкий замечает скрытую ловушку — смешивание
Проблема приоритетов событий
SIGTERM — системное событие с высоким приоритетом в цикле событий.
Production-пример и диагностика
Рассмотрим типичный код:
Здесь
* Логируйте порядок событий с таймстемпами.
* Используйте
* Проверяйте флаг
Практическое исправление
Решение — флаги состояния:
Для надёжности —
Типичная ошибка
Многие путают: проблема не в доступе к
Вывод: Используйте флаги состояния перед асинхронными операциями в обработчиках сигналов — это предотвращает потерю данных при грейсфул шатдауне.
Каждый middle/senior разработчик Node.js сталкивался с грейсфул шатдауном, но редкий замечает скрытую ловушку — смешивание
process.on('SIGTERM') и Worker.prototype.postMessage. Из-за разницы приоритетов событий и микрозадач теряются данные в production.Проблема приоритетов событий
SIGTERM — системное событие с высоким приоритетом в цикле событий.
postMessage — асинхронная операция, попадающая в очередь микрозадач. Если SIGTERM приходит до завершения отправки сообщения, оно обрабатывается уже после shutdown. Результат — потеря данных, которые должны были уйти в Worker.Production-пример и диагностика
Рассмотрим типичный код:
process.on('SIGTERM', () => {
console.log('Shutdown');
worker.terminate();
});
worker.postMessage({ type: 'data', payload: result });Здесь
postMessage может быть прерван сигналом. Как диагностировать:* Логируйте порядок событий с таймстемпами.
* Используйте
process.on('SIGTERM', { once: true }) для однократного вызова.* Проверяйте флаг
isShuttingDown перед любым postMessage.Практическое исправление
Решение — флаги состояния:
let isShuttingDown = false;
process.on('SIGTERM', () => { isShuttingDown = true; });
if (!isShuttingDown) worker.postMessage({ ... });
Для надёжности —
SharedArrayBuffer для атомарной синхронизации между основным потоком и Worker, если требуется строгий контроль.Типичная ошибка
Многие путают: проблема не в доступе к
process из Worker threads — Worker его не имеет. Корень — порядок микрозадач в основном потоке, где postMessage может быть отложен после обработки SIGTERM.Вывод: Используйте флаги состояния перед асинхронными операциями в обработчиках сигналов — это предотвращает потерю данных при грейсфул шатдауне.
👍2❤1
This media is not supported in your browser
VIEW IN TELEGRAM
Claude Code теперь стерильно для стаи
Наваливаться на одну сессию в терминале можно гуртом. Плюс теперь разрешено стыковать свои инстансы Claude, чтоб они балакали между собой.
Потыкать
👉 Node.JS [ru]
Наваливаться на одну сессию в терминале можно гуртом. Плюс теперь разрешено стыковать свои инстансы Claude, чтоб они балакали между собой.
Потыкать
Please open Telegram to view this post
VIEW IN TELEGRAM
❤2
OpenAI наконец-то почесались и закатили апдейт: GPT-5.6 Sol, Terra и Luna — уже в проде, дружок. Постепенно раздают ранний доступ по всему шарику.
Вот пруф, если глазам не веришь
👉 Node.JS [ru]
Вот пруф, если глазам не веришь
Please open Telegram to view this post
VIEW IN TELEGRAM
Anthropic раскатал схему работы Fable 5 в обнимку с Sonnet 5 — тут два варианта
Первый вариант — Fable 5 втыкается как «советник» (Advisor). Sonnet 5 тащит задачу, но периодически дёргает Fable 5 за стратегическим советом. На тесте SWE-bench Pro такая связка вывозит примерно 92% от качества Fable 5, при этом платишь всего 63% от её цены. Fable 5 дёргают примерно раз на задачу. Подробнее в документации.
Второй расклад — Fable 5 работает оркестратором. Она разруливает план и раздаёт таски исполнителям — Sonnet 5. В Benchmark-тестах BrowseComp на Claude Managed Agents такая конфигурация показала 96% производительности Fable 5, а стоила 46% от оригинала. Жирные по ресурсам задачи скидывают на Sonnet 5. Пример в кукбуке.
Claude Managed Agents переваривает оба подхода (документация). Каждый подагент держит свой кэш, так что при повторных вызовах не придётся платить за полную пережевку контекста.
👉 Node.JS [ru]
Первый вариант — Fable 5 втыкается как «советник» (Advisor). Sonnet 5 тащит задачу, но периодически дёргает Fable 5 за стратегическим советом. На тесте SWE-bench Pro такая связка вывозит примерно 92% от качества Fable 5, при этом платишь всего 63% от её цены. Fable 5 дёргают примерно раз на задачу. Подробнее в документации.
Второй расклад — Fable 5 работает оркестратором. Она разруливает план и раздаёт таски исполнителям — Sonnet 5. В Benchmark-тестах BrowseComp на Claude Managed Agents такая конфигурация показала 96% производительности Fable 5, а стоила 46% от оригинала. Жирные по ресурсам задачи скидывают на Sonnet 5. Пример в кукбуке.
Claude Managed Agents переваривает оба подхода (документация). Каждый подагент держит свой кэш, так что при повторных вызовах не придётся платить за полную пережевку контекста.
Please open Telegram to view this post
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM
Системный аналитик — не просто почтальон между бизнесом и разрабами. Часто тащит на себе интегратора, деливери-менеджера, продакта, архитектора и даже скрам-мастера. И периодически в него летят таски, которые вообще не его профиль. Это такой челлендж: либо ты закаляешься, либо ловишь ментальный аут.
Это уже второй заход от Александра Малышева, лид-аналитика из МТС Cashback. И тут он расписывает зону ответственности SA. Всё в стихах и с юмором — прямо как народная сказка.
📖 Читать сказку
👉 Node.JS [ru]
Это уже второй заход от Александра Малышева, лид-аналитика из МТС Cashback. И тут он расписывает зону ответственности SA. Всё в стихах и с юмором — прямо как народная сказка.
📖 Читать сказку
Please open Telegram to view this post
VIEW IN TELEGRAM
😁1
Anthropic решила, что пора поживиться, и попыталась снять с парня $16,6 млн просто за красивые глаза 👻
Корейский юзер, у которого бесплатный тариф и ноль запросов к API, получил официальную бумажку с требованием оплатить $16,6 млн. А за день до этого ему же прилетел запрос на $1,6 млн — сумма за ночь выросла в 10 раз. Хорошо, что банк просто зарубил эти попытки из-за лимита карты.
Anthropic пока молчит. И это не единичный случай: на Reddit народ тоже жалуется на внезапные списания от этой конторы.
Инфа сотка 😁
👉 Node.JS [ru]
Корейский юзер, у которого бесплатный тариф и ноль запросов к API, получил официальную бумажку с требованием оплатить $16,6 млн. А за день до этого ему же прилетел запрос на $1,6 млн — сумма за ночь выросла в 10 раз. Хорошо, что банк просто зарубил эти попытки из-за лимита карты.
Anthropic пока молчит. И это не единичный случай: на Reddit народ тоже жалуется на внезапные списания от этой конторы.
Инфа сотка 😁
Please open Telegram to view this post
VIEW IN TELEGRAM
Неявная блокировка Event Loop: как MapIterator убивает производительность в hot-path
Казалось бы, обычный
Почему это происходит
V8 в оптимизированном коде (TurboFan) может не аллоцировать итератор — он сохраняется на стеке или убирается escape analysis. Но деоптимизация из-за полиморфизма типов ключей (строки и числа),
Диагностика на практике
-
-
- Сравни
Практический совет
В hot-path используй
Типичная ошибка
Не проверяешь стабильность типов ключей
Вывод: Аллокация
Казалось бы, обычный
for...of по Map — и вдруг Event Loop начинает тормозить. Проблема в скрытой аллокации MapIterator при деоптимизации V8. В hot-path это приводит к частому GC и блокировкам.Почему это происходит
V8 в оптимизированном коде (TurboFan) может не аллоцировать итератор — он сохраняется на стеке или убирается escape analysis. Но деоптимизация из-за полиморфизма типов ключей (строки и числа),
try/catch внутри цикла или превышения inline-лимитов заставляет создавать MapIterator в куче. В production это вызывает пинг-понг с GC.Диагностика на практике
-
--trace-gc — ищем аллокации MapIterator.-
--trace-deopt — смотрим причину: часто смешение типов ключей или нестабильный полиморфизм.- Сравни
for...of, map.forEach и ручной обход через Array.from(map.keys()) в бенчмарке. Разница может быть кратной.Практический совет
В hot-path используй
forEach, а не for...of. V8 может встроить его колбэк и избежать аллокации итератора. Если нужны ключи — закешируй их через Array.from и обходи обычным циклом.Типичная ошибка
Не проверяешь стабильность типов ключей
Map. Если ключи смешанные (строки и числа) — for...of гарантированно деоптимизируется. Зафиксируй типы или используй forEach.Вывод: Аллокация
MapIterator в hot-path из-за деоптимизации V8 — скрытая причина лагов Event Loop; замена for...of на map.forEach или кеширование ключей через Array.from устраняет проблему без ущерба для читаемости.❤2
Заглянули под капот ChatGPT
Парень, который копался в производительности Linear, потратил несколько дней на изучение сорцов, бандлов и запросов к ChatGPT. Внутри — React Router 7, TanStack Query, Radix и ProseMirror. Вся ненужная хрень грузится только когда юзер начинает долбить по клавишам.
Base UI теперь рулит в shadcn/ui по умолчанию — свежие проекты стартуют на Base UI, а не на Radix. Radix пока не выкинули на помойку, оба набора компонентов доступны. Но для нового говна рекомендуют Base UI.
Shadcn подвезли переводы без передеплоя — облачный бэкенд от команды i18next с функциями saveMissing, редактированием в контексте, AI-переводом и мгновенными апдейтами через CDN. Есть бесплатный тариф, делают в Швейцарии.
👉 Node.JS [ru]
Парень, который копался в производительности Linear, потратил несколько дней на изучение сорцов, бандлов и запросов к ChatGPT. Внутри — React Router 7, TanStack Query, Radix и ProseMirror. Вся ненужная хрень грузится только когда юзер начинает долбить по клавишам.
Base UI теперь рулит в shadcn/ui по умолчанию — свежие проекты стартуют на Base UI, а не на Radix. Radix пока не выкинули на помойку, оба набора компонентов доступны. Но для нового говна рекомендуют Base UI.
Shadcn подвезли переводы без передеплоя — облачный бэкенд от команды i18next с функциями saveMissing, редактированием в контексте, AI-переводом и мгновенными апдейтами через CDN. Есть бесплатный тариф, делают в Швейцарии.
Please open Telegram to view this post
VIEW IN TELEGRAM
К 2013 году американская журналистика уже пятый год лежала на лопатках. Кризис 2008-го вынес рекламную модель прессы: в газетных ньюсрумах США осталось меньше половины сотрудников, десятки тысяч спецов остались не у дел. Потерявшие работу журналисты ушли на фриланс. Параллельно с этим университетские программы по научной журналистике (MIT, Нью-Йоркский, Санта-Круз) стабильно штамповали новых выпускников. И третий поток — учёные, свалившие из академии (постдоки, не получившие должность).
Итог: три потока продавцов и дохлый ручеёк покупателей. У Science или Smithsonian — 10-20 заказов на фриланс в месяц, заявок — десятки в день.
Источник: Хабр
👉 Node.JS [ru]
Итог: три потока продавцов и дохлый ручеёк покупателей. У Science или Smithsonian — 10-20 заказов на фриланс в месяц, заявок — десятки в день.
Источник: Хабр
Please open Telegram to view this post
VIEW IN TELEGRAM
structuredClone и Error с глубоким стек-трейсом: неявный убийца производительности в worker_threads
Передаёте Error через worker.postMessage()? Если стек-трейс глубокий — после цепочки асинхронных вызовов — V8 может незаметно блокировать поток на сериализации. Производительность падает линейно, а вы ищете причину в другом месте.
Почему это происходит
При вызове structuredClone V8 сериализует все перечислимые свойства объекта, включая .stack. Для Error с сотнями фреймов (длинные URL, аргументы функций) V8 парсит стековую строку целиком. Каждый фрейм проверяется, разбирается — и это происходит синхронно в вызывающем потоке. Пример: 5000 повторений .stack через repeat дают задержку ~100ms на postMessage.
Как диагностировать
Используйте --trace-event-categories или clinic flame. Ищите пики CPU в structuredClone. Сравните время postMessage с простым объектом против Error — разница в 10-50 раз при глубоком стеке. Типичная ошибка: разработчики грешат на дорогие вычисления, а на самом деле V8 тупо сериализует строчку.
Решение: не передавайте полный Error
Клонируйте только критичные поля: message, code, line, fileName. Для огромных стеков передавайте идентификатор ошибки, а полный стек собирайте локально. Можно упаковать Error в простой объект:
Production-кес: в нашем сервисе 20% CPU уходило на structuredClone ошибки с 1500+ фреймами. После замены на { msg: err.message } блокировка исчезла полностью.
Вывод: Не передавайте полный Error между воркерами при глубоких стеках — сериализация через structuredClone может незаметно съедать 10-20% CPU и блокировать поток.
Передаёте Error через worker.postMessage()? Если стек-трейс глубокий — после цепочки асинхронных вызовов — V8 может незаметно блокировать поток на сериализации. Производительность падает линейно, а вы ищете причину в другом месте.
Почему это происходит
При вызове structuredClone V8 сериализует все перечислимые свойства объекта, включая .stack. Для Error с сотнями фреймов (длинные URL, аргументы функций) V8 парсит стековую строку целиком. Каждый фрейм проверяется, разбирается — и это происходит синхронно в вызывающем потоке. Пример: 5000 повторений .stack через repeat дают задержку ~100ms на postMessage.
Как диагностировать
Используйте --trace-event-categories или clinic flame. Ищите пики CPU в structuredClone. Сравните время postMessage с простым объектом против Error — разница в 10-50 раз при глубоком стеке. Типичная ошибка: разработчики грешат на дорогие вычисления, а на самом деле V8 тупо сериализует строчку.
Решение: не передавайте полный Error
Клонируйте только критичные поля: message, code, line, fileName. Для огромных стеков передавайте идентификатор ошибки, а полный стек собирайте локально. Можно упаковать Error в простой объект:
const safeError = {
message: err.message,
code: err.code,
line: err.stack?.split('\n')[1]
};
worker.postMessage(safeError);Production-кес: в нашем сервисе 20% CPU уходило на structuredClone ошибки с 1500+ фреймами. После замены на { msg: err.message } блокировка исчезла полностью.
Вывод: Не передавайте полный Error между воркерами при глубоких стеках — сериализация через structuredClone может незаметно съедать 10-20% CPU и блокировать поток.
👍1
Reverse Engineering ChatGPT Web — чувак, который когда-то разбирал Linear по косточкам, решил не останавливаться и вдумчиво покопался в исходниках ChatGPT. Он прошерстил бандлы, запросы и нарыл там React Router 7, TanStack Query, Radix и ProseMirror. Фишка в том, что весь ненужный хлам не грузится, пока ты не начнешь стучать по клавиатуре.
Base UI теперь рулит в shadcn/ui — новые проекты на shadcn/ui едут на Base UI, а не на Radix. Радикс не умер, компоненты доступны для обеих платформ, но для свежих проектов советуют именно Base UI.
TypeScript 7.0 — выкатили компилятор на Go, который шпарит в 10 раз быстрее. Правда, без нормального API от версии 6.0 пока что его рекомендуют кучке фронтенд-фреймворков.
Vercel пригрела команду Better Auth — той самой популярной либы для аутентификации.
React Navigation 8.0 — ребята делятся успехами, в том числе навигацией с поддержкой Suspense.
Next.js — опубликовали официальный RFC для WebSocket в Route Handlers.
npm v12 — теперь жизненные циклы скриптов и сборки node-gyp по умолчанию не запускаются.
Подробнее — в выпуске React Status #482.
👉 Node.JS [ru]
Base UI теперь рулит в shadcn/ui — новые проекты на shadcn/ui едут на Base UI, а не на Radix. Радикс не умер, компоненты доступны для обеих платформ, но для свежих проектов советуют именно Base UI.
TypeScript 7.0 — выкатили компилятор на Go, который шпарит в 10 раз быстрее. Правда, без нормального API от версии 6.0 пока что его рекомендуют кучке фронтенд-фреймворков.
Vercel пригрела команду Better Auth — той самой популярной либы для аутентификации.
React Navigation 8.0 — ребята делятся успехами, в том числе навигацией с поддержкой Suspense.
Next.js — опубликовали официальный RFC для WebSocket в Route Handlers.
npm v12 — теперь жизненные циклы скриптов и сборки node-gyp по умолчанию не запускаются.
Подробнее — в выпуске React Status #482.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤1
TypeScript 7 переписали на Go: разбираемся, правда ли он в десять раз шустрее
📣 Главная цифра из блога TypeScript: проверка типов в проекте VS Code (примерно 1,5 млн строк) упала с 77,8 с до 7,5 с. Отсюда и заголовок — «примерно в 10 раз быстрее».
Это не обзор релиза, а попытка повторить один график на монорепе, специально склеенном для теста. Два компилятора на одной тачке, все цифры на руках, плюс репозиторий, где любой результат можно дёрнуть командой
Суть вот в чём: «10×» — это про проверку типов на большом проекте, а не про всё подряд. TS7 реально ускоряет валидацию и жрёт меньше памяти, бенчи это подтверждают. Но в 7.0 нет стабильного программного API — из сборки вылетели кастомные трансформеры, плагины к tsserver и типизированный линтинг. Так что вопрос миграции не «быстрее или нет», а «живёте ли вы без этого API».
🔗 Читать далее
👉 Node.JS [ru]
Это не обзор релиза, а попытка повторить один график на монорепе, специально склеенном для теста. Два компилятора на одной тачке, все цифры на руках, плюс репозиторий, где любой результат можно дёрнуть командой
pnpm bench. Спойлер: TS7 реально быстрее, и отрыв чувствуется. Но «в десять раз» — это для одного конкретного кейса, в других сценариях магия улетучивается.Суть вот в чём: «10×» — это про проверку типов на большом проекте, а не про всё подряд. TS7 реально ускоряет валидацию и жрёт меньше памяти, бенчи это подтверждают. Но в 7.0 нет стабильного программного API — из сборки вылетели кастомные трансформеры, плагины к tsserver и типизированный линтинг. Так что вопрос миграции не «быстрее или нет», а «живёте ли вы без этого API».
🔗 Читать далее
Please open Telegram to view this post
VIEW IN TELEGRAM
Диагностика и устранение неявного замедления TLS-рукопожатий из-за скрытой конкуренции за entropy pool в кластеризованных HTTPS-серверах
Если вы держите Node.js в кластере — через PM2 или встроенный cluster, — могли замечать странные задержки при HTTPS-соединениях. Вроде всё настроено, а рукопожатия то 5 мс, то 500+. Одна из неочевидных причин — драка за entropy pool.
Причина
Внутри каждого TLS-рукопожатия Node.js дёргает случайные числа для ключей. В Linux это
Симптомы: время рукопожатия плавает хаотично, иногда
Диагностика
Берите
Решения
* Отдельный seed для каждого воркера. При старте слать его через IPC из master:
* Для операций после старта — только асинхронный
* На bare-metal поставьте
Типичная ошибка
Не используйте
Вывод: Диагностика через strace и управление seed-ами — эффективный способ ускорить TLS без переписывания стека, но критично проверить нагрузку на entropy pool в кластере с 10+ воркерами.
Если вы держите Node.js в кластере — через PM2 или встроенный cluster, — могли замечать странные задержки при HTTPS-соединениях. Вроде всё настроено, а рукопожатия то 5 мс, то 500+. Одна из неочевидных причин — драка за entropy pool.
Причина
Внутри каждого TLS-рукопожатия Node.js дёргает случайные числа для ключей. В Linux это
/dev/urandom или getrandom(). Когда несколько воркеров одновременно лезут в один пул энтропии, начинаются блокировки. Особенно в контейнерах без аппаратного RNG — там стекло быстро заканчивается.Симптомы: время рукопожатия плавает хаотично, иногда
blocked for more than 120 seconds. strace покажет, что провал в crypto.randomFill().Диагностика
Берите
strace -e trace=getrandom -p <PID> -o entropy.log. Если вызовы висят дольше 1 мс — проблема. Проверьте скорость энтропии: dd if=/dev/urandom bs=1024 count=100 | pv.Решения
* Отдельный seed для каждого воркера. При старте слать его через IPC из master:
process.on('message', (msg) => {
if (msg.type === 'seed') {
global._seed = msg.seed;
}
});* Для операций после старта — только асинхронный
crypto.randomFill с callback. Синхронный вызов дёргает блокировку пула.* На bare-metal поставьте
haveged или rng-tools, в облаке — virtio-rng.Типичная ошибка
Не используйте
Math.random() для криптографии — это антипаттерн, который не решает проблему, а только создаёт уязвимости.Вывод: Диагностика через strace и управление seed-ами — эффективный способ ускорить TLS без переписывания стека, но критично проверить нагрузку на entropy pool в кластере с 10+ воркерами.
👾 Тамагочи из 90-х: собираем на коленке на JavaScript
Автор честно признаётся: раньше он вообще не втыкал, что это за зверь. Ну, видел в американском кино, типа в «Менталисте», где герой чуть ли не со слезой вспоминает виртуального питомца и даже толкает его коллеге. А потом до него дошло: если эта игрушка пережила три десятилетия и кучу раз её перепридумывали — значит, там что-то серьёзное, а не просто мусор.
Так он решил врубиться, что это за хрень и как она работает, и начал с истории. Без неё объяснять старые штуки — пустая трата времени.
Дальше на Habr
👉 Node.JS [ru]
Автор честно признаётся: раньше он вообще не втыкал, что это за зверь. Ну, видел в американском кино, типа в «Менталисте», где герой чуть ли не со слезой вспоминает виртуального питомца и даже толкает его коллеге. А потом до него дошло: если эта игрушка пережила три десятилетия и кучу раз её перепридумывали — значит, там что-то серьёзное, а не просто мусор.
Так он решил врубиться, что это за хрень и как она работает, и начал с истории. Без неё объяснять старые штуки — пустая трата времени.
Дальше на Habr
Please open Telegram to view this post
VIEW IN TELEGRAM
❤2🐳2