Спасибо, я лайкнул – Telegram
Спасибо, я лайкнул
225 subscribers
52 photos
81 links
Еженедельная подборка статей из мира фронтенда, бэкенда, CSS, дизайна, AI, менеджмента и прочих смежных областей.
Ретвичу и собираю в подборку последние тенденции в IT.

@hadoocken
Download Telegram
Всю прошлую неделю я писал про распил монолита, архитектуру, ADR и профилактику эрозии кода. Так вот — это не просто теоретические рассуждения. У нас сейчас в Островке именно такой вызов.

У направления транспортных продуктов есть четыре проекта. На каждом работает по одному фронтендеру, и формально до 20% времени мы можем тратить на техдолг. Но в реальности почти всё время уходит на продуктовые фичи.

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

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

🔎 Что будет:
* команда из двух разработчиков + я в роли лида,
* глубокое погружение в контекст продуктов (ЖД и Авиа -билеты),
* возможность заняться рефакторингом и архитектурой без постоянного давления продуктовых фич.

👉 Если вы сеньор или знаете того, кому это интересно — подавайтесь или рекомендуйте друзьям. Это шанс поработать с настоящим кейсом по распилу фронтенд-монолита.

https://career.ostrovok.ru/vacancy/4041263/
👍822
Пост про нашу архитектуру собрал больше всего огоньков, поэтому логично рассказать о ней чуть подробнее 🔥
Пойдём по порядку.

🧩 Main domain

Вообще, правильнее просто domain — «main» здесь лишь для того, чтобы буквы в аббревиатуре сошлись чтобы показать что домен может быть не main.

Вся суть — в структурной типизации TypeScript (a.k.a. утинной 🦆).
Для описания доменных сущностей мы используем инструмент Valibot.

Так как приложение взаимодействует с несколькими backend-сервисами, каждый из них отдаёт слишком много данных, не всегда нужных фронту. Поэтому мы выделяем из них только то, что действительно важно для работы приложения — main domain.

🧠 Что такое Valibot

Подробно я рассказывал об этом в докладе «Хроники Valibot» 🎥

Если коротко: Valibot (или любой другой рантайм-валидатор, например Zod) позволяет «починить» TypeScript, добавив типизацию не только на этапе билда, но и во время выполнения кода.

Теперь, имея схемы Valibot вместо обычных TS-типов, мы можем гарантировать: любая функция, ожидающая строку, всегда получит строку — иначе мы это узнаем и сможем обработать ошибку.

По сути, в домене мы храним не значения, а их описания.
Благодаря вложенной и гибкой валидации (email: pipe(string(), nonEmpty(), email())) мы получаем value objects из коробки — описания, которые несут в себе часть бизнес-логики и проверяются «на лету».

⚙️ Преобразования в домене

Любое преобразование описаний доменных сущностей — тоже часть домена.
То есть все мапперы, трансформеры, чекеры (`isSomething`), которые обычно попадают в helpers или utils, на самом деле относятся к доменному уровню, если они работают с сущностями.

Это помогает соблюдать принцип Functional Core, Imperative Shell. Ну и как следствие писать тестируемый код.

💡Простой пример

// schema
const PostSchema = object({
noscript: string(),
content: array(string()),
});

// typenoscript type
type Post = InferOutput<typeof PostSchema>;

// domain transformation
function isLongRead(post: Post): boolean {
return post.content.length > 5;
}


Теперь любой слой — в обе стороны от домена — должен зависеть от этого типа, и никогда наоборот.

🧭 В итоге domain становится центром приложения, который описывает бизнес-реальность: сущности, их поведение и правила. Всё остальное лишь обслуживает его — и это ключевой принцип матричной архитектуры.
3👍3🔥2
Спасибо, я лайкнул pinned «Всю прошлую неделю я писал про распил монолита, архитектуру, ADR и профилактику эрозии кода. Так вот — это не просто теоретические рассуждения. У нас сейчас в Островке именно такой вызов. У направления транспортных продуктов есть четыре проекта. На каждом…»
Application use-cases — название говорит само за себя. Это наши основные блоки бизнес-логики, сосредоточенные вокруг домена. Если вы хоть раз декомпозировали задачу по гибкому процессу, то наверняка помните user-story вроде: «Как пользователь, я хочу видеть все свои заказы». Юз-кейсы — это 1:1 маппинг таких сторей на код.

🧑‍💻 Что это в коде?
Интерфейс юз-кейса очень прост:

export interface UseCase<TInput, TOutput = void> {
execute(input?: TInput): Promise<TOutput>;
}


Есть что-то на входе, потом выполняется сценарий (execute), и что-то получается на выходе. Вызываешь execute — получаешь результат; тестировать такое удобно и просто.

🧪 Валидация входа/выхода.

Из прошлого поста помним, что вход должен соответствовать типу — у нас для этого Valibot. В больших системах юз-кейсов может быть сотни, и писать в каждом parse(input, InputSchema) в начале и parse(result, OutputSchema) в конце — плохо: это дублирование и убийство тестируемости.

Решение — вынести валидацию в базовую реализацию и оставить в конкретных юз-кейcах только бизнес-логику.

👩‍👦 Паттерн: базовый класс для UseCase.

Идея — абстрактный класс, который отвечает за валидацию и за «безопасный» вызов, а конкретные юз-кейсы наследуются и реализуют только execute.

abstract class BaseUseCase<TInput, TOutput>
implements UseCase<TInput, TOutput>
{
constructor(
private inputValidator: Validator<TInput>,
private outputValidator: Validator<TOutput>,
) {}

async #safeExecute(input?: TInput): Promise<Either<Error, TOutput>> {
this.inputValidator.validate(input);
const result = await this.execute(safeInput.value);
return this.outputValidator.validate(result);
}

async exec(input?: TInput): Promise<Either<Error, TOutput>> {
const result = await this.#safeExecute(input);

if (result.isRight()) {
return result;
}

return Left.create(result.error);
}

abstract execute(input?: TInput): Promise<TOutput>;
}


Наследуемся — и базовый класс заставит нас реализовать execute, при этом коммуникация с валидатором и обработка ошибок уже централизованы.

♊️ Про монаду Either:

Внимательный читатель заметил: мы используем монаду Either. Это приём, чтобы юз-кейсы всегда возвращали значение — независимо от успеха или ошибки.
Преимущество: меньше неожиданных падений и лучше наблюдаемость — слой, который вызывает UseCase (обычно UI), обязан обработать Either и при ошибке показать нужное уведомление.

🔎 «(Не|ну)жная монада Either: теория и практика» 🧠
Коротко: доклад Дмитрия Махнёва и Артёма Кобзаря, в котором разбирают Either-монаду и её прикладное использование для безопасной обработки ошибок. Показывают, почему Either часто удобнее исключений и как встроить её в реальные JS/TS-проекты.
#either #monads #errorhandling
👍4🔥31
💡 Toolkit или Ties — узлы на пересечении горизонтальных и вертикальных слоёв системы. Это инструменты, которые связывают юз-кейсы со всеми слоями архитектуры, сохраняя при этом модульность и тестируемость.

📘 Об этом подробно рассказывает Роберт Мартин в первых главах «Чистой архитектуры». Ключевая мысль: избавляйтесь от нестабильных импортов.

Если вы импортируете что-то из внешнего источника (например, npm-пакет), вынесите этот код в свой слой.
Так вы превращаете нестабильный импорт в стабильный, контролируемый вами.

🔧 Пример:
вместо прямого import * as Sentry from 'sentry' создайте свой хук useTracking, который внутри может использовать Sentry, Datadog, Prometheus — что угодно.
Захотите поменять инструмент — меняете только внутреннюю реализацию, а не весь код, использующий это.

🎯 Ещё один принцип — направление зависимостей:
импорты кода идут от центра к периферии, а не наоборот.

Общий слой не должен знать о специфичном.

Но здесь может появиться ловушка. Например, транспортный слой, который просто делает fetch-запросы, вдруг должен начать понимать, что ошибки с core-backend надо обрабатывать по-особому. Потом появляется второй сервис, третий… и в коде множатся if и импорты.

🧩 На помощь приходит DI (Dependency Inversion).

Мы определяем контракт для обработки ошибок, например:

if (!response.ok) {
const parsedError = await this.errorTools.parseError(response.clone());
if (parsedError) { ... }
}


Для базового случая используем заглушку:

di.bind<ErrorTools>(ERROR_TOOLS_DI_TYPE).to(BaseErrorTools);


А при необходимости — переопределяем поведение, не трогая общий код:

di.rebind<ErrorTools>(ERROR_TOOLS_DI_TYPE).to(SpecificErrorTools);


И даже в рамках одного юзкейса можно временно заменить зависимость:

export const getOrder = (input: GetOrderInput) => {
di.rebind<ErrorTools>(ERROR_TOOLS_DI_TYPE).to(SpecificErrorTools);
const result = di.get<GetOrderUseCase>(GET_ORDER_DI_TYPE).exec(input);
di.rebind<ErrorTools>(ERROR_TOOLS_DI_TYPE).to(BaseErrorTools);
return result;
};


В итоге:
* основной код остаётся нетронутым
* транспорт остаётся общим
* а конкретное поведение легко настраивается через DI
🔥32💯2
UI Renderer или Representation — следующий слой нашей архитектуры.

Есть ощущение, что всё, что я уже написал, больше подходит для бэкенд-разработки?
Да, это практически так и есть. MATRIX появилась в условиях разработки BFF, а это и есть обычный бэкенд-сервис.

