Павел Варнавский, руководитель группы разработки «ДАР» (Корус Консалтинг), рассказал, как их команда использует BI Magic в своих проектах для создания мощных аналитических решений.
Как внедрять CI/CD для дэшбордов и масштабировать решения под конкретные процессы, там, где стандартных «коробочных» решений не хватает
Два практических кейса, где кастомная разработка на Luxms BI решила нетипичные задачи
Будет интересно всем, кто работает с нестандартной аналитикой, сложными требованиями бизнеса и хочет понимать, как кастомная BI-разработка может быть управляемой и удобной
Представлен открытый веб-редактор изображений DPaint.js (онлайн-версия) на JavaScript, созданный по образцу легендарного Deluxe Paint, с упором на ретро-форматы файлов Amiga. Помимо современных форматов изображений, DPaint.js может читать и записывать файлы иконок Amiga и изображения IFF ILBM.
Основные возможности проекта: слои, выделение, маскирование, инструменты трансформации, эффекты и фильтры, множественная отмена/повтор действий, копирование/вставка из любой другой программы обработки изображений или источника изображений, настраиваемые инструменты дизеринга и циклическая смена цветов.
Коллеги привет, искал себе решение как реагировать на изменения в объекте и нашел отличный сервис, который используется внутри директив таких как NgClass и NgStyle.
KeyValueDiffers позволяет создать KeyValueDiffer для сравнения изменений текущих пар ключ-значение с новыми. Если вы используете иммутабельные объекты, то можно просто обернуть все в эффект, ну а если вы наследники крутого легаси, где все объекты мутируются по ссылке, тогда проверку нужно вешать в DoCheck, чтобы реагировать на каждый тик change detection.
Накидал оба примера, чтобы поделиться с вами:
Иммутабельный с effect:
@Component({
selector: 'app-test',
template: ''
})
export class TestComponent {
public state = input.required<Record<string, string | number>>();
private differs = inject(KeyValueDiffers);
private differ: KeyValueDiffer<string, string | number> | undefined;
constructor() {
effect(() => {
const currentState = this.state();
// создаем диффер, если он еще не создан
if (!this.differ) {
this.differ = this.differs.find(currentState).create();
}
// Эффект будет перезапускаться при изменении инпут-сигнала.
const changes = this.differ.diff(currentState);
// только если есть изменения
if (changes) {
changes.forEachAddedItem((record) => {
console.log(`В объект добавлена запись: Ключ: ${record.key} | Значение: ${record.currentValue}`)
});
changes.forEachChangedItem((record) => {
console.log(`Изменено: ${record.key} | Новое значение: ${record.currentValue}`)
});
changes.forEachRemovedItem((record) => {
console.log(`Удалено: ${record.key}`)
});
// Остальные методы forEachItem и forEachPreviousItem по необходимости
}
})
}
}
Легаси подход, которого, надеюсь, ни у кого нет, но на всякий случай :)
@Component({
selector: 'app-legacy',
template: ''
})
export class LegacyComponent implements OnInit, DoCheck {
@Input({ required: true }) state!: Record<string, string | number>;
private differs = inject(KeyValueDiffers);
private differ: KeyValueDiffer<string, string | number> | undefined;
ngOnInit() {
// Создаем диффер при инициализации
this.differ = this.differs.find(this.state).create();
}
// Запускается на каждый тик change detection, так как мутации по-другому не отследим.
ngDoCheck(): void {
const changes = this.differ?.diff(this.state);
if (changes) {
changes.forEachAddedItem((record) => {
console.log(`В объект добавлена запись: Ключ: ${record.key} | Значение: ${record.currentValue}`)
});
changes.forEachChangedItem((record) => {
console.log(`Значение изменилось: ${record.key}`)
});
changes.forEachRemovedItem((record) => {
console.log(`Запись удалена: ${record.key}`)
});
// Остальные методы forEachItem и forEachPreviousItem по необходимости
}
}
}
Почему у PWA до сих пор нет полноценного «магазина приложений» — возможно ли это вообще?
Всем привет.
В течение последних месяцев, работая с PWA-приложениями, мы постоянно сталкивались с одним и тем же вопросом:
Почему в 2025 году у PWA до сих пор нет настоящего App Store?
Не просто каталога ссылок, а полноценного магазина приложений — знакомого, вызывающего доверие и понятного обычным пользователям.
При изучении существующих PWA-магазинов и каталогов обнаруживаются одни и те же повторяющиеся проблемы.
⸻
Установка остаётся непонятной для пользователей
Даже сегодня установка PWA вызывает затруднения у обычных пользователей.
Большинство из них не понимают: • когда приложение действительно можно установить, • почему инструкции по установке не совпадают с реальными шагами в их браузере или на устройстве.
Во многих PWA-каталогах всё ограничивается текстовой инструкцией — и на этом взаимодействие с сервисом фактически заканчивается.
⸻
Отсутствие доверия
Со стороны пользователя это проявляется в следующем: • нет содержательных отзывов, • отсутствует история установок, • нет ощущения личной библиотеки приложений.
Со стороны разработчиков наблюдаются крайности: • либо любой может опубликовать приложение без подтверждения права собственности, • либо проверка обязательна, но сложна и ограничена одним способом (например, через DNS-записи).
В итоге доверие не формируется ни у одной из сторон.
⸻
Разработчики — второстепенные участники экосистемы
Распространённые проблемы: • медленные и неудобные процессы публикации, • почти полное отсутствие автоматического заполнения данных из манифеста, • нехватка инструментов, которые были бы полезны разработчику ещё до установки приложения пользователем.
Экосистема не стимулирует разработчиков поддерживать и развивать свои PWA.
⸻
Интерфейс не воспринимается как «нативный»
Это тонкий, но важный момент.
Если магазин: • выглядит как обычный веб-сайт, • не вызывает ассоциаций с App Store или Google Play,
пользователи инстинктивно доверяют ему меньше — даже если сами приложения качественные.
⸻
При этом сами PWA как технология за последние годы заметно повзрослели: офлайн-режим, push-уведомления, installability, Web APIs. Однако именно слой распространения и доверия остаётся самым слабым звеном.
⸻
Главный вопрос, к которому мы пришли
Возможно ли вообще создать PWA-магазин, который: • пользователи будут воспринимать как настоящий магазин приложений, • не станет источником боли для разработчиков, • сможет устойчиво развиваться, а не быть заброшенным через несколько месяцев?
Или же сама идея магазина PWA в текущей экосистеме изначально ошибочна?
Будет интересно узнать ваш опыт.
Вы публиковали PWA-приложения в существующих магазинах или каталогах? Что вызывало наибольшие сложности — у разработчиков или у пользователей?
Открытый скрипт для браузера IKEA 3D Model Downloader добавляет на сайте IKEA кнопку «Download 3D» на страницы товаров. Проект позволяет скачать файл с точной 3D-моделью дивана, стола или шкафа. Очень удобно для планирования ремонта. Можно закинуть мебель в 3D-планировщик квартиры, посмотреть, как она встанет по размерам, прикинуть цвета и сочетания, а не покупать вслепую «по картинке» Работает на всех версиях сайта IKEA и с новой версией тоже. По сути — примерка мебели у себя дома, только в цифре.
-Сказать дорого, ничего не сказать. После всех оптимизаций какие толкьо можно было выкрутить с помощью перплексити от 1 до 16м токенов в запрос? Да ну на х… -Молниеносное отупение. Вместо того чтобы понимать какой компонент нужно доделать руками или через чат, тупо жми кнопку все будет ок. Только плати.
Вообщем вывод:
если у тебя большой проект: плати и тупи;
если у тебя сайтик, то повезло. Радуйся и не думай.
А лично я , кроме удобного интерфейса , не увидел ни чего хоть на 1% нужного. Знаю тут ни кто не поддержит) ну и ладно)))
Функциональное программирование перевернуло фронтенд: почему JS возвращается к платформам?
Функциональное программирование перевернуло фронтенд-разработку, но теперь индустрия возвращается к платформенным подходам — почему и как это меняет JS-экосистему?
Виктория Копылова делится своим анализом, основанным на современных наблюдениях и на тех статьях прошлого, где функциональное программирование воспринималось как путь к «правильному» фронтенду.
С новым рабочим годом, Хабр Мы в SSP SOFT опять расширяем команду и ждем ваши резюме
Кто мы? Занимаемся заказной разработкой ПО и предоставляем крупным клиентам выделенные команды на ИТ-аутсорсинг.
В 2025 году SSP SOFT вошел в число лидеров найма ИТ-специалистов на российском рынке— за год мы наняли 179 сотрудников! И главное — в 2026 году найм продолжается.
У нас новый московский офис в 2025 году у самой Красной площади! А еще есть вакансии в офис в Томске и на удаленку из любой точки России.
Открытые вакансии в SSP SOFT: это реальные проекты, дружная команда и атмосфера, где работать — продуктивно, без выноса мозга и микро-менеджмента. В январе 2026 ищем гуру, кто готов в новое профессиональное будущее вместе с нами.
Что вас ждет в SSP SOFT: ✅ Рост: Центр компетенций для максимального апгрейда скиллов. ✅ Свобода геолокации: Возможность работать удаленно, гибрид или офис. ✅ Баланс: Работаем, чтобы жить, а не наоборот.
🎁 Приятные бонусы: ивенты для всей команды, ДМС для штата, обучение и бенефиты.
Подробности о вакансиях читайте на нашей странице ХХ.ру, но туда откликаться необязательно. Ждем резюме в ЛС нашему HR Lead Алине (https://t.me/AONikitina). Не забудьте добавить «секретную фразу» в сопроводительное письмо, «Увидел(а) вакансию на Хабре».
Решил сегодня почитать, что пишут в Ангуляр комьюнити Хабра, и увидел сильно популярный пост с аж 51 лайком и 71 закладкой.
Начал читать и был удивлен примерами. Автор с уверенностью говорит, как писать на Ангуляр грамотно, и при этом приводит плохие практики в качестве примеров. Я дошел до примера с RxJS, который меня немного триггернул.
Не буду разбирать все кейсы указанные в данном посте, покажу лишь самый плохой пример который меня немного тригернул:
Автор условно говорит, что у нас есть плохой пример использования:
this.http.get('/api/data').subscribe((data) =>; {
this.data = data; // Что если запрос не вернётся?
});
и затем приводит хороший пример с сигналами и RxJs:
readonly data = signal([]);
readonly error = signal(null);
loadData() {
this.http.get('/api/data').pipe(
tap(() =>; this.error.set(null)), // Сбрасываем предыдущую ошибку перед загрузкой
catchError((err) =>; {
this.error.set('Не удалось загрузить данные');
return of([]); // Возвращаем пустой массив, чтобы поток не прерывался
})
).subscribe((result) =>; {
this.data.set(result);
});
}
я даже не буду указывать на количество антипатернов и плохих практик в данном примере, я просто покажу правильный пример с сигналами и RxJs:
Почему стоит использовать protected в Angular компонентах?
Если вы используете в своих компонентах только public и private, вы упускаете возможность сделать архитектуру чище. Я предлагаю четко разделять ответственность членов класса.
Часто мы по инерции делаем public любые методы и свойства, которые нужны в шаблоне (HTML). Но public в TypeScript означает, что это публичный API компонента - к этим методам может получить доступ любой родительский компонент через @ViewChild.
Почему стоит использовать protected:
1. Явное намерение: protected сигнализирует, что метод предназначен для использования внутри класса или в его шаблоне, но не должен вызываться извне.
2. Защита от регрессии: Если другой разработчик попытается вызвать такой метод через @ViewChild, TypeScript выдаст ошибку. Это заставит его задуматься: «Действительно ли мне нужно делать этот метод публичным?» или «Может, стоит создать отдельный метод для API?».
3. Читаемость: Открывая код, вы сразу видите: public - для внешнего мира, protected - для шаблона, private - для внутренней логики сервисов и подписок.
Разделяйте Public API и внутреннюю логику шаблона - ваш код станет надежнее и понятнее.
@Component({
selector: 'app-user-profile',
template: `
<!-- В шаблоне мы без проблем обращаемся к protected свойствам -->
<div class="card">
<h3>{{ userName() }}</h3>
<button (click)="onUpdateClick()">Обновить</button>
@if(isLoading()) {
<div>Загрузка...</div>
}
</div>
`
})
export class UserProfileComponent {
// PRIVATE: Внутренняя логика.
// Не доступно ни в шаблоне, ни родительскому компоненту.
private _userId = 123;
// PROTECTED: Доступно только внутри класса и в ШАБЛОНЕ.
// Идеально для переменных состояния UI и обработчиков событий.
protected userName = signal('Алексей');
protected isLoading = signal(false);
protected onUpdateClick(): void {
this.logAction();
console.log('Кнопку нажали в шаблоне');
}
// PUBLIC: Публичный API компонента.
// Только эти методы мы разрешаем вызывать родительскому компоненту.
public resetState(): void {
this.userName.set('Гость');
this.isLoading.set(false);
}
private logAction(): void {
console.log(`Action logged for userId: ${this._userId}`);
}
}
Что это: Пространство для авторов и читателей, упор сделан на книги с высоким уровнем визуала (графические романы, комиксы, манги, книги с упором на иллюстрации).
Преимущества: Три настраиваемых режима просмотра книг, поиск и фильтры по произведениям, лайки, избранные, страницы авторов и всё в этом духе.
На текущий момент это MVP - буквально базовая версия продукта, есть планы по его доработке и даже (о, ужас) "дорожная карта", которую, может быть, я реализую )))
_____
Должен упомянуть, что автор идеи и базового дизайна, которые я впоследствии доработал - Семён Диваченко.
Буду рад обратной связи тут в комментариях. Если найдёте баг или ошибку (а это на текущей стадии несложно), в меню есть кнопка "Ошибка?" специально для неравнодушных пользователей.
Lex Kravetski в ФБ написал оду Микрософт Ворду, с которым у меня плохие отношения еще с конца 1980-х, когда он был под DOS-ом в графическом режиме. Причем тогда Ворд был еще более-менее, хотя бОльшая часть его функциональности лично мне не была нужна, для форматированных текстов хватало Лексикона от Eugene Veselov из ВЦ Академии Наук, затем уехавшим в Микрософт и ныне ставшим очень политизированным.
Главные проблемы с Вордом для меня начались после 2000 года, когда у него стало прыгать форматирование невпопад, особенно в текстах с комбинацией списков, таблиц и картинок. Lex такую проблему упоминает с позиции своих оппонентов: "блин, даже пробел в нём как-то странно работает, по коей причине даже простое форматирование сделать тяжело."
Из-за этого прыгания я сейчас для редактирования форматированных текстов как правило использую простой текстовый редактор joe (который имитирует редактор в TurboC 1988 года, но с квадратными блоками), а в нем - .md Markdown, который потом конвертирую в .pdf с помощью программы pandoc.
Также использую Google Doc, в нем форматирование не прыгает, как в Microsoft Word и в Libre Office, а сделано по человечески.
Раньше еще писал в текстовом редакторе на HTML, но Markdown удобнее, так как читабильнее в голом виде. Если вы еще не выучили Markdown и мучаетесь Вордом - просто нагуглите его в википедии, он учится за 15 минут.
Как получить почти бесконечное зацикливание без использования циклов и без переполнения стека вызовов:
// Установите N = 64, и эта функция никогда не завершится
// Количество вызовов (calls) = 2^(N+1)
// Максимальная глубина вложенности = N
let calls = 0
const N = 18
function func(state, visited) {
calls++
if (calls > 10_000_000) {
throw new Error('calls: ' + calls)
}
if (visited.includes(state)) return
const newVisited = [...visited, state]
func((state + 1) % N, newVisited)
func((state + 1) % N, newVisited)
}
func(0, [])
console.log('calls:', calls)
Почему это работает без переполнения стека?
func(0, [])
├── func(1, [0])
│ ├── func(2, [0,1])
│ │ └── ... глубина растёт до N
│ │ и перебираются все возможные комбинации значений в newVisited
│ └── func(2, [0,1]) - возвращается, глубина УМЕНЬШАЕТСЯ
└── func(1, [0]) - второй вызов, стек уже освободился
А Garbage Collector (GC) при этом бесконечно удаляет созданные ранее массивы newVisited
Стек "дышит" - достигает максимума N, потом сворачивается, потом снова растёт. Это обход огромного дерева, имеющего небольшую глубину, но очень большую ширину. Это не бесконечная рекурсия. Но при N = 64 количество вызовов будет 2^65 (примерно 10^19) - это займёт тысячи лет, и стек никогда не переполнится.
За годы менторства по Angular (в том числе в HTML Academy) я заметил одну системную проблему: студенты и даже миддлы часто знают синтаксис RxJS, но не понимают реактивного мышления. В итоге мы получаем subscribe внутри subscribe и императивную лапшу.
Я искал интерактивные курсы, но большинство бесплатных ресурсов ограничиваются основами.
Курс бесплатный. Делал для себя и студентов, но теперь делюсь со всеми.
Буду рад фидбеку и баг-репортам (проект активно допиливаю).
Команда разработчиков Chrome собрала на одной странице все крупные изменения в CSS за 2025 год. У каждой новой фичи есть подробное описание, интерактивная демонстрация, примеры кода и информацию о поддержке в основных браузерах.
Друзья, классная новость! Мы с коллегами из GitVerse закончили разработку интеграции!
Теперь в Gramax можно подключить GitVerse в качестве хранилища. Работает в лучшем виде: клонирование, синхронизация, коммит, пуш — все как и должно быть ✨
Это была масштабная и интересная работа: мы вместе анализировали API, чтобы получилось максимально удобно. Потому будем очень рады увидеть ваши плюсики!
Gramax — Open Source-платформа для работы с документацией в подходе Docs as Code. GitVerse — AI-first платформа для работы с кодом.
Поделюсь самодельным расширением для VS Code, которое позволяет создавать React-компоненты в один клик.
Демонстрация работы расширения
Что умеет:
гибкое именование файлов: выбор между PascalCase, camelCase, kebab-case или snake_case для генерируемых файлов;
работа с .tsx и .jsx для файлов компонентов, а также .scss, .css, .less и .sass для стилей;
редактируемые шаблоны: настройка содержимого генерируемых файлов прямо в VS Code;
опциональное создание файлов реэкспорта и стилей.
Более подробный readme на странице расширения, ссылка на исходники там же. На мой взгляд экстешнен написан таким образом, что его довольно легко переписать для любого web-фреймворка.
На всякий случай: ни с какими внешними сервисами и нейронками расширение не взаимодействует)
Массовая замена HTML-разметки в Sublime Text с помощью RegExp
При работе с HTML часто возникает рутинная задача: есть десятки однотипных элементов, которые нужно заменить на реальную разметку. Делать это вручную - долго и рискованно, также можно легко допустить ошибку.
В моём случае это были заглушки вида:
<p>Image 1</p>
<p>Image 2</p>
<p>Image 3</p>
Нужно было превратить их в теги изображений, сохранив номера файлов изображений.
Почему не руками
Ручная правка подходит только для пары строк. Когда элементов больше 10–20, возрастает риск:
ошибиться в номере изображения
забыть закрыть тег
нарушить единообразие разметки
Поэтому логичнее использовать инструменты, которые уже есть в редакторе.
Решение через Sublime Text и RegExp
В Sublime Text есть мощный поиск и замена с поддержкой регулярных выражений. Этого более чем достаточно для задачи.
Открываем HTML-файл и вызываем панель замены:
Ctrl + H (Windows / Linux)
Cmd + Alt + F (macOS)
Обязательно включаем режим Regular Expression (иконка .*).
Шаблон поиска
<p>Image (\d+)</p>
Разберём выражение:
<p>Image — фиксированная часть
(\d+) — группа захвата, которая находит любое число
</p> — закрывающий тег
Число внутри скобок сохраняется как первая группа.
Шаблон замены
<img src="images/\1.jpg">
Здесь \1 — ссылка на первую группу захвата из шаблона поиска. На её место подставляется найденное число.
В результате:
Image 1 → images/1.jpg
Image 12 → images/12.jpg
Проверка и замена
Перед массовой заменой полезно нажать Find All, чтобы убедиться, что совпадения находятся только там, где нужно.
После этого можно смело использовать Replace All — Sublime Text заменит все подходящие строки за один шаг.
Подводные камни
Несколько моментов, о которых легко забыть:
если не включить RegExp, \1 подставлен не будет
в Sublime используется именно \1, а не $1
шаблон чувствителен к пробелам и регистру
Альтернативы
Для подобных задач подойдут и другие инструменты:
sed в терминале
массовая генерация HTML через bash-циклы
аналогичная замена в VS Code
Но если файл уже открыт в Sublime Text, RegExp — самый быстрый вариант.
Вывод
Регулярные выражения в Sublime Text позволяют эффективно автоматизировать рутинные правки HTML без дополнительных скриптов и плагинов. Даже простые приёмы вроде групп захвата экономят время и снижают количество ошибок.
Дополнительно
Более подробная пошаговая инструкция с ориентацией на новичков опубликована в моём блоге: