EvApps
192 subscribers
1.24K photos
52 videos
2 files
252 links
IT-aутстафферы из Тулы💚
https://evapps.ru/

Здесь пишем про веб- и мобильную разработку

▶️ Наш чат для системных аналитиков: https://t.me/pro_sa_evapps

▶️ Посмотреть, как мы живём: https://vk.com/evapps
Download Telegram
⚙️ Реализация: шаг 1 — настраиваем CDC через Debezium

Почти любая современная СУБД ведёт журнал изменений, куда сначала записываются все операции, а уже потом они применяются к данным.
Это механизм Write Ahead Log 📝
В MySQL таким журналом является binlog.
Если читать этот журнал, интерпретировать изменения и передавать их в другую систему, мы фактически реализуем подход Change Data Capture (CDC) 🔄

Почему CDC удобен для синхронизации:
- Работает потоково, почти без задержек
- Обеспечивает конечную согласованность
- Не требует тяжёлых батч-процедур
- Сохраняет порядок изменений
- Масштабируется под высокую нагрузку

Для работы с binlog чаще всего используют Debezium. Он подключается к MySQL, отслеживает изменения и публикует их в Kafka через Kafka Connect.
Дальше эти события может забирать ClickHouse 🧩

Ниже — только те настройки Debezium, которые важны именно для корректной работы с ClickHouse.

1. Оставляем только актуальное состояние записи
По умолчанию Debezium отправляет событие в таком виде:
- Cостояние до изменения
- Cостояние после изменения
Плюс при удалении формируется «пустое» сообщение

Для Kafka это нормально, а вот для ClickHouse — неудобно: таблицы Kafka Engine ожидают плоскую структуру.

Поэтому включаем трансформацию:
"transforms": "unwrap",
"transforms.unwrap.type": "io.debezium.transforms.ExtractNewRecordState"


Что это даёт:
- Для INSERT и UPDATE остаётся только новое состояние строки
- Поле before отбрасывается
- Структура сообщения упрощается

2. Корректно обрабатываем удаления

После упрощения структуры Debezium перестаёт передавать удаления. Чтобы это исправить, добавляем:
"transforms.unwrap.delete.handling.mode": "rewrite"


Теперь:
- Удалённые записи не пропадают
- В сообщении появляется поле __deleted = true
- Остальные операции получают __deleted = false

Это позволяет:
- Хранить историю изменений
- Фильтровать удалённые строки уже на стороне ClickHouse (через view)

3. Проблема обновлений неключевых полей
В нашем примере:
- В MySQL первичный ключ — id
- В ClickHouse таблица отсортирована по (id, status)
Если обновляется поле status, ClickHouse видит новую комбинацию ключей и создаёт ещё одну строку → появляются дубликаты 😬

Чтобы этого избежать, Debezium нужно явно сказать, какие поля считать идентификатором записи при обновлениях.

Настройка:
"message.key.columns": "inventory.orders:id;inventory.orders:status"


Теперь при изменении этих полей:

- Сначала генерируется событие удаления старой версии
- Затем событие вставки новой
- ClickHouse корректно «заменяет» строку через ReplacingMergeTree

Итоговая конфигурация Debezium
{
  "name": "mysql-connector",
  "config": {
    "connector.class": "io.debezium.connector.mysql.MySqlConnector",
    "database.hostname": "mysql",
    "database.port": "3306",
    "database.user": "root",
    "database.password": "mypassword",
    "database.server.id": "2",
    "database.server.name": "dbz.inventory.v2",

    "database.include.list": "inventory",
    "table.include.list": "inventory.orders",

    "message.key.columns": "inventory.orders:id;inventory.orders:status",

    "schema.history.internal.kafka.bootstrap.servers": "broker:9092",
    "schema.history.internal.kafka.topic": "dbz.inventory.history.v2",

    "snapshot.mode": "schema_only",
    "topic.prefix": "dbz.inventory.v2",

    "transforms": "unwrap",
    "transforms.unwrap.type": "io.debezium.transforms.ExtractNewRecordState",
    "transforms.unwrap.delete.handling.mode": "rewrite"
  }
}

⚠️ Важный момент: как выбирать message.key.columns

После задания message.key.columns:
- Эти поля используются как ключ сообщения в Kafka
- По ним распределяются данные по партициям
- Нарушенный порядок событий = риск рассинхронизации в ClickHouse

Практическое правило:
1️⃣ Определите ключ сортировки таблицы в ClickHouse
2️⃣ Поймите, из каких колонок источника он формируется
3️⃣ Объедините все эти колонки
4️⃣ Укажите их в message.key.columns
5️⃣ Убедитесь, что они входят в ORDER BY в ClickHouse

#ClickHouse #MySQL #CDC #Debezium #Kafka #DataSync #OLTP #OLAP #DataEngineering #АналитикаДанных
🗄 Шаг 2: таблицы в ClickHouse
ClickHouse умеет читать сообщения напрямую из Kafka через Kafka Engine.
Для этого настраивается цепочка из трёх таблиц и одного представления.

📥 Kafka-таблица
Описывает структуру сообщений и Kafka-топик, из которого будут читаться данные.
CREATE TABLE default.kafka_orders
(
    `id` Int32,
    `status` String,
    `price` String,
    `__deleted` Nullable(String)
)
ENGINE = Kafka('broker:9092', 'inventory.orders', 'clickhouse', 'AvroConfluent')
SETTINGS format_avro_schema_registry_url = 'http://schema-registry:8081';