Но на фронтенде ситуация практически та же самая.
Я имею в виду все те принципы, что мы уже рассмотрели:
* доменные сущности и их преобразования,
* стабильные импорты,
* направление импортов и т. д.

😦 Из нового здесь, пожалуй, лишь то, что у нас все компоненты — "глупые".

Надеюсь, все знакомы со статьёй Дэна Абрамова где он описал паттерн разделения React-компонентов на презентационные (глупые) и контейнерные (умные). Презентационные компоненты отвечают только за отображение UI и получают данные через props, а контейнерные управляют логикой и состоянием.

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

Поэтому у нас остаются только "глупые" компоненты, для которых мы прописываем свой контракт взаимодействия (пропсы), чтобы они приняли нужные пропсы и отобразили нужный UI. Остаётся только как-то передать эти пропсы в компоненты. Здесь на помощь приходит паттерн composeHooks.

На самом деле, это что-то вроде connect из Redux, который подключает компонент к стору с данными. Только в нашем случае composeHooks подключает компоненты к доменным сущностям в агностик режиме.

React Hooks Compose — композиция хуков для чистой архитектуры 🧩
Библиотека react-hooks-compose позволяет отделить хуки от компонентов, создавая переиспользуемые контейнеры. Она работает по принципу Redux connect, но для хуков — принимает хук и компонент, возвращает компонент с внедрёнными данными. Ключевой момент: хуки не содержат бизнес-логику, а служат адаптерами для подключения доменных сущностей к UI.
#hooks #architecture #composition
👍51💯1
Incoming ports — входящие порты — последний недостающий элемент, который связывает слой домена и слой UI.

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

По сути, порты — это граница между нашим BFF и тем, что находится в браузере пользователя.

Подробно останавливаться на них нет смысла — всё это уже отлично описано в документации по шестиугольной архитектуре (Hexagonal Architecture). Формально, входящие порты в матричной архитектуре выполняют ту же роль.

Зато стоит отметить, какими могут быть виды входящих портов 👇

* API-вызов
Когда мы напрямую обращаемся к хендлеру и обрабатываем параметры запроса.

* Серверный action
Почти то же самое, что и API-хендлер, но с улучшенным DX — вызов эндпоинта замаскирован под вызов обычной функции.

* Серверный компонент
Вариант для случаев, когда мы работаем с fullstack-компонентами или layout-структурами в новом роутере Next.js.

Вероятно, существуют и другие способы запускать use case в приложении, но мы используем только эти три на постоянной основе.
👍21🔥1
🌐 eXternal Adapter — последний ключевой элемент матричной архитектуры.

Как видно из названия, это слой, который адаптирует запросы нашего приложения к внешнему миру.
Приложение может существовать само по себе, внешние сервисы — тоже, но чтобы они могли взаимодействовать, нужен общий протокол.
Слой адаптера как раз и выполняет эту роль.

🧩 Что считается внешней системой?
Чаще всего — это другой бекенд-сервис (микросервис), который принимает запросы и возвращает данные.

Но также внешней системой может быть:
* файловая система
* шина данных
* вебхуки
* или любые другие источники вне нашего приложения.

Соответственно, протокол взаимодействия будет зависеть от типа системы:

* для бэкендов — HTTP или gRPC
* для файлов — локальное чтение, S3 и т.д.

Поэтому внутри адаптера обычно выделяют подслой транспорта, который отвечает за конкретную коммуникацию.


⚙️ Пример: взаимодействие с бекендом

Слой адаптера, как и любой другой слой, должен иметь свой интерфейс.
Для сетевых запросов мы можем свести всё к двум операциям — query и mutation (да, почти как в GraphQL).

Где бы нам ни понадобилось обратиться к внешнему сервису, мы используем эти два метода, а уже адаптер решает, как именно выполнить запрос — через REST, GraphQL или что-то ещё.


🧱 Как сделать транспорт стабильным?

Проблема: параметры запроса в fetch (method, body, headers) и в GraphQL (query, variables) — разные.
Решение: развернуть зависимость на стабильный импорт.

Создаём свой интерфейс TransportRequest, в котором обобщаем параметры:

* вместо resource для REST используем query для GraphQL
* вместо bodygraphql query string

Так мы создаём единый контракт, не зависящий от конкретного транспорта.


📁 Пример с файловой системой

Здесь тоже появляется дополнительный уровень:
создаём интерфейс FileMeta с полем baseDir, где локально это корень проекта, а для S3 — сервер с файлами.

Дальше — транспорты:

* node:fs для локальной работы
* s3-sdk для облака

При этом сам адаптер FS может иметь минимальный набор методов, например:

* files()
* write()
* read()

То же самое и с API-адаптером — он просто предоставляет query() и mutation().


💡 В итоге eXternal Adapter — это граница между вашим приложением и внешним миром,
которая изолирует сложность коммуникации и делает систему по-настоящему стабильной и расширяемой.
🔥31👍1
Итак, новая неделя — новая тема для серии.

