Steven
Steven6 мин. чтения

Одна CSS-переменная, пять раундов ревью и Swift-тулчейн, который солгал

GeekBye v2.0.7 сделал весь оверлей регулируемо полупрозрачным — звучит как однострочное изменение в CSS и совершенно им не является. Настоящая история — в том, что поймало код-ревью: две поверхности, которые отказывались растворяться, панель записи, которая при той же прозрачности выглядела не так, и скомпилированный бинарник, который туда-сюда менял 672 байта, потому что наша же документация назвала ревьюеру не ту версию Swift.

Инженерия
Дизайн
Сборка
Релизы GeekBye
Одна CSS-переменная, пять раундов ревью и Swift-тулчейн, который солгал

«Сделай оверлей полупрозрачным» — это задача из тех, что звучат как ползунок и CSS-свойство opacity. GeekBye v2.0.7 выкатил ровно этот пользовательский результат — единый регулируемый «светлый режим», который позволяет видеть сквозь панели ассистента то, что находится за ними. Но интересная часть этого релиза — не сама фича. Это пять раундов ревью, которые потребовались, чтобы всё сделать правильно, и военная история про скомпилированный бинарник, который сменил размер, потому что наша собственная документация солгала ревьюеру.

Правильный способ сделать полупрозрачным весь интерфейс

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

Вместо этого v2.0.7 использует одну общую CSS-утилиту и токен для каждой поверхности. Есть единственный класс overlay-surface, который вычисляет фон панели из двух custom properties: базовой прозрачности, которую каждая поверхность объявляет для себя (панель управления, чат, пузыри транскрипта, блоки кода и так далее — каждая выбирает свою), и глобального множителя альфы, который живёт на корне оверлея и представляет настройку ползунка у пользователя. Каждая поверхность читает один и тот же множитель; каждая сохраняет свой характер. Что критично — масштабируется только фон панели, а текст, иконки и backdrop-blur остаются полностью отрисованными, так что «полупрозрачный» никогда не значит «нечитаемый».

Два решения в этом дизайне стоит отметить, потому что именно они — разница между фичей, которая помогает, и той, что плодит тикеты в поддержку:

  • По умолчанию всё полностью непрозрачно. Настройка прозрачности поставляется на 100% — это no-op. Ни у кого оверлей не стал вдруг просвечивать после обновления; прозрачность есть, когда ты за ней тянешься, и невидима, пока ты этого не сделаешь.
  • Настройка валидируется защитно. Есть один источник истины для минимума, максимума, значения по умолчанию и шага, и нормализатор, который прикрывает пустой/отсутствующий случай. Это важно из-за одной ловушки JavaScript: Number(null) равен 0, поэтому отсутствующая настройка при наивном приведении защёлкнула бы оверлей в самое прозрачное состояние вместо значения по умолчанию. Нормализатор ловит это до того, как оно случится.

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

Что поймало ревью (и никогда не поймал бы тест на одном мониторе)

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

Панель, которая не растворялась. Оверлей горячих клавиш упрямо оставался плотным, пока всё остальное становилось полупрозрачным. Причина: он рендерится вне поддерева DOM, несущего переменную прозрачности, поэтому он так и не унаследовал значение, каскадом стекающее вниз от корня оверлея. CSS custom properties наследуются через дерево DOM — и если узел на самом деле не является потомком того места, где ты задал переменную, он молча не получает ничего. Исправлением было задать переменную локально и на этой панели тоже.

Hover-подсветка, которая исчезла. Кнопки Fn/Assist потеряли своё голубое hover-свечение. Утилита прозрачности задавала фон с !important (иначе она не победит дефолты поверхности), и это закатало под асфальт hover:-подсветку фона на том же элементе. Коллизия специфичности. Исправлением было сделать hover-подсветку столь же настойчивой, чтобы она могла отвоевать своё при наведении.

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

Одинаковая прозрачность, разный вид. Самый тонкий: панель записи и панель простоя были выставлены на идентичную базовую прозрачность и всё равно выглядели заметно по-разному. Почему? У пилюли записи было более тяжёлое backdrop-blur, а сильное размытие поверх тёмного фона усредняется в сторону более тёмного результата — так что равная альфа дала неравный вид. Ещё у неё была слабая рамка там, где у панели простоя была яркая кромка градиентного кольца. Исправление подогнало уровень размытия и кольцо. Урок засел прочно: равная альфа — не равный вид, как только в дело вступает размытие. Прозрачность — это система взаимодействующих эффектов, а не одно число.

Бинарник, который сменил размер, потому что документация солгала

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

GeekBye поставляет несколько предварительно скомпилированных Swift-бинарников (нативные помощники для таких вещей, как определение встреч). Поскольку скомпилированный бинарник закоммичен в репозиторий, каждый разработчик должен компилировать его точно той же версией Swift — разные тулчейны производят разные байты из идентичного исходника, а это означало бы фантомный git-конфликт каждый раз, когда кто-то пересобирает. Поэтому есть скрипт, который энфорсит одну конкретную версию: Swift 6.3.x, проверяемую на этапе сборки.

Во время ревью этого релиза ревьюер отметил, что бинарник надо пересобрать, чтобы он соответствовал «тулчейну, предписанному репозиторием». Разработчик сделал ровно это — и размер бинарника изменился с 89,184 байт до 88,512. Сообщение коммита даже гласило, что это «восстановление байт-стабильного вывода» через пересборку с Swift 6.2.1, «тулчейном, предписанным репозиторием».

Вот только 6.2.1 была не той версией. Скрипт-энфорсер сборки требовал 6.3.x. Проблема была в том, что прозаическая документация — README и гайд для контрибьюторов — всё ещё говорила 6.2.1 в паре мест. Так что добросовестный ревьюер и добросовестный разработчик, оба пытаясь поступить правильно, следовали документации прямиком к тому, чтобы закоммитить ровно тот артефакт, ради отклонения которого и существует скрипт-энфорсер. Бинарник 6.2.1 в репозитории на 6.3.x — это именно тот дрейф, который сломал бы чистую пересборку у всех остальных разработчиков.

Следующий коммит это исправил — и исправление и есть суть. Он не просто пересобрал бинарник обратно в его форму 6.3.x (размер восстановлен до 89,184, ровно туда, откуда всё начиналось). Он ещё и исправил устаревшие упоминания «6.2.1» в документации, чтобы ловушку нельзя было захлопнуть снова. Бинарник туда-сюда сменил 672 байта за два коммита, а настоящий баг никогда не был в бинарнике. Он был в том, что два источника истины — скрипт-энфорсер и написанная документация — расходились, а человек доверился не тому.

Три вещи, которым научил этот релиз

  1. Каскад CSS-переменной — это граница корректности. Два из трёх багов ревью свелись к переменной, которая не унаследовалась, и !important, который затоптал hover-состояние. Если значению приходится доходить до элемента через наследование или побеждать через специфичность, этот путь — логика; проверяй его как логику, а не как стилизацию.
  2. Равные параметры могут давать неравные результаты. Панель записи и панель простоя делили одну прозрачность и всё равно выглядели по-разному, потому что другое размытие изменило исход. Когда унифицируешь визуальную систему на одном числе, проверь, что другие переменные вокруг каждой поверхности тихо не меняют, что это число значит.
  3. Когда документация и энфорсер расходятся, чини расхождение — а не только симптом. Баг был не в плохом бинарнике; он был в документе, который назвал не тот тулчейн, и скрипте-энфорсере, который говорил иначе. Пересборка бинарника лечит симптом. Привести все источники истины к согласию — вот настоящее исправление, потому что следующий ревьюер тоже доверится документации.

GeekBye v2.0.7 выкатил единообразную прозрачность, все крайние случаи отревьюены, а документация по тулчейну выправлена. Это последний из релизов «тихого ремесла» перед реляйабилити-спринтом, что последовал за ним. О фундаменте под ним читай чего на самом деле стоит вторая версия (v2.0.0); о родственном полировочном релизе — спокойный софт: фикс мерцания и чип режима ответа (v2.0.3 + v2.0.5); а о том, как оверлей остаётся скрытым во время звонка — как оставаться невидимым при демонстрации экрана.

Похожие статьи

Перепроектирование дашборда вокруг одного волнового графика
Steven
Steven7 мин. чтения

Перепроектирование дашборда вокруг одного волнового графика

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

Инженерия
Дизайн
Дашборд
Страница логина и ЕСТЬ демо
Steven
Steven6 мин. чтения

Страница логина и ЕСТЬ демо

GeekBye v1.7.5 добавил скриншот продукта на страницу логина и удалил его в тот же день — пересобрав вместо него демо из настоящих компонентов. Плюс: пользователь, который оказался быстрее OCR, и как на самом деле выглядят 25 стилевых коммитов за один день.

Инженерия
Дизайн
UX
Одна кодовая база, два приложения: как сделать white-label без форка
Steven
Steven5 мин. чтения

Одна кодовая база, два приложения: как сделать white-label без форка

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

Инженерия
Архитектура
Сборка