🔁Материализатор данных из Kafka
Kafka-таблица читает сообщения только один раз — смещения коммитаются в consumer group.
Поэтому каждую запись нужно сразу перекладывать в постоянную таблицу.
CREATE MATERIALIZED VIEW default.consumer__orders
TO default.stream_orders
(
    `id` Int32,
    `status` String,
    `price` String,
    `__deleted` Nullable(String)
) AS
SELECT
    id,
    status,
    price,
    __deleted
FROM default.kafka_orders;


🧱 Основная таблица
Хранит все версии строк и пометки об удалении.
Для корректной замены старых записей используется ReplacingMergeTree.
CREATE TABLE default.stream_orders
(
    `id` Int32,
    `status` String,
    `price` String,
    `__deleted` String
)
ENGINE = ReplacingMergeTree
ORDER BY (id, price)
SETTINGS index_granularity = 8192;


👀 Витрина данных
Скрывает удалённые строки и возвращает только актуальное состояние данных.
CREATE VIEW default.orders
(
    `id` Int32,
    `status` String,
    `price` String
) AS
SELECT
    id,
    status,
    price
FROM default.stream_orders
FINAL
WHERE __deleted = 'false';

Важно:

постоянное использование FINAL дорого по ресурсам.
В production лучше:
1. Агрегации, - last value
2. Фоновые merge, - ожидание схлопывания данных
3. Материализованные витрины, - предрасчитанные представления

Заключение
Мы собрали полноценный конвейер синхронизации между MySQL и ClickHouse через CDC.
Ключевые элементы:
1. Debezium, - читает binlog MySQL
2. Kafka, - гарантирует доставку и порядок событий
3. Kafka Engine, - потоковая загрузка в ClickHouse
4. ReplacingMergeTree, - устранение дубликатов
5. Поле __deleted, - корректная обработка удалений

В результате получается аналитическая копия боевой OLTP-базы:
MySQL продолжает обслуживать транзакции,ClickHouse — тяжёлую аналитику и отчёты,
оба без взаимных блокировок и деградации производительности.

На этом мы завершаем линейку постов про ClickHouse.Мы разобрали, на мой взгляд, все ключевые аспекты — дальше только практика-практика и еще раз практика

Ещё услышимся 👋
#ClickHouse #MySQL #CDC #Debezium #Kafka #DataSync #OLTP #OLAP #DataEngineering #АналитикаДанных
Media is too big
VIEW IN TELEGRAM
А на досуге можно глянуть новый эпизод нашего подкаста - в нем ребята поговорили с совладельцем EvApps Русланом Ишмухамедовым - предпринимателем, яхтсменом и не выспавшимся отцом - о бизнесе, путешествиях, воспитании детей и будущем аутстаффинга😉

Смотреть в VK Видео
🔥2
Мы вездесущи😈
В том смысле, что теперь ты можешь смотреть и слушать наш подкаст ITToLк там, где тебе удобно:

в Яндекс Музыке: clck.ru/3RiYL9
в VK Видео: clck.ru/3RidoV
в VK Подкастах: clck.ru/3RiduC

Делись в комментариях, какие еще подкасты про IT и на каких площадках ты слушаешь. Мы доберемся и туда😉
🔥2
🤖Сегодня большинство цифровых продуктов рано или поздно получают AI-функциональность

AI-агенты, NLP, автоматические решения — постепенно становятся привычной частью продуктовой разработки.

И вместе с этим появился новый рефлекс:
- Ответ получился странным — переписываем промпт
- Модель ошиблась — добавляем больше контекста.
- Поведение непредсказуемо — промпт недостаточно точный.

Со временем складывается впечатление, что качество работы AI зависит от prompt engineering. На практике это не так.

🔍Что происходит на самом деле

AI редко существует сам по себе. Чаще он встроен в продукт:
Получает данные из backend-сервисов, использует API, опирается на модели данных и выполняет действия внутри системы.
Здесь и возникает главная проблема.
Когда AI начинает работать плохо, команда пытается исправить поведение модели, и промпт превращается в инструмент компенсации архитектурных пробелов.

🛠 Почему всё сводится к промптам
Потому что их изменить проще.
Не нужно наводить порядок в данных, вводить единый источник правды или пересобирать бизнес-логику.
Достаточно добавить инструкции вроде «если данные неполные — сделай разумное предположение» или «если значения конфликтуют — выбери логичное», и вроде бы начинает работать лучше.
Но часть архитектурных решений просто переносится внутрь запроса к модели.

⚡️Важно понимать:
AI не знает, как устроен продукт.
Он видит только ту картину мира, которую открыли — кусок данных, ограниченный контекст, описание правил.
Если бизнес-логика не формализована, модель будет угадывать.
Если не определила источник истины, будет интерпретировать.

Когда AI начинает не только отвечать, но и выполнять действия, ситуация становится ещё хуже.
AI-агент — это распределённая система с вероятностным компонентом: запросы выполняются асинхронно, состояние восстанавливается из контекста, операции повторяются, ошибки сложно воспроизводятся.
Если архитектура продукта не рассчитана на такие сценарии, появляются знакомые эффекты: действия выполняются дважды, решения принимаются на неполных данных, система уверенно делает неправильные шаги.
И снова кажется, что проблема в AI, хотя он лишь усиливает системные слабости.

Ограничения, проверки и дополнительные инструкции - помогают снизить количество ошибок.
Но чаще это выглядит так: ещё больше правил в промпте, дополнительные проверки после ответа, просьбы к модели уточнять действия у пользователя.
Снижаются симптомы, но не устраняется причина — отсутствие чёткой структуры взаимодействия между AI и продуктом.

