
Одна CSS-переменная, пять раундов ревью и Swift-тулчейн, который солгал
GeekBye v2.0.7 сделал весь оверлей регулируемо полупрозрачным — звучит как однострочное изменение в CSS и совершенно им не является. Настоящая история — в том, что поймало код-ревью: две поверхности, которые отказывались растворяться, панель записи, которая при той же прозрачности выглядела не так, и скомпилированный бинарник, который туда-сюда менял 672 байта, потому что наша же документация назвала ревьюеру не ту версию 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 байта за два коммита, а настоящий баг никогда не был в бинарнике. Он был в том, что два источника истины — скрипт-энфорсер и написанная документация — расходились, а человек доверился не тому.
Три вещи, которым научил этот релиз
- Каскад CSS-переменной — это граница корректности. Два из трёх багов ревью свелись к переменной, которая не унаследовалась, и
!important, который затоптал hover-состояние. Если значению приходится доходить до элемента через наследование или побеждать через специфичность, этот путь — логика; проверяй его как логику, а не как стилизацию. - Равные параметры могут давать неравные результаты. Панель записи и панель простоя делили одну прозрачность и всё равно выглядели по-разному, потому что другое размытие изменило исход. Когда унифицируешь визуальную систему на одном числе, проверь, что другие переменные вокруг каждой поверхности тихо не меняют, что это число значит.
- Когда документация и энфорсер расходятся, чини расхождение — а не только симптом. Баг был не в плохом бинарнике; он был в документе, который назвал не тот тулчейн, и скрипте-энфорсере, который говорил иначе. Пересборка бинарника лечит симптом. Привести все источники истины к согласию — вот настоящее исправление, потому что следующий ревьюер тоже доверится документации.
GeekBye v2.0.7 выкатил единообразную прозрачность, все крайние случаи отревьюены, а документация по тулчейну выправлена. Это последний из релизов «тихого ремесла» перед реляйабилити-спринтом, что последовал за ним. О фундаменте под ним читай чего на самом деле стоит вторая версия (v2.0.0); о родственном полировочном релизе — спокойный софт: фикс мерцания и чип режима ответа (v2.0.3 + v2.0.5); а о том, как оверлей остаётся скрытым во время звонка — как оставаться невидимым при демонстрации экрана.