Решили: всё, чем Лаборатория не владеет, она читает у владельца на лету, а не хранит у себя: палитра приходит из Assets/_Project/UI/Theme/tokens.*.uss, карта документации — из docs/wiki. Сервер (scripts/lab_server.py) парсит их по запросу и отдаёт JSON; без сервера раздел честно пишет «нет данных».

Почему: копия расходится с оригиналом молча. У цвета это особенно зло — «примерно тот же оттенок» глазами не проверишь, и страница будет годами показывать палитру, которой в игре уже нет. Отвергли два варианта: захардкодить токены в разделе (быстро, но заводит второго владельца) и генерировать снимок скриптом в файл (тот же второй владелец, только с лишним шагом и датой протухания).

Та же логика провела границу с ГДД: сайт держит то, на что надо СМОТРЕТЬ, а текст остаётся в vault и публикуется Quartz’ом. В разделе #/gdd живут только метаданные — кластеры, заголовки, статусы, объём — и ссылка obsidian://, открывающая заметку у владельца.

Грабли:

  • Раздел объявляется синхронно, а данные приходят потом. Рисовалке на canvas хватает опроса каждый кадр, но блок из живого DOM строится один раз и сам себя не перерисовывает — ему нужен именно сигнал. Отсюда Feed.settled в api.ts. Ждать через requestAnimationFrame нельзя: в окне нулевого размера он не тикает вовсе, и блок остаётся пустым навсегда.
  • Внешние разделы (сайт баланса) втягивать внутрь нельзя: он генерирует свои данные своим скриптом. Такой пункт объявляется полем href и открывается новой вкладкой.

Владелец правды: scripts/lab_server.py (что именно читается с диска), docs/lab/src/api.ts (контракт загрузки), Assets/_Project/UI/Theme/tokens.*.uss (сама палитра).