🕵️Непопулярная правда
Со временем становится заметна одна неприятная закономерность: AI не упрощает архитектуру продукта и не компенсирует слабые места.
Наоборот, делает их очевидными.
Когда архитектура изначально спроектирована аккуратно — с понятными границами, согласованными данными и формализованной бизнес-логикой — AI внутри неё ведёт себя спокойно и предсказуемо.

Но если продукт изначально построен на неявных договорённостях, разрозненных данных и логике, размазанной по частям системы, AI начинает создавать противоположное впечатление.
На демо выглядит впечатляюще — модель угадывает намерения, достраивает недостающую информацию и будто бы компенсирует несовершенство.
Однако при реальной эксплуатации эта иллюзия быстро исчезает

Индустрия регулярно попадает в эту ситуацию по вполне понятной причине
Изменение промпта даёт мгновенный результат: поведение модели меняется сразу, улучшение легко показать, прогресс ощущается буквально после следующего запроса.
Архитектурные же изменения требуют времени, редко выглядят впечатляюще и становятся заметны только спустя месяцы.
Поэтому естественно возникает желание улучшать то, что быстрее приносит видимый эффект, даже если это временное решение.

🚀В итоге
Prompt engineering остаётся важным инструментом работы с AI
Но не исправляет продуктовую архитектуру, AI ускоряет момент, когда архитектурные проблемы становятся заметны и раньше, чем это происходило в системах без AI.

#AI #web #development #NLP #architecture #tech
🔥2
В прошлом посте я писал, что prompt engineering не исправит архитектуру.💻
И сразу получил ожидаемый ответ:
«Окей, но умение писать промпты — не менее важная часть работы с AI‑системами».

И… да.
Как и умение писать SQL, который компенсирует плохо спроектированную базу.
Полезно? Конечно.
Но это не отменяет того, что проблема — глубже.

Для тех, кто хочет разобраться в том, как писать запросы лучше, есть книга Prompt Engineering (Lee Boonstra, 2025). 

Давайте разберёмся, что в книге действительно полезно, а что стоит читать с поправкой на реальность.

Что в книге действительно ценно🧩
Книга честно проговаривает то, что хайп вокруг AI обычно замалчивает:
LLM — не разум. Это предсказательная машина.  
Она угадывает следующий токен. Вежливо. Последовательно. Без понимания.

Всё остальное — «мышление», «рассуждение», «принятие решений» — это надстройки, которые мы сами прикручиваем.

Техники, описанные в книге:
- Chain of Thought
- Step‑back prompting
- Self‑consistency
- ReAct
- JSON‑схемы
- Ограничения вывода

Это не магия. Это механизмы контроля.

Если вы когда‑то:
- Оборачивали нестабильный API ретраями,
- Добавляли идемпотентность,
- Заставляли ответ соответствовать схеме, чтобы он не ломал систему,

то вы уже занимались prompt engineering.
Просто не называли это так.

Ключевая мысль книги⚙️
Prompt engineering — это middleware для вероятностных систем.

Все техники в книге решают одни и те же проблемы:
- Недетерминизм,
- Отсутствие структуры,
- Отсутствие контрактов,
- Непредсказуемые ретраи,
- Побочные эффекты.

Классические проблемы распределённых систем.

Только вместо логов — абзацы.
Вместо stack trace — уверенность.

Когда промпт‑инжиниринг действительно уместен🎯
Он отлично работает, когда:
- Задача по природе размытая (язык, суммирование, классификация),
- Цена ошибки низкая,
- Вывод носит рекомендательный характер,
- Можно безопасно ретраить,
- Никто не притворяется, что система детерминирована.

Но если вы используете его, чтобы:
- Применять бизнес‑правила,
- Принимать финансовые решения,
- Менять состояние продакшена,
- Заменять доменную логику,

Вы сталкиваетесь не с проблемами модели, а с техническим долгом, который наконец стал заметен.

Главный урок книги ( о котором явно не говорят)📘
Лучшие промпты в книге имеют общие черты:
- Чёткие входные форматы,
- Явные схемы,
- Узкие обязанности,
- Детерминированные ожидания,
- Скучные, предсказуемые ответы.

Настоящий урок — не:
Стань гуру промптов
а:
«Твоя система наконец должна иметь границы. И AI больше не позволит тебе их игнорировать».

Вывод🧠
Книгу действительно стоит прочитать — не как набор трюков и не как быстрый способ «прокачать» навыки.
Она полезна, если смотреть на неё глазами инженера, который разбирается с тем, как ведут себя вероятностные системы

Prompt engineering не исправит архитектуру.
Он делает важную вещь:
Помогает увидеть те проблемы, которые раньше было легко не замечать.
Возможно, именно поэтому создаётся ощущение, что это такой мощный инструмент.
2
🌐Браузер сегодня — это уже не просто «рендер HTML».
Проблема в том, что мы часто по привычке тянем библиотеки… даже когда нужный инструмент уже встроен:
1) Structured Clone API 🧬
Раньше вопрос «как правильно скопировать объект?» был классикой.
Кто-то вспоминал про ссылки, кто-то — про Object.assign, кто-то — про JSON, а кто-то начинал гуглить 😉
Сегодня всё стало проще:
const copy = structuredClone(original);

Почему это удобно:
- Работает с Map, Set, Date, Blob, File, ArrayBuffer
- Корректно обрабатывает циклические ссылки
- Поддерживается всеми современными браузерами

2) Performance API ⏱️
Мы часто спорим об оптимизациях, но редко честно измеряем результат.
А между тем, браузер уже даёт простой инструмент:
performance.mark("start");
// код
performance.mark("end");
performance.measure("test", "start", "end");
console.log(performance.getEntriesByName("test"));

