Skip to content

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

v3 0 stars 0 forks 0 watchers 1 branch 0 runs Public
Перепроверка ссылок по методу: хранилище переезжало трижды (localStorage → cookie → localStorage), color_scheme_override появился в PR #15 Пенара 02.12.2022, а не у Джордана в 2020-м. Добавлен кадр про этот PRv3

Discourse, июль 2020 — февраль 2026.

Discourse — движок форумов, на котором стоят сообщества Rust, Docker, Figma и тысячи других. Тёмная тема у него есть с 2020 года. Кнопка «переключить» — тоже.

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

30 июля 2020 — Пенар делает автоматику.

Penar Musaraj, инженер Discourse, присылает PR #10341 «FEATURE: automatic dark mode», 1583 строки:

When the browser/OS is in dark mode using (prefers-color-scheme: dark), the dark color scheme will apply.

Когда браузер или ОС в тёмном режиме — по (prefers-color-scheme: dark) — применяется тёмная цветовая схема.

Настройка живёт у операционной системы. Сайт её только слушает. Просто и честно: никакого своего хранилища — нечему сбрасываться.

Через три недели, 27 августа, он же добавляет выбор схемы в профиле — и с первого дня ставит вопрос о месте хранения:

allows the regular/dark scheme preference to be stored in a cookie or in the user_options table (similar to theme/text size choices)

позволяет хранить выбор обычной/тёмной схемы в cookie или в таблице user_options (как выбор темы и размера текста)

Cookie для анонимных, таблица для вошедших. Правильный ответ дан в 2020-м. Дальше история о том, как его на четыре года потеряли.

1 сентября 2020 — Джордан делает кнопку.

Дизайнер Jordan Vidrine заводит отдельный репозиторий Init Commit — компонент темы: кнопку в меню, которая переключает тёмную и светлую на месте, без перезагрузки. В тот же день кнопка уже работает и запоминает выбор:

localStorage.setItem('userToggledScheme', 'dark')

(строки 17–29)

Компонент темы — это код, который живёт в браузере. Сервер о нажатии не узнаёт, аккаунт тоже.

Через три недели, 21 сентября, Джордан меняет хранилище: Refactor + Add cookie functionality, ключ userSelectedScheme. Cookie сервер прочитать может — но в 2020-м её на сервере не читает никто: строка forced_color_mode появится в ядре только через четыре с половиной года.

Кнопку ставят себе тысячи форумов. Она работает.

2 декабря 2022 — настройка возвращается в браузер.

Пенар присылает в компонент PR #15 «REFACTOR: Use single-button toggle». Три пункта из списка в описании:

  • uses only one icon
  • switches to using local storage instead of cookies
  • resets saved override when OS dark mode matches color scheme, so for example if OS is in dark mode and the toggle switches the site to dark as well, the override is reset (essentially reverting back to auto)

— одна иконка вместо нескольких; — переходим с cookie на localStorage; — сохранённый выбор сбрасывается, когда системный тёмный режим совпадает с цветовой схемой: если ОС тёмная и переключатель тоже сделал сайт тёмным, выбор сбрасывается (то есть возвращается «авто»).

Cookie заменена на localStorage, COLOR_SCHEME_OVERRIDE_KEY = "color_scheme_override". И сброс — не побочный эффект, а объявленная возможность: настройка стирается намеренно, чтобы «авто» не расходилось с системой.

27 декабря 2022 — Пенар описывает трещину.

Через двадцать пять дней, в теме поддержки компонента, Пенар отвечает на вопрос и, между делом, фиксирует то же поведение — уже как неудобство:

now the dark/light mode toggler respects the OS dark mode setting. If a user toggles their OS appearance, this component's setting is reset.

теперь переключатель уважает системную настройку. Если пользователь меняет оформление ОС, настройка компонента сбрасывается.

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

13 февраля 2023 — приходят пользователи.

Через два месяца после того рефакторинга приходят те, ради кого кнопка ставилась. jrgong, администратор форума:

Our users reported that after switching to dark mode manually, the forum switches back to light mode automatically once the device goes back to light mode. […] Expected behavior is that forum stays in dark mode due to users choice of manually switching

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

В тот же день — Kris (awesomerobot), дизайнер Discourse, и это ключевой кадр всей истории:

I guess the colors are switched on the client side, but it looks like user settings are left untouched? Is it possible to maybe update the user "color scheme" preference on toggle to avoid this? The client-side switch can provide the seamless transition, and changing the user preference can avoid the brief flash of the opposing color scheme on refresh?