Вообще, в Питере наступает настоящая осень: небо затянуло серой непроглядной тьмой, и единственное, что приходит на ум — «набросить на вентилятор» 😈

Так вот, давайте набросим на самое дорогое для фронтендеров — стейт-менеджеры не нужны!

Ну реально — это же нескончаемый источник хейта среди разработчиков. Зачем ещё плодить холивары?

А если немного вернуться в объективное русло, то у нас ведь всегда был самый крутой стейт-менеджер — Браузер и его адресная строка.

Всё состояние страницы можно описать прямо в URL — фильтры, сортировки, активные вкладки, выбранные элементы. Когда придёт время, приложение просто подгружает нужные данные в соответствии с этим состоянием.


🧩 В типичном фронтенд-приложении стейт обычно размазан по нескольким слоям:

* основная масса живёт на бэкенде,
* часть хранится на BFF, чтобы быстро получать данные,
* остатки — на клиенте, где мы с ними возимся.

И чтобы ничего не потерять, запускается бесконечный цикл синхронизации между этими слоями.

Пользователь изменил инпут → сработал эффект → эффект дернул ручку → запрос ушёл на BFF → там его обогатили → передали дальше → вернули ответ → разгрузили → обновили клиент...
♻️ И так по кругу.


Почему бы не сделать этот круг единым?

Есть адрес, описывающий состояние приложения → загружаешь приложение в нужном состоянии.
Что-то изменилось → обновился URL → приложение загрузило новое состояние.

Просто. Прозрачно. Логично.

Используйте для этого серверные компоненты или loader-pattern — и всё начнёт работать естественным образом, без лишнего прослойного хаоса.


📄 «А как же промежуточное состояние? Пока я не сабмитнул форму, где мне хранить данные?»

Отличный вопрос. Но тут тоже есть ответ.
Для формочек ты, скорее всего, уже используешь библиотеку — а ведь форма сама по себе и есть стейт-менеджер.

Пока ты заполняешь инпуты, внутреннее устройство хранит твои данные, даёт возможность подписываться и изменять стор.
Формы — это тот самый локальный стейт-менеджер, который уже у тебя есть.

Доклад по теме: Роутер как у сына маминой подруги — Павел Малышев

А ты как считаешь, нужны стейт-менеджеры или нет?

💯 — согласен, стейт-менеджеры не нужны
🗿 — не согласен, без стейт-менеджеров только лендинги клепать
🗿6💯31🔥1
Юнит-тесты не нужны! (...больше?)

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

Ладно, бекендеров можно понять: у них тесты давно встроены в культуру. Без них тимлид просто перестанет тебя уважать и не прожмёт апрув. Но как объяснить фронтендерам, что тесты — это не пытка, а инструмент?

С развитием фронтенд-тулинга ситуация, к счастью, меняется.
Тот же TypeScript и появление мета-фреймворков (Next.js, Remix и т.д.) превратили классическую пирамиду в кубок тестирования 🏆 — концепт от Кента Доддса.
Благодаря статической типизации можно меньше писать юнитов и сосредоточиться на интеграционных тестах — ведь именно там живут связи и самые критичные сценарии.

🔗 The Testing Trophy and Testing Classifications
«Кубок тестирования и классификация тестов» 🏆
Кент Доддс предлагает заменить пирамиду тестирования на «кубок»: меньше юнитов, больше интеграционных тестов, а E2E — как вершина уверенности в системе.
#testing #frontend #kentdodds

При этом E2E-тесты всегда остаются в меньшинстве.
Аргумент классический: дорого, долго, муторно.

Но, кажется, больше нет!

Недавно я уже писал про тулинг для LLM и про MCP — протокол взаимодействия моделей с инструментами.
Теперь представьте: вы прикручиваете браузерный движок (Playwright, Puppeteer и тд) к LLM — и модель сама заходит в браузер, смотрит страницу и пишет на неё тесты. 🤯


Так мы и сделали.
У нас был один монолит без тестов, и было страшно его трогать. Решили написать смоуки, но чисто на вайбе — через LLM.

За неделю, в неспешном режиме, всё было готово. И даже для меня — душнилы на ревью — код автотестов оказался на удивление приемлемым.


💡 Выводы после эксперимента:

Нет, полностью автоматизировать процесс без участия человека не выйдет.
Но создать гибрид человек–LLM–браузер — реально.

Ты просишь модель открыть браузер, сам выставляешь нужное состояние, а потом — просишь перевести всё это в тест.
Ваши сознания буквально сливаются, как в фильме про кайдзю 🦖: вы видите одно и то же, понимаете контекст и друг друга.

Главное — стоимость тестов реально падает.
Ты меньше устаёшь, не переключаешь режимы (писатель, наблюдатель, редактор), а код ревью всё так же может делать другой человек.


А ты что думаешь?

💯 — согласен, юниты больше не нужны, e2e и так всё покрывают
🗿 — не согласен, если каждый модуль протестирован, то и вся система норм
👍3💯3🗿3🤔1
Swagger не нужен!

Как часто у вас бывает, что команда разделена на бэкендеров и фронтендеров, и между ними минимум коммуникации?
Фронт пилит своё, бэк — своё, а когда приходит время интеграции, бэкендер просто кидает ссылку:
«Вот тебе Swagger, разбирайся».

И ведь поддерживать Swagger в актуальном состоянии никто не собирается 🙃
Стоит в контракте появиться новому обязательному полю или поменяться структуре — фронт падает.
И начинается классика жанра: «А что, вы не знали? Мы же в Jira написали!»

Проблема стара как REST.
Все эти попытки наладить процесс обмена знаниями о новых схемах обычно кончаются ничем — потому что знание не передаётся, пока не ломается прод.


🎯 У нас есть продукт — заказ трансферов.
Типичный кейс: аренда машины с водителем из аэропорта в отель и обратно.

Когда продукт только начинался, мы сами добавляли поставщиков:
договаривались о контрактах, согласовывали схемы данных, тестировали ручки.
За три года добавили всего четырёх поставщиков — медленно, мучительно, предсказуемо.

А потом продукт выстрелил 🚀
И мы поняли, что в таком темпе не вывезем.

Решение оказалось простым:
мы сделали reverse API — когда не мы интегрируем поставщиков, а они сами интегрируются к нам.
Потому что мы крутые 😎

Результат — 20+ новых поставщиков за следующие 3 года.


💡 И вот тогда меня осенило.

Почему фронтендеры не могут поступить так же?

Почему бы, не ждать, пока фронт упадёт из-за новой схемы, а сделать так, чтобы бэкендеры сами приходили и говорили:
«Ребята, мы сейчас будем вас ломать — вот новая схема, поддержите её».


⚙️ Как это реализовать?
Очень просто.

Пишешь автотесты (привет прошлому посту 👋), но добавляешь их не к себе в пайплайн, а в пайплайн бэкенда.

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

И да, если хочется избежать всей этой бюрократии — сделай все поля опциональными и не рассчитывай ни на кого. 😉


А ты как считаешь?

💯 — даёшь реактивный подход, пусть бэкенд сам просит поддержать их схему
🗿 — проактивный подход норм, мы сами будем следить за своими источниками данных
😁6🗿5💯2🔥1
Мусорные npm-пакеты не нужны! (кто бы сомневался)

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


🤔 Что за e18e?

Когда я впервые увидел это сокращение, у меня перед глазами пронеслись классические a11y, l10n и i18n.
А потом я узнал, что e18e расшифровывается как Ecosystem Performance — и это, внимание, движение по очистке JavaScript-экосистемы от мусора.

💡 Идея простая, но мощная:
e18e — это комьюнити, которое объединяет разработчиков вокруг идеи сделать экосистему JS легче, быстрее и чище.

Если коротко, это как обещание «убрать мусор из океана зависимостей».
Благородно, но с чего вообще начать?


🧹 Команда e18e определила три направления:

1️⃣ Clean up — убрать или заменить избыточные зависимости.
2️⃣ Speed up — ускорить популярные пакеты и фреймворки.
3️⃣ Level up — строить современные, лёгкие альтернативы старым библиотекам.


🔥 Началось всё с репозитория replacements project, где собраны community-driven альтернативы популярным пакетам.
Эти данные уже используются разными инструментами:

* ESLint-плагин, который советует, какие зависимости стоит вычистить,
* набор codemods, которые автоматически мигрируют ваш код на современные пакеты или нативные API.

Кроме того, участники e18e активно делают performance-патчи в известных JS-библиотеках и публично тегают тех, кто помогает чистить экосистему.

А ты что думаешь?

💯 – да, это благородная миссия
🗿– не уверен, что JS-экосистему можно спасти
💯12🗿5👍1🤔1🤡1
Сегодня без набросов 🌿

Выступаю на конференции в Светлогорске – осень уже добралась и сюда 🍂
Но, как ни странно, смена обстановки реально снимает токсичность.

А ещё хотел вернуться к посту про вакансию в Островок.
Там есть приятный бонус:
— Амбассадоры компании могут бронировать отели бесплатно (в рамках командировок, конечно 😄)
— Остальные сотрудники получают корпоративные цены на отели и транспорт.

Ни на что не намекаю 😉
Всем хорошей пятницы и выходных!
🔥72💯2
Есть такое ощущение, что людей, пишущих MCP-серверы, гораздо больше, чем тех, кто их реально использует 😅

Недавно попробовал новый фреймворк для написания MCP — xmcp.

На первый взгляд всё круто: удобная структура, DX на уровне, TypeScript — всё как мы любим.