Это полезно для:
- Микро-бенчмарков
- Сравнения разных реализаций
- Проверки, имеет ли смысл Worker или WASM
Работает стабильно во всех современных браузерах.

3) Page Visibility API 👀
Этот API сообщает, активна ли вкладка.
document.addEventListener("visibilitychange", () => {
if (document.hidden) {
video.pause();
}
});

Реальный мир выглядит так:
Пользователь открыл ваше приложение — и ушёл в другую вкладку на полчаса.
Или вообще забыл вернуться.
Можно:
- Cтавить видео и анимации на паузу
- Останавливать polling
- Снижать нагрузку на CPU
И серверу станет легче жить.

4) ResizeObserver 📐
Наконец-то можно следить за размером элемента, а не только окна.
const ro = new ResizeObserver(entries => {
for (const entry of entries) {
console.log(entry.contentRect.width);
}
});
ro.observe(element);

Если вы делали адаптивные компоненты или графики, вы точно писали костыли под resize.

5) IntersectionObserver 📍
Этот API отвечает на вопрос: попал ли элемент в область видимости?
const io = new IntersectionObserver(entries => {
entries.forEach(entry => {
if (entry.isIntersecting) {
console.log("Элемент виден");
}
});
});
io.observe(element);

Идеально подходит для:
- Lazy loading
- Infinite scroll
- Анимаций при скролле
Любой, кто реализовывал infinite scroll вручную, знает, насколько это «весёлое» занятие 😄

6) AbortController 🛑
Чаще всего его используют с fetch, но на самом деле он подходит для любых отменяемых операций.
const controller = new AbortController();
fetch(url, { signal: controller.signal });
// позже
controller.abort();

Плюсы:
- Можно отменять несколько операций
- Подходит для fetch, стримов, обработчиков событий и т.д.

7) Idle Detection API 🧍
Если Page Visibility говорит о вкладке, то Idle Detection говорит активен ли пользователь за устройством
const detector = new IdleDetector();
await detector.start();
detector.addEventListener("change", () => {
console.log(detector.userState);
});

Пользователь может держать вкладку открытой, но в реальности — уйти на созвон.
Примеры применения:
- Авто-выход из аккаунта
- Статус «away»
- Оптимизация фоновых задач
Поддержка в основном в Chromium и требует разрешения.

8) BroadcastChannel API 📡
Простой способ общаться между вкладками одного сайта.
const channel = new BroadcastChannel("app");
channel.postMessage("logout");
channel.onmessage = e => {
console.log(e.data);
};

Подходит для:
- Cинхронизации логаута
- Cостояния авторизации
Особенно актуально, когда у пользователя открыты пара вкладок.

9) Web Locks API🔐
Нужен, чтобы не делать одну и ту же работу в нескольких вкладках.
navigator.locks.request("data-lock", async () => {
await fetchData();
});

Например:
- Только одна вкладка опрашивает сервер
- Меньше лишних запросов

10) File System Access API 📁
Да, браузер умеет работать с файлами напрямую.
const [fileHandle] = await window.showOpenFilePicker();
const file = await fileHandle.getFile();

Это открывает дорогу для:
- Веб-редакторов
- Инструментов импорта/экспорта
Поддержка в основном у Chromium-браузеров.

А какими вы уже пользовались? 😊

#web #frontend #javascript #dev
🤖 AI убил рынок джунов?
Сейчас много разговоров о том, что AI убил рынок джунов.
Статистика вакансий действительно выглядит пугающе.

Но проблема немного в другом.
Роль junior-разработчика не умерла — просто сломалась карьерная лестница.

Раньше путь выглядел довольно понятно:

junior делал скучную работу — писал тесты, фиксил мелкие баги, делал CRUD, конвертировал схемы.
Для сеньоров это была рутина.
Для джунов — лучший способ понять, как на самом деле работают системы.
Через эту “скучную работу” постепенно появлялось главное:
интуиция — как ломаются системы, где появляются баги, как течёт data flow.
Сегодня эту работу делает AI.
Boilerplate, тесты, схемы, простые функции — всё это модель генерирует быстрее и дешевле.
То есть нижняя ступенька лестницы просто исчезла.

В результате рынок начинает выглядеть как странная “гантеля”:
• На одном конце — супер-сеньоры, которые с AI работают в 10 раз быстрее
• На другом — люди, которые умеют писать промпты
А моста между ними почти нет
И именно это сейчас ломает карьерный pipeline.
Но из этого не следует, что роль junior исчезла.Скорее она трансформировалась.

Новый junior — это не кодер, а аудитор
AI может генерировать код.
Но кто проверяет, что он правильный?
Именно здесь появляется новая роль.
Новый ключевой навык джуна — verification.

То есть способность:
— Читать код, который написал AI
— Находить архитектурные проблемы
— Замечать баги, которые проходят тесты
— Понимать, где AI ошибается
Проблема в том, что проверить можно только то, что понимаешь.
А значит от джунов теперь требуют почти сеньорские навыки анализа —но без нормального пути, как до них дойти.
Раньше обучение происходило через практику.Теперь эту практику всё чаще делает AI.

🔎Исследования это подтверждают
В эксперименте Anthropic джуны с AI выполняли задачи быстрее, но хуже понимали код.
Разница составила –17% по тестам на знание.
Разница не в инструменте.
А в том, как его использовали:
— одни делегировали задачу AI
— другие задавали вопросы и пытались понять решение
Те, кто пытался разобраться, учились.
Те, кто копировал ответ — нет.

🧠Почему это происходит

