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
Steven6 мин. чтения

Спокойный софт: как мы убили мигающий переключатель и научили чат сообщать, в каком он режиме

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

Продукт
Дизайн
Инженерия
Анатомия доведения ПО до совершенства: как код-ревью поймало то, что не смогли тесты
Steven
Steven6 мин. чтения

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

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

Инженерия
Процесс
Код-ревью
Ваше Mac-приложение получает доступ к микрофону — и забывает его при каждом запуске
Steven
Steven5 мин. чтения

Ваше Mac-приложение получает доступ к микрофону — и забывает его при каждом запуске

GeekBye запросил доступ к микрофону, вы его выдали, и всё заработало. Следующий запуск: доступа нет. А в Системных настройках → Микрофон приложение вообще не появилось. Виновником оказалась функция безопасности macOS, тихо запускающая приложение из пути, который исчезает — вот диагноз и исправление в одно нажатие.

macOS
Разрешения
Инженерия