Но вот UI-ресурсы не захотели дружить с новым Apps SDK от OpenAI 🤷‍♂️

Зато официальный SDK для MCP работает чётко, без танцев.

Видимо, пока придётся остаться на нём — а за xmcp понаблюдать, потенциал у проекта явно есть.

#mcp #openai
💯31👍1
Пост для тех, кто не стал слезать с кофеиновой иглы, а решил копнуть поглубже.

Есть такое, что определённые кофейные напитки напоминают разработку.

🧊 Капучино — фронтенд-разработка

Всегда украшен латте-артом, разнообразные архитектуры, тысячи вариаций — но по факту разное количество молока.

Главная цель — сделать вкусно пользователю.


☕️ Эспрессо — бэкенд-разработка

Кратко, мощно, концентрировано.
Как и хороший backend-код — его не видно, но он делает всю тяжёлую работу.

Без украшений, без пенки и сиропов — только суть.

Требует точности: одна секунда лишнего — и вкус испорчен. Как и запрос к базе или по API.

И всё бы ничего…
Но как будто эти дескрипторы эспрессо — выдают в себе что-то фронтендерское, правда ведь? 😏
👍2💯2🔥1
Новый Big Thing в разработке при помощи ИИ?

Anthropic представили новый подход к тому, как помочь LLM лучше понимать, чего вы от неё хотите.
Они назвали это Skills — по сути, это обычный md-файл, описывающий, как именно действовать в конкретной области.

Например, Skill для генерации PDF содержит примеры, инструкции и рекомендации, как собирать файлы без ошибок.
LLM, видя такой файл, может выбрать нужный «скилл» и действовать по заранее описанным правилам.

🎯 Главная идея — оптимизация контекста.
Вместо огромного системного промпта «на все случаи жизни» — чёткие описания навыков, которые подгружаются по необходимости.

Что-то похожее я уже рассказывал, когда писал про memory-bank для Cursor: там IDE понимала, какие файлы ей нужны в зависимости от режима.
Теперь же — та же концепция, но уже в виде официальной реализации. Ещё один сайд-проект стал каноном.

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

* 🧩 MCP — это целый протокол, сервера и клиенты.
* 🪶 Skills — это просто файлы. Простые, лёгкие и при этом экономные по токенам.

Даже банальное переключение режимов теперь не нужно — какой-нибудь Skill Gateway сам подгрузит нужные инструкции или гайдлайны.

📎 Ссылки для погружения:

* Equipping Agents for the Real World with Agent Skills🌍 как Anthropic видит будущее LLM-агентов.
* Anthropic Skills overview🧠 подробности о формате и целях.
* Simon Willison on Claude Skills💬 мнение разработчика о практическом применении.
* Anthropic Skills Repo🛠 репозиторий с примерами «скиллов»

#ai #anthropic #claude #llm

А ты как думаешь?

💯 — согласен, новый виток в тулинге для ИИ
🗿 — не увидел ничего прорывного
💯7🗿3👍1
Как впихнуть в LLM больше контекста? 🌐

После появления текстовых моделей логичным шагом стали мультимодальные — они могут принимать текст, изображения, голос, видео и отвечать по содержанию, которое вы им передали. Чего стоит только история, когда по рисунку на салфетке новая (на тот момент) ChatGPT сгенерировала код для приложения.

Так вот, дают ли такие модели что-то ещё кроме дополнительных каналов связи?

💸 Иногда — да. На мультимоделях можно экономить.

Слово «токен» у всех прочно связано с чисто текстовыми моделями. Но токены есть и у моделей «изображения+текст» — просто считаются они иначе. Очень-очень грубо: один токен в такой модели может соответствовать, например, патчу изображения 20×20 пикселей. Если подобрать шрифт и качество картинки, в этот «визуальный токен» можно упаковать больше текста за ту же цену, чем если бы мы кормили модель чистым текстом. Этот трюк называют оптической компрессией.

Это примерно как смотреть YouTube на скорости ×2: мозг справляется, а вы экономите время. Здесь модель «смотрит» текст как картинку и экономит токены.


Подробнее про оптическую компрессию
🧩 Should LLMs just treat text content as an image?
Короткая заметка о том, почему визуальные токены могут быть информативнее текстовых: изображение кодируется более «плотно», а значит часть задач выгоднее решать через картинку с текстом, чем через текстовые токены. Автор обсуждает компромиссы и где такой подход даёт экономию.
#multimodal #tokenization #compression

Подробно про топовые визуальные LLM
🤖 How to use frontier vision LLMs: Qwen 3 VL-2
Разбор современного Qwen 3 VL-2: как запускать, чем он хорош для OCR, анализа документов и многошагового вывода; практические примеры промптов и режимов ввода. Полезно как стартовое руководство по работе с сильной мультимодальной моделью.
#qwen