Во многом мы начинаем понимать идею именно в процессе её формулирования.
Когда этот процесс полностью отдаётся AI, результат появляется — но мышление может так и не сформироваться.
Если junior делегирует решение модели, он получает функцию.
Но пропускает сам процесс рассуждения, который позволил бы заметить, почему это решение может сломаться

⚠️Плохие ответы на Stack Overflow, странные баги, неожиданные проблемы —всё это заставляло сомневаться и проверять решения.
Иногда ответ выглядел правильным, но через пару часов становилось понятно, что он ломает половину системы.
Так появлялось настоящее понимание.
AI убирает этот опыт.
Он всегда отвечает уверенно и выглядит правым.
Поэтому легко просто скопировать решение —
и не разбираться, почему оно работает.

🧑‍💻В 2026 портфолио джуна — это уже не todo-app.

AI делает такой проект за 30 секунд.
Гораздо ценнее показать мышление и суждение:
— “Вот код, который предложил AI — и почему я его отклонил”— “Вот баг, который AI пропустил”
— “Вот как я проверил архитектурное решение”
— “Вот почему чистое решение AI ломается на масштабе”
Сегодня ценится не столько генерация кода, сколько способность разобраться, где он неправильный.
Не генерация. Суждение.

В Если компании перестанут нанимать джунов, потому что “AI дешевле” —откуда возьмутся сеньоры через 5–10 лет?
Пока хорошего ответа ни у кого нет.
Но компании, которые смогут заново построить эту лестницу, получат огромный кадровый запас.

А как вы думаете?
#ai #webdev #career #junior #programming #discussion
🔥5
🎨CSS функции в 2026: насколько далеко они зашли

Когда-то в CSS было всего две функции:

- calc()
- rgb()
И даже calc() многие использовали с осторожностью

Сегодня в CSS появились новые функции:
- anchor()
- anchor-size()
- exp()
- hypot()
- sign()
- shape()
- xywh()
- image-set()
CSS постепенно получает всё больше возможностей для работы с layout, вычислениями и адаптивностью

Разберём несколько интересных функций.
⚓️1. anchor() — позиционирование относительно элемента
Раньше для позиционирования tooltip или popover часто использовали JavaScript:
getBoundingClientRect()
+ scroll offsets
+ ResizeObserver

Теперь часть подобных задач можно решить средствами CSS
.tooltip {
position-anchor: --btn;
left: anchor(left);
top: anchor(bottom);
}

CSS может позиционировать элемент относительно другого элемента-якоря
Это упрощает реализацию tooltip, popover и похожих интерфейсных элементов

📏2. anchor-size() — размеры относительно другого элемента
Теперь элемент может брать размер от другого элемента, а не только от родителя или viewport
.popover {
width: anchor-size(width);
}

Это полезно для:
- Dropdown меню
- Popover
- Контекстных панелей
- Floating UI
Например, когда ширина выпадающего меню должна совпадать с шириной кнопки

📈3. exp() — экспонента в CSS
CSS получил функцию для экспоненциальных вычислений
width: calc(exp(2) * 10px);

Это может быть полезно для:
- Систем масштабирования
- Типографических шкал
- Плавных кривых анимации
Пример:
font-size: calc(1rem * exp(var(--scale)));

📐4. hypot() — вычисление расстояния
Функция возвращает длину гипотенузы по теореме Пифагора
width: hypot(30px, 40px);

Она может использоваться, например, при вычислениях расстояния или диагонали
Иногда применяется в сложных layout или анимациях:
transform: translate(
hypot(10px, 20px)
);

5. sign() — работа со знаком числа
Функция возвращает:
-1
0
1
в зависимости от значения
Пример:
opacity: calc(sign(var(--value)) * 1);

Это позволяет строить простые условные зависимости через CSS-вычисления

🖼6. image-set() — изображения для разных плотностей экранов
Позволяет указывать несколько версий изображения
background-image: image-set(
"image.png" 1x,
"image@2x.png" 2x
);

Браузер сам выберет подходящую версию в зависимости от плотности пикселей экрана
Плюсы:
- Оптимальная загрузка
- Экономия трафика
- Более чёткое отображение на retina-экранах

🔺7. shape() — работа с геометрией
Функция позволяет задавать геометрические формы для clipping
clip-path: shape(
from 0 0,
line to 100% 0,
line to 50% 100%,
close
);

Это упрощает создание нестандартных форм без SVG

📦8. xywh() — понятный синтаксис
Функция задаёт прямоугольник через координаты и размеры
Раньше:
clip-path: inset(10px 20px 30px 40px);

Теперь:
clip-path: xywh(10px 20px 200px 100px);

x, y, width, height
Такой синтаксис легче читать и поддерживать

🧩Итог
Современные функции CSS позволяют решать больше задач прямо на уровне стилей
Многие задачи интерфейса, которые раньше решались с помощью JavaScript, теперь иногда можно реализовать напрямую в CSS.

💬 Как вы относитесь к тому, что часть задач интерфейса постепенно переходит из JavaScript в CSS?
Это упрощает разработку или наоборот усложняет поддержку?


#css #webdev #frontend #webdevelopment #dev #programming
Готовь сани летом, а архитектуру - на старте проекта! 🚀

Если не хотите через пару лет плакать над контроллерами на 2000+ строк и молиться на God Class - приходите на наш вебинар.

2 апреля в 18:30 МСК встречаемся с ведущим fullstack-разработчиком EvApps Михаилом Прохоровым, чтобы поговорить по делу:

Почему «просто MVC» - это путь к архитектурному долгу?
Где проходит грань, когда DDD реально окупается, а не просто «усложняет ради понтов»?
Как тактические паттерны (Value Objects, Aggregates) вытаскивают проект из болота?