Похоже, цвета переключаются на клиенте, а настройки пользователя не трогаются? Нельзя ли при нажатии обновлять и «цветовую схему» в профиле? Клиент даёт плавный переход, а запись в профиль уберёт вспышку противоположной схемы при перезагрузке.

Крис не просит починить симптом. Он называет причину — настройка живёт не там — и описывает решение целиком: клиент для мгновенности, сервер для памяти. Оно и будет реализовано. Через два года.

20 сентября 2023 — Сара задаёт вопрос из одной строки.

Sarah Jane Obligar:

Hi, can I make the dark mode the default color? Because, whenever I log in it will switch back to light mode.

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

Ей отвечает Jakke Flemming (Jagster), администратор из сообщества: посмотрите в настройках профиля. Совет верный — и он же доказывает разрыв: кнопка и профиль не знают друг о друге, и человек должен выставить одно и то же в двух местах.

Год спустя, 11 декабря 2024, тот же Jagster на тот же вопрос от следующего человека:

It is always good policy to respect user's choises

Уважать выбор пользователя — всегда правильная политика.

За четыре года в теме — 143 сообщения. Половина из них про одно и то же.

7 февраля 2025 — Осама переносит настройку на сервер.

Osama Sayegh, инженер Discourse, вливает PR #31086 «FEATURE: Dark/light mode selector» — 30 файлов, +1065/−111:

This PR makes the color-scheme-toggle theme component a core feature with improvements and bug fixes. […] the color mode selector has a new "Auto" option […] the splash screen respects the color mode selected by the user

Этот PR делает компонент переключателя частью ядра, с улучшениями и исправлениями. […] у переключателя появляется вариант «Авто» […] заставка уважает выбранный режим

Главное изменение не в списке, а в устройстве: localStorage заменён на cookie forced_color_mode. Cookie читает сервер. Значит, страницу можно сразу нарисовать нужного цвета — и вспышка, о которой Крис писал два года назад, исчезает. В тот же день компонент Джордана объявлен устаревшим:

This theme component has been folded into Discourse core

Компонент влит в ядро Discourse

Настройка переехала из браузера на границу с сервером. Но не в аккаунт.

31 июля 2025 — и наконец в аккаунт.

Ещё через полгода Осама присылает PR #33875 «FEATURE: Color mode preference»:

it also allows setting the default mode for the user that gets used when the user logs in on a new device. It remains possible to set a color mode per device

ещё это позволяет задать режим по умолчанию для пользователя — он применится при входе с нового устройства. Задать режим для отдельного устройства по-прежнему можно

user_options.interface_color_mode. Вопрос Сары из одной строки закрыт: тёмная по умолчанию, на любом устройстве, при любом входе. Вопрос Криса — тоже: переключатель пишет и на клиент, и в профиль.

И развилка, которую здесь решили явно: настройка аккаунта и настройка устройства — разные вещи. Обе нужны. Аккаунт — умолчание; устройство — уточнение. Cookie сильнее таблицы, потому что человек мог намеренно сделать телефон тёмным, а компьютер светлым.

Хвост.

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

12 августа 2025Krzysztof Kotlarek убирает отдельную галочку тёмного режима из профиля: два органа управления одним и тем же — лишний.

10 октября 2025Kris (awesomerobot), семь строк:

Currently this color mode switcher has no accessible label or title, this adds both!

Сейчас у переключателя нет ни доступной подписи, ни title — добавляю обе!

9 января 2026Régis Hanol (ZogStriP), сооснователь Discourse, находит, что серверные страницы — 404, сброс пароля, приглашения — всё ещё берут логотип по системной теме:

the <source> media query was hardcoded to (prefers-color-scheme: dark), ignoring the forced_color_mode cookie.

media-запрос в <source> был зашит как (prefers-color-scheme: dark) и не учитывал cookie forced_color_mode.

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

Здесь нет виноватых, и это важнее всего.

Пенар в 2020-м дал верный ответ про хранение — для того, что тогда было: выбор схемы в профиле. Джордан в 2020-м пробовал обе двери: localStorage, через три недели cookie. Разницы не вышло — cookie сервер прочитать может, но читать её было некому: компонент темы не заставит ядро рисовать страницу иначе. Пенар в 2022-м вернул localStorage и в том же PR объявил сброс возможностью, а через двадцать пять дней сам записал, как это выглядит со стороны человека. Крис в 2023-м увидел причину, но перенос компонента в ядро — это +1065 строк и 30 файлов, ради кнопки, которая «и так работает». Осама сделал это, когда сошлись «Авто», заставка, логотипы категорий — когда переезд стал оправдан не одной жалобой, а десятком.