Открытые модели для OCR и пайплайны
📄 Supercharge your OCR Pipelines with Open Models
Обзор открытых OCR-стеков: как современные модели учитывают разметку страницы, когда помогает промпт-ориентированная настройка и что выбрать для документов со сложной структурой. Есть рекомендации по моделям и практические советы для продакшн-кейсов.
#ocr #opensource
👍41
🎨 CSS: функции и условные стили

5 полезных CSS-функций на новом правиле @function
Уна Кравец показывает, как писать собственные CSS-функции (начиная с Chrome 139): отрицание значений, полупрозрачные цвета, fluid-типографика, условный радиус и layout-сайдбар. Функции принимают аргументы и возвращают вычисленное значение — удобно для дизайн-систем, но кросс-браузерная поддержка пока отстает.
#css #functions

Разбираемся с CSS if(): условное цветовое оформление
Автор объясняет, как if() даёт инлайновые условия прямо в декларациях css — удобно для темизации на кастомных свойствах.
#css #if

Пользовательские функции в браузерном CSS
Мириам Сюзанн о прототипе author-defined функций, доступном в Chromium Canary с флагом Experimental Platform Features. Разбирается синтаксис (@function --name() { result: … }), параметры и условные результаты.
#css #functions


🧩 Данные и сериализация

jsonriver: быстрый потоковый парсер JSON
Инкрементально парсит JSON из стрима (сетевой запрос, LLM и т. п.), отдавая всё более полные значения по мере поступления байтов. Маленький, без зависимостей, на стандартных Web API.
#json #streaming

Сериализация и десериализация — и как не «сломать» React
Практики, чтобы JSON.parse/stringify не вызывали лишние ререндеры если ваш единый источник данных это десериализованнй стейт: write-only режим и структурное шаринг-обновление через replaceEqualDeep.
#react #serialization #performance

maml.js: парсер формата MAML для JS/TS
Крошечный парсер/сериализатор для MAML JSON: 0 зависимостей, ~2 kB gz, работает в Node/Deno/Bun/браузерах. В бенчмарках существенно быстрее YAML/TOML (до ×64).
#maml #json

🛡 Безопасность npm и загрузок

pompelmi: сканер файлов для Node.js с YARA и deep-ZIP
Быстро проверяет загружаемые файлы на вредоносный код, умеет глубокий анализ архивов и встраивается как middleware для Express/Koa/Next.js и GitHub Action для CI. Акцент на приватность и минимализм.
#nodejs #security #malwarescanning

NPM Security Best Practices: защита от атак цепочки поставок
Сводка практик для разработчиков и мейнтейнеров. База – фиксируйте версии, используйте lock-файлы, отключайте lifecycle-скрипты, вводите минимальный «возраст» релиза, включайте 2FA и provenance. Охватывает npm, bun, deno, pnpm и yarn.
#npm #supplychain #bestpractices #security

npq: проверка пакетов до установки
CLI-утилита, которая до инсталла оценивает пакет по базе уязвимостей (Snyk) и эвристикам: возраст, скачивания, наличие README/лицензии, pre-/postinstall-скрипты. Можно добавить алиас на обычный npm/yarn, чтобы проверка была всегда.
#npm #security #cli
🔥4👍2
OpenAI и MCP: как OAuth 2.1 решает главную проблему интеграций?

OpenAI объявила о полной поддержке Model Context Protocol (MCP) в режиме разработчика!

🤔 Что происходит?

OpenAI запустила Apps SDK — инструмент для создания приложений, которые работают прямо внутри ChatGPT. Представьте: вы пишете в чат "Найди мне квартиру в Москве до 50к", и ChatGPT вызывает приложение с картой, фильтрами и всей логикой — без перехода на другой сайт. Под капотом всё это работает через MCP.

К концу 2025 года OpenAI планирует открыть размещение приложений для всех желающих.

🧑‍💻 Проблема, которую все игнорировали:

Когда разработчики начали создавать MCP-серверы для продакшена, они столкнулись с неприятным вопросом: как аутентифицировать пользователей?

Цепочка выглядит так:
user → ChatGPT → MCP server → external API → response back

От чьего имени делать запросы к вашему серверу? Как понять, что пользователь действительно имеет право читать финансовые данные или создавать заказы? API-ключи работают для демок, но в продакшене это дыра в безопасности — один утекший ключ даёт полный доступ навсегда.

MCP долго не имел стандартного решения для авторизации пользователей. До тех пор, пока в спецификацию не вписали OAuth 2.1.

🗝 Почему именно OAuth 2.1

OAuth 2.1 решает сразу несколько критичных задач:

1. Scoped tokens вместо master keys
Вместо одного ключа на всё — токены с ограниченными правами (read:data, write:orders). Нужны только данные? Получите токен только на чтение.

2. Время жизни токенов
Токены автоматически истекают через заданное время. Утёк токен? Ничего страшного — через час он мёртв.