Участие бесплатное, но места надо занять😉
🔗 Регистрация по ссылке: clck.ru/3SaKJV
Media is too big
VIEW IN TELEGRAM
Врываемся в твою рабочую неделю с новым эпизодом подкаста IT ToLк🚀

В новом выпуске - Андрей Карпов, сооснователь проекта PVS-Studio: поиск ошибок в коде программ. Андрей 17 лет в IT, у него есть бэкграунд CTO, а сегодня он является амбассадором статического анализа кода, автором целого сборника "вредных советов" о C#, а еще строит DevRel в своей компании.

Что в подкасте?

⭐️ Скучают ли топ-разработчики по C++ после перехода в управление?

⭐️ Rust вытеснит C++ или это хайп?

⭐️ Почему AI-код - это новая головная боль для SAST-инструментов?

⭐️ И главное: что посоветовать себе в 2007 году, чтобы писать код без багов?

Гость честный, вопросы интересные. Поехали 🎧

Смотрим по ссылке: https://clck.ru/3Si2SR
1
Стартуем через полчаса🚀

EvApps приглашает вас на запланированную конференцию: Zoom.
Подключиться к конференции Zoom
https://us06web.zoom.us/j/86917612389?pwd=g9Lpv9yy4Jj4xNP9VqL2BNO5zjqm3u.1

Идентификатор конференции: 869 1761 2389
Код доступа: 874163
1
Architecture Evolution.pdf
1.6 MB
Всем, кто очень хотел, но не успел на вчерашний вебинар по эволюции архитектуры, дарим ссылочку на запись и прочие материалы😊

🔗 Запись вебинара здесь
Презентацию прикрепили👌
Ссылка на код здесь
2
Третья часть про то, что уже встроено в браузер, но мы всё равно городим лишнее.

1) <dialog> 🪟
Признайтесь — вы ставили bootstrap или MUI только ради модалки.
А браузер давно закрыл этот вопрос — и сразу с нормальной доступностью:
<dialog id="alert">
<p>Вы уверены? Это нельзя отменить.</p>
<button onclick="alert.close()">Подумаю ещё</button>
<button onclick="doTheThing()">Да, я понимаю</button>
</dialog>
<button onclick="alert.showModal()">Удалить всё</button>

Никаких зависимостей. Работает. Доступно.

2) Container Queries 📦
Media queries всегда смотрели на окно целиком — и это было источником боли при переиспользовании компонентов. Теперь компонент живёт своей жизнью:
.widget { container-type: inline-size; }

@container (min-width: 500px) {
.widget__body {
display: flex;
gap: 1rem;
}
}

Положи его в сайдбар или в центр страницы — он сам разберётся.

3) @supports 🧪
Раньше — полифилы, гадание на кофейной гуще и молитвы перед деплоем.
Теперь просто пишете условие и браузер сам решает:
.hero {
background: #1a1a2e;
}

@supports (background: oklch(50% 0.2 270)) {
.hero {
background: oklch(20% 0.15 270);
}
}

Старые браузеры получают fallback, новые — красоту.

4) crypto.getRandomValues 🔐
Вот этот паттерн живёт в миллионе кодовых баз:
const id = Math.random().toString(36).slice(2);
// где-то в продакшене плачет дев

Замените на нормальное решение:
const arr = new Uint8Array(10);
crypto.getRandomValues(arr);
const id = [...arr].map(n => n.toString(16).padStart(2, "0")).join("");

Энтропия реальная, вероятность коллизий — смешная.

5) requestIdleCallback ⏸️
Отправка аналитики прямо в момент рендера — один из способов испортить пользователю жизнь незаметно. Браузер сам скажет, когда у него есть свободное время:
const sendStats = () => {
navigator.sendBeacon("/api/stats", JSON.stringify(stats));
};

"requestIdleCallback" in window
? requestIdleCallback(sendStats)
: setTimeout(sendStats, 300);

Safari долго саботировал этот API — fallback обязателен.

6) :focus-within 🎯
Классика жанра: инпут в фокусе, а стилизовать нужно обёртку вокруг него. Раньше это решалось через JS и eventListener. Сейчас:
.field {
border: 2px solid transparent;
transition: border-color 0.2s;
}

.field:focus-within {
border-color: #6c63ff;
}

Чисто, без единой строчки скрипта.

7) navigator.onLine 📶
Офлайн — это не баг, это сценарий. Телефон в кармане, лифт, метро — всё это реальность:
const queue = [];

window.addEventListener("offline", () => {
queue.push(getPendingData());
});

window.addEventListener("online", async () => {
while (queue.length) {
await sendToServer(queue.shift());
}
});

Главная ловушка — online говорит только о наличии сети, но не о том, что ваш сервер жив.

8) Speech Recognition API 🎙
Перед тем как добавить ещё один тяжёлый пакет — сначала это:
const Engine = window.SpeechRecognition || window.webkitSpeechRecognition;

if (Engine) {
const voice = new Engine();
voice.lang = "ru-RU";
voice.onresult = ({ results }) => {
handleInput(results[0][0].transcript);
};
voice.start();
}

Работает только в Chromium-браузерах. В продакшен — с осторожностью, для прототипа — идеально.

9) requestAnimationFrame 🎞
setInterval для анимаций — это как делать setTimeout(fn, 16) и надеяться на лучшее. Браузер знает точный момент перерисовки, воспользуйтесь этим:
let position = 0;

function loop(timestamp) {
position = Math.sin(timestamp / 800) * 120;
el.style.transform = `translateX(${position}px)`;
requestAnimationFrame(loop);
}