А 143 сообщения в теме — это люди, которые по одному обнаруживали, что их выбор не запомнили, и каждому по одному объясняли, где лежит вторая настройка.

Что это значит для нас

1
Хранить настройку человека у человека

Выписать всё, что интерфейс запоминает про пользователя (тема, шрифт, язык, свёрнутые панели), и по каждому ответить: это про человека или про устройство? Первое — в аккаунт, второе — в cookie или localStorage.

Why: У нас оформление лежало в четырёх ключах localStorage. На айфоне у одного сайта три хранилища — Safari, ярлык на экране и встроенный браузер Telegram. Человек ставит тёмную в одном, приходит из другого — «сбросилась». Ровно жалоба Сары из 2023-го.
Check
  • Настройки человека читаются из аккаунта на любом устройстве
  • Настройки устройства (свёрнутая панель) не тянутся в аккаунт
2
Сделать cookie копией для сервера, а не хранилищем

Если страницу рисует сервер, ему нужно знать тему до первого байта. Cookie для этого; пишет её только сервер, читается она, когда аккаунта нет.

Why: Вспышка противоположной темы при перезагрузке — то, о чём Крис писал в 2023-м. Она лечится не скриптом, а тем, что сервер знает ответ заранее. Discourse пришёл к cookie `forced_color_mode` в #31086.
Check
  • `<html>` с сервера приходит уже нужного цвета, без перекраски скриптом
  • Инлайн-скрипт остался только для режима «как в системе»
3
Решить явно: аккаунт против устройстваRecommended

Записать в одном месте, кто сильнее, когда аккаунт и устройство спорят. У Discourse сильнее устройство; у нас — аккаунт, и это осознанный выбор для портала, где у каждого один телефон и один компьютер.

Why: Два хозяина у одной настройки без явного приоритета — это трещина, которую Пенар описал в 2022-м: «если пользователь меняет оформление ОС, настройка сбрасывается».
Check
  • Приоритет записан в коде одной строкой и в комментарии — почему так
  • Изменить его — правка одной строки, а не переезд хранилища
4
Перенести старые ключи, а не бросить

При первом заходе после выката прочитать прежние ключи localStorage, записать в аккаунт, удалить. Компонент переноса убрать через пару месяцев.

Why: Иначе выкат сам станет «всё сбросилось» во второй раз. Правка ZogStriP’а в 2026-м — напоминание, что прежний ответ доживает в углах и после четырёх переездов.
Check
  • У человека с выставленной раньше темой после выката она та же
  • Старых ключей в localStorage не остаётся
5
Подписать кнопки, у которых только иконкаRecommended

Пройти по проекту: элементы без текста внутри — с aria-label и title. Дешевле всего сделать подпись обязательной по типу в общем компоненте.

Why: Семь строк Криса в 2025-м. У нас такая же стрелка на карточках сводки была без имени.
Check
  • Кнопка-иконка без подписи не проходит проверку типов
  • Читалка экрана объявляет назначение, а не «кнопка»

Кнопка переключения темы — самая простая вещь в интерфейсе. Нажал — стало тёмным.

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

Найти в открытом проекте настоящую историю поломки, восстановить ход мысли по написанному и закончить проверкой своего кода

v10Public 0 0
updated Sep 1, 2026

Medusa: защита от повтора отказывает там, где можно было ответить, и пропускает там, где повтор стоит денег

v2Public 0 0
updated Sep 2, 2026

Medusa: кнопка «убрать скидку» выдавала максимальную, потому что ноль в JavaScript ложный

v2Public 0 0
updated Sep 2, 2026

Люди и проекты из разборов — по поступкам, датам и ссылкам. Как определитель птиц: не оценивает, а помогает узнать, кого встретил

v8Public 0 0
updated Sep 2, 2026

Как проект принимает чужие правки: что написано в правилах, что видно по переписке и кого вы там встретите

v2Public 0 0
updated Sep 2, 2026

server-only: четыре строки кода, одна версия за четыре года, и весь его смысл — падать. Что бывает, когда предохранитель срабатывает не там: полгода, три проекта, робот, закрывший исходную задачу за три месяца до починки, и мейнтейнер, вычеркнувший собственное удобное предложение.

v1Public 0 0
updated Sep 2, 2026