3. PKCE для публичных клиентов
ChatGPT — это публичный клиент, он не может безопасно хранить секреты. PKCE (Proof Key for Code Exchange) позволяет безопасно аутентифицироваться без встроенных паролей.

4. Dynamic Client Registration
MCP-клиенты могут регистрироваться автоматически через RFC 7591. Не нужно вручную создавать OAuth-приложения для каждого сервера — всё происходит на лету.

5. Discovery через metadata
MCP-сервер публикует метаданные по адресу /.well-known/oauth-protected-resource. ChatGPT читает документ, узнаёт, какой authorization server использовать, какие scopes поддерживаются, и автоматически запускает OAuth-флоу. (То самое, "Войти через Google")

🗄 Как это работает на практике

1. Пользователь вызывает инструмент в ChatGPT
2. MCP-клиент получает 401 от сервера с указанием на metadata
3. ChatGPT динамически регистрируется на authorization server
4. Пользователь логинится через OAuth (Google, корпоративный SSO)
5. ChatGPT получает access token с нужными scopes
6. Все последующие запросы идут с заголовком Authorization: Bearer <token>
7. MCP-сервер проверяет JWT: issuer, audience, expiry, scopes.

🔗 Полезные ресурсы:

* OpenAI Apps SDK — официальная документация
* Apps SDK Authentication — про OAuth 2.1 в контексте OpenAI
* MCP Authorization Spec — полная спецификация протокола
* WorkOS MCP Auth Guide — практическое руководство с примерами кода
🔥5👍2
🎯 А вот и гайды для Next.js подъехали: как запустить фреймворк внутри ChatGPT

Next.js не был бы самим собой, если бы не надо было патчить нативные API. Очевидно, чтобы поддержать уровень безопасности на высоте, OpenAI пытается вставить палки в колёса злоумышленникам, но при этом страдают обычные разработчики. Чтобы ваше приложение работало в полной изоляции, Apps SDK подразумевает, что оно будет запускаться triple iframe способом. То есть ChatGPT → Sandbox iframe → Inner iframe → Your app

🔧 Какие с этим проблемы:

1. Triple-iframe убивает asset loading
ChatGPT рендерит приложения в трёхслойной структуре iframe'ов. Next.js думает, что его origin — web-sandbox.oaiusercontent.com, и все запросы к /_next/static/ возвращают 404.

Решение: assetPrefix в next.config.ts форсирует запросы на реальный домен.

2. Относительные URL ломаются везде
Изображения, шрифты, API calls — всё резолвится к sandbox-домену.

Решение: HTML <base href={baseUrl}> устанавливает базовый URL для всех относительных путей.

3. Browser History палит реальный домен
history.pushState сохраняет полные URL https://your-app.vercel.app/about, что ломает sandbox security.

Решение: патч, оставляющий только path + search + hash.

4. Client-side navigation делает fetch не туда
При клике на Link, Next.js делает fetch за RSC payload, но запрос идёт на sandbox-домен.

Решение: пропатчить window.fetch, переписывая same-origin запросы на правильный base URL с mode: "cors".

5. CORS блокирует React Server Components
Cross-origin запросы фейлятся без CORS headers.

Решение: Next.js middleware обрабатывает OPTIONS и добавляет CORS headers ко всем ответам.

🔗 Почитать подробнее:

* Running Next.js inside ChatGPT
* Next.js Starter Template

#nextjs #chatgpt #openai #appsdk
👍5🔥1
Тайлер Виген — человек, который превратил статистический троллинг в искусство. 🖼

С 2014 года на его сайте Spurious Correlations опубликовано более 25 тысяч переменных (мемы, профессии, google-запросы, акции), которые он сравнивает между собой абсолютно все со всем. Получается 636 миллионов комбинаций. И в этом хаосе обязательно найдутся идеальные совпадения и корреляции.

Например, потребление маргарина в США коррелирует с уровнем разводов в штате Мэн с коэффициентом 0.99 — почти функциональная зависимость. Популярность мема "distracted boyfriend" коррелирует с количеством офисных клерков в Кентукки. Расстояние между Нептуном и Меркурием — с рейтингами ТВ-шоу.

Звучит как бред? Именно в этом и смысл.

📊 Что такое data dredging

Data dredging (он же p-hacking, он же data fishing) — это когда вы прогоняете тысячи статистических тестов на одном датасете и публикуете только те, что дают "статистически значимый" результат.

Виген честно пишет на сайте: "Я швыряю данные в блендер и смотрю, какие корреляции вывалятся. Это опасный способ анализа, потому что любой достаточно большой датасет даст вам сильные корреляции полностью случайно".

Понятно, что все совпадения случайны и математически некорректны, но выглядит забавно, когда просто смотришь на графики. Отличный пример для всех презентаций, которые используют графики как доказательство чего-то.
👍5