requestAnimationFrame(loop);

Плавно, без артефактов, CPU не страдает.

Библиотеки — хорошо. Но перед npm install стоит спросить себя: а не встроено ли это уже? 😄

Что из списка уже в вашем коде?

#web #frontend #javascript #css #webdev
А есть ли среди нас РП и прочие руководители разработки? Сейчас проверим👌

Если тебе знаком регулярный разбор полетов на тему "Почему в прошлом квартале все метрики были на высоте, а деньги все равно куда-то ушли?", и вечная головная боль с бюджетами, тебе точно на наш новый вебинар.

🤬 Команда часами ворошит дашборды, пытаясь понять, "что вообще не так с продуктом", а проблема в сломанной кнопке, миграции, которую никто не отследил, или воронке с дырой размером в 30% пользователей?

10 июня в 18:30 по Москве наш системный аналитик Мария Костыкова расскажет:

Как найти в воронке "слабое звено", в котором исчезает почти треть лидов - и не сломать то, что работает;
Как отличить "метрики для управления" от "метрик для видимости";
Как посчитать ROI самого мониторинга, чтобы прийти к финансистам с цифрами, а не с фразой "нам бы еще отчетик настроить..."

У Маши 8 лет опыта в финтехе, промышленности и на ведомственных проектах - она разбирается💪

⭐️ Результат: ваша команда перестанет тратить часы на диагностику, а вы начнете видеть, где реально и безболезненно срезать затраты.

Участие бесплатно, но без регистрации - никак.
🔗 Ссылочка для регистрации
А вот и прямая ссылка для подключения к вебинару⬇️
Ждем всех в 18:30 МСК👌

EvApps приглашает вас на запланированную конференцию: Zoom.
Подключиться к конференции Zoom
https://us06web.zoom.us/j/85033644309?pwd=fnxE9hKm233Fso1Y5hgo9s1GE2k14l.1

Ссылка на чат конференции
https://us06web.zoom.us/launch/jc/85033644309

Идентификатор конференции: 850 3364 4309
Код доступа: 068498
⚡️Как обещали на вчерашнем вебинаре, делимся полезными материалами для тех, кто хочет знать, какие метрики помогут сэкономить бюджет, а какие дадут только красивые дашборды.

⬇️ Запись вебинара
📎 Презентация
📎 Шаблон дорожной карты

‼️ А подписаться на новый канал для IT руководителей можно здесь
Четвёртая часть про то, что браузер уже умеет сам — а мы по привычке тянем пакет.
Popover API 🎈 В прошлой части был <dialog> для модалок. Но половина случаев — это не модалка, а поповер: меню, тултип, дропдаун. И вот тут раньше начинался цирк с z-index, кликом «мимо» и Escape. Теперь два атрибута:
<button popovertarget="menu">Меню</button>
<div id="menu" popover>
<a href="#">Профиль</a>
<a href="#">Выйти</a>
</div>

Клик вне — закрывает. Escape — закрывает. Всё в top layer, никаких войн со z-index. Ноль JS.
:has() — родительский селектор 🎯 Двадцать лет фронтенд просил «а можно стилизовать родителя от ребёнка?». Наконец можно:
/* карточка, внутри которой есть картинка */
.card:has(img) { padding-top: 0; }

/* форма с невалидным полем */
form:has(:invalid) button { opacity: .5; }

Раньше это был querySelectorAll + навешивание классов вручную. Теперь — одна строка CSS.
crypto.randomUUID() 🆔 Классика, которая до сих пор живёт в проде:
const id = Date.now() + Math.random();

Нормальный UUID v4 встроен:
const id = crypto.randomUUID();
// "6f4d2c1e-8a3b-4f9c-b2e1-7d0a5c3e1f92"

Криптографически стойкий, коллизий можно не бояться. Работает во всех современных браузерах (нужен https или localhost).
Object.groupBy() 🗂 Сколько раз вы писали reduce, чтобы сгруппировать массив? Теперь это одна функция:
const users = [
{ name: "Аня", role: "dev" },
{ name: "Петя", role: "qa" },
{ name: "Ира", role: "dev" },
];

const byRole = Object.groupBy(users, u => u.role);
// { dev: [Аня, Ира], qa: [Петя] }

Есть и Map.groupBy(), если ключом должен быть объект. lodash можно не звать.
Intl.RelativeTimeFormat «2 часа назад», «через 3 дня» — ради этого до сих пор ставят moment/dayjs. А это уже в браузере, да ещё и с локализацией:
const rtf = new Intl.RelativeTimeFormat("ru", { numeric: "auto" });

rtf.format(-2, "hour"); // "2 часа назад"
rtf.format(1, "day"); // "завтра"

Сам знает про склонения и «вчера/завтра». Бесплатно, без килобайтов зависимостей.
Иммутабельные методы массива 🔄 sort() и reverse() десятилетиями мутировали исходный массив — и ловили баги в React, где стейт менять нельзя. Теперь есть неразрушающие версии:
const sorted = list.toSorted((a, b) => a - b);
const reversed = list.toReversed();
const patched = list.with(2, "new"); // заменить по индексу

Оригинал цел, возвращается копия. Больше никаких [...arr].sort().
View Transitions API 🎬 Плавные переходы между состояниями UI раньше означали Framer Motion или ручные костыли. Теперь браузер сам анимирует разницу до/после:
document.startViewTransition(() => {
updateTheDOM();
});

Меняете DOM внутри колбэка — браузер делает кроссфейд между старым и новым кадром. Для SPA-переходов и списков выглядит дорого почти бесплатно. Пока стабильно в Chromium, для остальных — прогрессивное улучшение.
inert 🚫 Открыли модалку, а Tab всё равно уводит фокус на кнопки под ней. Классическая дырка в доступности. Один атрибут — и целое поддерево выключено: ни фокуса, ни кликов, ни скринридера.
<main inert>...весь фон...</main>
<dialog open>...</dialog>

Раньше это был ручной обход всех focusable-элементов и tabindex="-1". Теперь — inert.
text-wrap: balance ⚖️ Заголовок, у которого одно слово одиноко висит на второй строке — вечная боль. Раньше лечили   вручную. Теперь браузер сам балансирует строки:
h1 { text-wrap: balance; }
p { text-wrap: pretty; }

balance — ровные строки для заголовков, pretty — убирает висячие слова в абзацах. Красиво и без ручной вёрстки.
Перед тем как поставить очередную библиотеку, всё чаще стоит проверить — а вдруг платформа это уже умеет? 😄
Что из этого уже используете?
#web #frontend #javascript #css #webdev #dev
🔥1
🤖 Кто отвечает за код, который написал AI?

Ассистент выдал 200 строк, вы нажали Tab. По закону вы, возможно, не владеете ни одной из них — но отвечать за каждый баг будете именно вы. В этом зазоре и живёт вся тема: модель не засудишь, а вендор давно всё написал мелким шрифтом — ответственность течёт вниз, к человеку, нажавшему Tab.

⚖️ Что говорят суды В США копирайт требует живого автора — то самое правило, по которому обезьяна не смогла зарегистрировать своё селфи. Copyright Office уточнил: AI-код охраняется, только если человек внёс «достаточно выразительного вклада». Одних промптов мало — в отчёте это сравнили с рулеткой: колесо крутишь, результат не контролируешь.

Инженерный перевод: «Tab to accept» — это рулетка. А «принял, переписал 4 строки, встроил в свой класс» — уже твой вклад. Код, вышедший из модели нетронутым, может юридически не принадлежать компании так, как код сеньора: его не защитить как актив и не засудить того, кто скопировал.

📜 Что говорит договор Строка из условий GitHub Copilot, которую не цитируют на встречах про внедрение AI: «Вы сохраняете всю ответственность за ваш код, включая Suggestions в нём».

Одно предложение отдаёт вам suggestions — и вешает все риски: лицензии из обучающих данных, чужой копирайт, баги. Это единственная рабочая форма: вендор не может гарантировать чистоту кода, обученного на всём GitHub, поэтому риск уходит туда, где кнопка merge.

🕳 Дыра с защитой от исков GitHub прикрывает от исков — но только на Business/Enterprise. На Individual те же suggestions, тот же риск и ноль защиты. А проекты сплошь едут на личных подписках: компания платит за Enterprise и думает, что закрыта, но код через личный аккаунт под защиту не попадает. CISO узнаёт об этом, когда прилетает иск.

🇪🇺 Регулятору всё равно, кто писал В ЕС смотрят не на владение, а на результат. AI Act, Cyber Resilience Act и Product Liability Directive складываются в одну логику: неважно, писал человек, AI или подрядчик — важен продукт на рынке. Уже 2 августа 2026 вступает часть AI Act. «Это модель предложила» — не аргумент, как и «это подрядчик написал».

⚠️ Тихая проблема ревью AI-код ревьюят менее внимательно: меньше времени, меньше замечаний, больше «компилируется, выглядит норм — го». Диф вдвое длиннее, ревью вдвое поверхностнее — там и проскакивают баг, косяк с лицензией и тихая дыра в авторизации.

🧭 Рабочая ответственность — четыре скучных шага — Все на индемнифицированном тарифе, никаких личных подписок для рабочего кода. — Duplicate-фильтр включён и проверяется в аудите, как SSO. — AI-код на ревью = сторонняя зависимость. Вопрос себе: «подписался бы я под этим, если бы модели рядом не было?» — У каждого PR есть человек с именем, который возьмёт трубку, когда всё сломается. Не модель, не вендор, не «команда».

Каждый suggestion — это вклад анонимного соавтора, который прочитал весь опенсорс планеты, которого не допросишь и которого договор вынес за пределы ответственности. Это не повод бросать AI и не повод тормозить. Вся защита сводится к самой скучной части процесса: человек смотрит на диф и решает, мержить или нет.

Ты написал merge commit — значит, он твой.

А у вас в команде кто отвечает за AI-код? 🤔

#ai #programming #career #law #webdev #discussion
👍1
Поздравления в студию🎉🎉🎉

Мы прошли ежегодную процедуру Минцифры России и в очередной раз подтвердили: EvApps — официально аккредитованная ИТ-компания.

И это вам не формальность "для галочки"!

💭 Ежегодная переаккредитация — это проверка того, что компания действительно ведёт профильную ИТ-деятельность, соответствует требованиям по структуре доходов и продолжает работать прозрачно.

Проходить её из года в год — значит стабильно расти и подтверждать это документально, а не только на словах💪

Что вообще все это значит?

Для наших сотрудников, попадающих под нужные категории, сохраняется отсрочка от призыва, остаётся доступной льготная ИТ-ипотека, а компания продолжает пользоваться налоговыми — а значит, может стабильно инвестировать в команду💚

Для наших партнёров и клиентов это дополнительная гарантия надёжности при выборе подрядчика по аутстаффингу, никаких сюрпризов и рисков, связанных со статусом компании, и это ещё одно подтверждение того, что с нами можно планировать сотрудничество вдолгую.

Спасибо нашей команде, чья ежедневная работа делает такие результаты возможными 🙌
🔥2