docs: update docs

This commit is contained in:
2026-09-05 20:39:44 +05:00
commit 960e098ff7
2 changed files with 387 additions and 0 deletions
+60
View File
@@ -0,0 +1,60 @@
# twitch-panel-bio
Twitch Panel Extension: одна панель (318×496), внутри — несколько документов.
Контент — Markdown из git-репозитория: push → CI собирает → зрители видят новое
через ≤5 минут. Никаких бэкендов и кнопок «обновить».
## Структура
```
viewer.html / config.html входы (single-file билд, один файл каждый)
src/viewer/ вьювер: пейджер документов, тема Twitch
src/config/ панель управления (Creator Dashboard): порядок/скрытие
src/shared/DocRenderer.svelte рекурсивный рендерер JSON-AST (без {@html})
builder/ валидация MD по белому списку → JSON-AST, sharp-картинки
content/docs/*.md документы (front-matter: title обязателен, order/hidden/header)
.github/workflows/publish.yml CI: push в main → ветка `published` (index.json + docs + img)
config.json ← впиши сюда repo: "user/repo"
```
## Команды
| Команда | Что делает |
|---|---|
| `npm run dev` | dev-сервер (HTTPS :8080): `/viewer.html` и `/config.html` |
| `npm run build` | два single-file билда → `dist/pkg/` (viewer.html + config.html) — это zip-билд |
| `npm run build:content` | контент: `content/``out/` (index.json, docs/*.json, img/*.webp) |
Локально билдер требует заполненного `config.json`; в CI SHA коммита подставляется автоматически.
## Контент
- Документы: `content/docs/<id>.md`. Front-matter: `title` (обязателен), `order` (число),
`hidden` (bool), `header` (путь к заголовочной картинке от `content/assets/`).
- Картинки в тексте: `![](картинка.png)` — путь относительно .md; билдер жмёт в WebP
(макс. ширина 640) и переписывает на иммутабельный jsDelivr-URL. Битые пути = ошибка сборки.
- Заголовочные картинки режутся в 636×340 (панель 318×170 @2x, object-fit: cover).
- Белый список: заголовки, абзацы, **жирный**/*курсив*/~~зачёркнутый~~, списки (включая
GFM-чекбоксы), ссылки (только http/https), картинки, цитаты, `---`. Всё прочее
(HTML, код-блоки, таблицы) — ошибка сборки с файлом:строкой.
## Доставка
CI (`push` в `main`) собирает `out/` и коммитит в ветку `published` (peaceiris/actions-gh-pages,
история сохраняется). Вьювер читает `raw.githubusercontent.com/<repo>/published/index.json`
(кэш 5 мин), документы — по абсолютным jsDelivr-URL с SHA коммита (иммутабельные).
Конфиг-сегмент Twitch хранит только per-channel исключения: `{v:1, hidden:[], order:[]}`.
## Чеклист Twitch-консоли (dev.twitch.tv → Console)
1. Create Extension → тип **Panel**, заполнить имя/описание.
2. **Asset Hosting**: Base URI `https://localhost:8080/` (для Local Test);
Panel Viewer Path = `/viewer.html`; Panel Config Path = `/config.html`.
3. **CSP**: connect-src — `raw.githubusercontent.com`, `cdn.jsdelivr.net`;
img-src — `cdn.jsdelivr.net` (добавить raw, если картинки будут там).
4. Загрузить zip из `dist/pkg/` (Files → Upload Version).
5. Local Test → Activate на своём канале (ревью не нужно). Публичный релиз —
Hosted Test → Review (walkthrough + change log).
Pop-out отключить нельзя — это кнопка UI Twitch. Скрипт Helper (`twitch-ext.min.js`)
обязателен в обоих HTML — уже подключён.
+327
View File
@@ -0,0 +1,327 @@
# Исследование: Twitch Panel Extension «мультидокументная панель»
> Задача: расширение размером в одну Twitch-панель (в блоке «О канале»), внутри которого
> листается несколько документов на основе Markdown (+ заголовочные картинки).
> Управление контентом — через удобную админ-панель; желательно обновление через git.
> Фронтенд — Svelte 5.
---
## 1. Вердикт
**Полностью реализуемо, без бэкенда, бесплатно.** Платформа Twitch Extensions жива
(20252026, Developer Rig устарел, но сами Extensions и ревью работают).
Ключевой трюк: код приложения (зашит в билд расширения) почти не меняется, а контент
(MD + картинки) тянется в рантайме из git-репозитория через CORS-дружелюбный CDN
(jsDelivr / raw.githubusercontent.com — оба отдают `Access-Control-Allow-Origin: *`,
проверено curl-ом). Обновление контента = `git push`, без пересборки и без ревью Twitch.
---
## 2. Ключевые факты о панельных расширениях (проверено по докам)
| Факт | Значение |
|---|---|
| Тип расширения | **Panel** — «коробка» в блоке «О канале», под плеером |
| Размер | **318 × 496 px**, жёстко (чтобы не было скролла iframe); скроллим внутренний контент |
| Активность | Панель видна **даже когда канал офлайн** — идеально для био/справки |
| Pop-out | Зритель может открыть расширение в отдельном окне побольше |
| Хостинг кода | zip-загрузка на CDN Twitch (`*.ext-twitch.tv`) ИЛИ свой HTTPS-хостинг (Base URI) |
| Config Service | 3 сегмента (developer / broadcaster / global), **5 КБ на сегмент**, публично читаемо |
| Helper-библиотека | `<script src="https://extension-files.twitch.tv/helper/v1/twitch-ext.min.js">` — обязательна |
| CSP | Настраивается в консоли: домены для `connect-src` и `img-src` надо внести в allowlist |
| Sandbox | нет `alert/confirm/prompt`, нет вложенных iframe, нет top-navigation, storage API ограничен |
| Мобилки | Панели показываются в мобильном приложении, если включить mobile-ready; бюджет первой загрузки **1 МБ / 3 сек** |
| Ревью | Нужно только для публичного релиза. **На свой канал ставится без ревью** (Local Test → Activate) |
| Жизненный цикл | Local Test → Hosted Test → Review → Released (Released не редактируется, только новая версия) |
Важно: в Config Service можно хранить **только метаданные** (список документов, порядок,
URL'ы), не сами документы — 5 КБ мало, и сегмент публичен (никаких секретов).
Контент живёт в git-репозитории.
---
## 3. Архитектура (рекомендуемый вариант, без бэкенда)
```
GitHub-репозиторий (public) Twitch
├─ docs/
│ ├─ index.json ← манифест контента ┌─────────────────────────────┐
│ ├─ rules.md ← сами документы │ Extension (Panel 318×496) │
│ ├─ faq.md │ Svelte 5 + Vite │
│ └─ assets/ │ ┌───────────────────────┐ │
│ ├─ rules-header.png │ │ табы/пейджер доков │ │
│ └─ ... │ │ MD → HTML (sanitized) │ │
└─ (git push = обновление контента) │ │ картинки из CDN git'а │ │
│ └───────────────────────┘ │
Config Service (5 КБ) │ Config view (в дашборде): │
broadcaster segment ← JSON: список доков, │ CRUD документов, порядок, │
порядок, заголовки, URL md/картинок │ сохранение в конфиг-сегмент│
└─────────────────────────────┘
```
**Поток данных во вьювер-вью:**
1. При старте Helper отдаёт `configuration.broadcaster` (JSON со списком документов).
2. Приложение рендерит табы/карусель; по выбору таба лениво фетчит MD с jsDelivr
(`https://cdn.jsdelivr.net/gh/<user>/<repo>@<branch|sha>/docs/faq.md`).
3. Markdown → HTML (`marked`/`markdown-it`) → `DOMPurify.sanitize``{@html}` в Svelte.
4. Относительные пути картинок переписываются на абсолютные URL CDN до рендера.
5. Кэширование: jsDelivr держит кэш ветки ~12ч; для мгновенных апдейтов — либо
`@sha`-пиннинг (SHA коммита кладётся в index.json/конфиг), либо cache-buster query.
**Конфиг-вью (панель управления):** отдельная страница того же Svelte-приложения
(в консоли на неё указывает Panel Config Path; режим можно бранчить по query `?mode=config`,
которую Helper/Twitch сам подставляет). UI: список документов, добавить/удалить/скрыть,
переупорядочить (drag&drop), заголовок, ссылка на .md, URL заголовочной картинки,
кнопка «Сохранить» → `Twitch.ext.configuration.set('broadcaster', '1', JSON.stringify(...))`.
**Картинки:** заголовочные и inline-картинки кладутся в `docs/assets/` в том же репо
(«загрузка картинки» = файл в репо; при желании конфиг-вью может коммитить их сам через
GitHub REST API — см. открытый вопрос про PAT).
### Альтернативы
- **С EBS (свой мини-бэкенд, CF Worker/Node):** синк репо по webhook, прокси приватного
репо, серверное хранение PAT, аплоад картинок из UI коммитом в репо. Плюсы: приватность,
секреты, мгновенная инвалидация. Минусы: инфраструктура, которую мы хотели избежать.
- **Без git вообще (Twitch-only):** нежизнеспособно — Config Service 5 КБ, файловый хост
Twitch меняется только вместе с билдом версии. Именно поэтому внешний CDN — правильный путь.
---
## 4. Git-обновления: что именно работает
- `raw.githubusercontent.com``ACAO: *`, кэш 5 мин.
- `cdn.jsdelivr.net/gh/...``ACAO: *`, кэш браузера 7 дней / CDN 12 ч; поддерживает
пиннинг по ветке, тегу и SHA; есть минификация для `gh`.
- GitHub Pages — тоже `ACAO: *` (можно собирать статический сайт/бандлы из репо).
Схема: ветка `main` = черновик, тег/отдельная ветка `published` = то, что показывает панель;
в конфиг-сегменте храним ссылку с `@published` + SHA. Обновить контент = push + (при
SHA-пиннинге) одно нажатие в конфиг-вью. Никакого пересборочного пайплайна не нужно,
если MD рендерится на клиенте; при желании можно рендерить на CI (GitHub Actions →
JSON-бандл в `gh-pages`), но это опция.
---
## 5. Плюсы и минусы
### Плюсы
- Нативное место на канале, виден офлайн — ровно то, что нужно для био/справки.
- Одна панель слота — сколько угодно документов (табы), лимит только UX.
- Хостинг кода на CDN Twitch бесплатно; контент — на CDN GitHub бесплатно. Ноль затрат, ноль серверов.
- Обновление контента через `git push` — без пересборки расширения и без ревью (ревью нужно только коду при публичном релизе).
- Config Service бесплатно решает per-channel настройки (у каждого стримера свой список документов).
- Svelte 5 здесь уместен: маленький бандл (бюджет мобильной первой загрузки 1 МБ), рунеты ($state/$derived) закрывают весь стейт, Vite мульти-HTML сборка из коробки.
- Версионирование контента бесплатно из коробки — это git.
### Минусы / ограничения
- 318×496 px — тесно для длинных документов; обязателен внутренний скролл и мобильный дизайн; pop-out спасает частично.
- CSP-allowlist: каждый внешний домен (jsDelivr, GitHub) надо руками вносить в консоли на версию — забыл = контент молча не грузится.
- Config Service публичен и мал (5 КБ) — секреты (PAT) там хранить нельзя.
- Нет серверного аплоада картинок в CDN Twitch в рантайме — картинки только через репо/внешний хост.
- Песочница: нет alert/confirm, нет вложенных iframe, storage API ограничен (localStorage под вопросом — проверить рано).
- Platform risk: Twitch вправе менять правила (Rig уже убили); расширение не переносится на другие платформы.
- Мобильная поддержка = отдельная mobile-ready версия вьюера + жёсткий бюджет 1 МБ/3 c.
- Для публичного релиза — ревью с гайдом/скриншотами; Released-версию нельзя править, только новая версия.
---
## 6. Библиотеки
| Назначение | Пакет | Комментарий |
|---|---|---|
| Фреймворк | `svelte@5`, `vite`, `@sveltejs/vite-plugin-svelte` | два entry: viewer.html / config.html |
| Markdown | `marked` | крошечный, GFM из коробки; либо `markdown-it` если нужны плагины |
| Санитайзер | `dompurify` | **обязателен** — контент внешний |
| Заголовки-анкоры | `marked-gfm-heading-id` или `github-slugger` | для оглавления в панели |
| Подсветка кода | `highlight.js` (опц.) | shiki тяжёлый для 1 МБ бюджета |
| Формулы | `katex` (опц.) | если нужны |
| Git API из UI | `octokit` (опц.) | коммит картинок из конфиг-вью |
| Local dev | `mkcert` + Twitch CLI / community DevRig | Rig от Twitch устарел; HTTPS обязателен даже локально |
| Типы Helper | своя `.d.ts` для `window.Twitch.ext` | npm-пакета с офиц. типами нет надёжного |
CSS-фреймворк не нужен — свои стили на 318px проще.
---
## 7. Что надо сделать (по шагам)
1. **Консоль** dev.twitch.tv → Extensions → создать (тип Panel), получить client id/secret.
2. **Скелет**: Vite + Svelte 5, два entry (`viewer.html`, `config.html`), скрипт Helper
в обоих, `mkcert` для localhost:8080, в консоли Base URI + пути вьюера/конфига.
3. **Вьювер**: чтение `configuration.broadcaster` (fallback — дефолтный demo-документ),
табы/пейджер, ленивый фетч MD, marked+DOMPurify, рерайт путей картинок, скролл.
4. **Конфиг-вью**: CRUD списка документов + порядок + заголовочные картинки, сохранение
сегмента через `configuration.set`. (В конфиг-вью JWT имеет роль broadcaster.)
5. **Репозиторий контента**: `docs/*.md`, `docs/assets/`, `index.json`; CSP-домены в консоли.
6. **Тест на своём канале**: Local Test → «Activate» в Extension Manager — ревью не нужно.
Потом Hosted Test (загрузка zip) — проверить кэши CDN Twitch.
7. **(Опц.) Публичный релиз**: walkthrough guide, change log, скриншоты, EULA/privacy →
Review → Released или Invite Only (allowlist стримеров).
Оценка трудоёмкости MVP (вьювер + конфиг + репо + тест на своём канале): 2–4 вечера.
---
## 8. Открытые вопросы (решить в процессе)
1. **localStorage в sandbox** — работает ли (кэш доков между сессиями)? Проверить первым делом.
2. **Точный лимит zip-билда** в консоли (официальной цифры в доках нет, форум даёт ориентиры ~несколько МБ) — некритично, билд маленький.
3. **PAT для коммита картинок из конфиг-вью**: в Config Service хранить нельзя (публично).
Варианты: вводить PAT сессией (in-memory), хранить в EBS, или отказаться от аплоада из UI —
картинки просто коммитятся в репо руками.
4. **Приватный контент?** Если да — только через EBS-прокси (или приватный репо + Worker).
5. **Мобильная версия**: делать ли mobile-ready сразу; как табы ведут себя в мобильной панели.
6. **Инвалидация кэша**: ветка@main (до 12ч задержка jsDelivr) vs SHA-пиннинг (мгновенно, но
нужно обновлять ссылку) — выбрать схему; компромисс: index.json на @main, он дешёвый.
7. **Pop-out у панелей** — заявлен в доках дизайна; проверить фактическое поведение и размеры.
8. **Формат index.json** и схема конфиг-сегмента (поля: id, title, headerImage, src, sha?, hidden, order) — зафиксировать до начала UI.
---
## 9. Источники
- Extensions overview / hosting / CSP: https://dev.twitch.tv/docs/extensions/
- Building (Helper, Config Service 5 КБ, PubSub): https://dev.twitch.tv/docs/extensions/building/
- Designing (панель 318×496, pop-out): https://dev.twitch.tv/docs/extensions/designing/
- Life cycle (Local Test, ревью, Released): https://dev.twitch.tv/docs/extensions/life-cycle/
- Guidelines & Policies (мобайл 1 МБ): https://dev.twitch.tv/docs/extensions/guidelines-and-policies/
- Mobile-видимость панелей: https://help.twitch.tv/s/article/how-to-use-extensions
- Статус платформы / deprecation Rig: https://dev.twitch.tv/docs/product-lifecycle/ и https://barrycarlyon.co.uk/wordpress/2023/04/04/the-twitch-extensions-developer-rig-is-dead/
- CORS jsDelivr/GitHub: проверено curl-ом (ACAO: * на raw.githubusercontent.com, cdn.jsdelivr.net, pages)
- Pop-out как элемент UI Twitch (отключить нельзя): RFC 0008 https://discuss.dev.twitch.com/t/rfc-0008-pop-out-support-for-extensions/16920
- vite-plugin-singlefile (один вход на билд, multi-entry не поддерживается): https://github.com/richardtallent/vite-plugin-singlefile
---
## 10. Итерация 2 — принятые решения
Уточнения от автора: подсветка кода и формулы не нужны; localStorage не нужен;
приватность не нужна; интерактив только — кнопки листания и ссылки; попапы/pop-out не нужны;
нужен билдер контента (кроп картинок, валидация MD, чистка лишнего); главный приоритет —
надёжная загрузка контента.
### 10.1 Билд фронтенда → два самодостаточных HTML
- Vite/Svelte по умолчанию НЕ дробит на мелкие чанки: код-сплиттинг возникает только из
`import()` и нескольких entry с общими модулями. Правило: **не использовать `import()` вообще**
(ленивость не нужна — контент лениво грузится по сети).
- Схема: `viewer.html` и `config.html`, каждый собирается отдельным билдом через
`vite-plugin-singlefile` → один HTML без внешних js/css (плагин официально не умеет
multi-entry, поэтому два конфига: `vite build -c vite.viewer.config.ts` + `vite build -c vite.config.config.ts`).
В консоли: Panel Viewer Path = `/viewer.html`, Panel Config Path = `/config.html`.
- Размеры: вьювер без marked/DOMPurify (см. 10.2) ≈ 1525 КБ, конфиг-вью ≈ 40–60 КБ —
на порядок ниже мобильного бюджета 1 МБ и лимитов zip.
- Альтернатива (проще): обычный multi-entry Vite без плагина → 2 html + 34 hashed-ассета.
Тоже корректно; single-file выигрывает надёжностью (меньше файлов, меньше шансов ошибиться в путях).
- CSP в консоли: `connect-src`/`img-src` = cdn.jsdelivr.net + raw.githubusercontent.com.
Шрифтов и GA нет — системный стек.
### 10.2 Пре-рендер MD в билдере + чистка на стороне зрителя
Рендер Markdown происходит **на этапе сборки (в CI)**, но решение «убрать санитайзер
из рантайма» пересмотрено (итерация 3): всё, что приходит с jsDelivr, считается
**недоверенными данными** (репо/CDN могут быть подменены), а доверенный корень — это
код вьювера (CDN Twitch) и конфиг-сегмент (Twitch). Чистка результата выполняется
у зрителя, до вставки в DOM — два варианта в 10.7.
Пайплайн: `git push → GitHub Actions → builder → ветка published → jsDelivr`.
### 10.3 Билдер контента (Node-скрипт, `builder/`)
Стек: `unified` + `remark-parse` + `remark-gfm` (AST + валидация по белому списку),
`gray-matter` (front-matter: title/order/hidden), `sharp` (картинки). Сериализация:
основной вариант — компактный JSON-AST (своя функция ~50 строк поверх mdast, см. 10.7);
запасной — `remark-rehype` + `rehype-external-links` + `rehype-stringify` → HTML
(тогда во вьювере DOMPurify).
- **Валидация MD по белому списку узлов AST**: heading, paragraph, emphasis/strong,
list/listItem, link, image, blockquote, hr, break, text. Всё прочее (html, code, table,
footnote…) = ошибка сборки с файлом и позицией. Белый список — один конфиг-объект.
- **Картинки (sharp)**: заголовочные — кроп 636×340 (318×170 @2x, fit:cover), webp;
inline — max width 640, webp q≈80; метаданные стирать; битые пути = ошибка билда;
лимит на размер исходника (напр. 10 МБ) и на суммарный вес документа.
- **Чистка лишнего (кандидаты, расширяемо)**: сырой HTML в MD; запрещённые конструкции;
битые относительные ссылки/картинки; нормализация уровней заголовков (в панели не выше h4);
трим пустых строк/пробелов; слишком большие документы → warning с размером.
- **Выход**: `published/index.json`, `published/docs/*.json` (или `*.html` в варианте A),
`published/img/*`; для каждого файла — SHA-256 в index.json (см. 10.7). Относительные
пути картинок работают на jsDelivr как есть (всё под одним `@sha`-префиксом) — рерайт не нужен.
### 10.4 Схема доставки (надёжность важнее автомагии)
- **Индекс читается вьювером напрямую из CDN**: `https://raw.githubusercontent.com/<u>/<r>/published/index.json`
(TTL raw — 5 минут, файл крошечный). Каждый entry содержит абсолютные jsDelivr-URL,
**запиннованные на SHA коммита** (`.../gh/<u>/<r>@<sha>/published/...`) — контент иммутабелен,
нет расхождения TTL: индекс ссылается только на документы, которые уже существуют.
- **Обновление контента полностью автоматическое**: push → CI собрал published →
через ≤5 минут зрители видят новое. Никаких кнопок и заходов в панель.
- **broadcaster-сегмент — только per-channel исключения** (опционально):
`{v:1, hidden:[id], order:[id]}`; пустой сегмент = порядок из index.json.
- Fallback: index недоступен → developer-сегмент → вшитый дефолтный документ.
- localStorage не используется.
### 10.5 Pop-out и интерактив
- Pop-out отключить нельзя — это кнопка UI Twitch (RFC 0008), не наша. Приложение просто
остаётся нейтральным к размеру окна.
- Интерактив внутри: только кнопки «‹ ›» и ссылки. Ссылки `target=_blank` разрешены
песочницей (allow-popups), top-navigation заблокирован самим Twitch — «увести» зрителя
со страницы нельзя.
### 10.6 Итоговый стек (после упрощений)
- Приложение: `svelte@5`, `vite`, `@sveltejs/vite-plugin-svelte`, `vite-plugin-singlefile`. Всё.
- Билдер: `unified`/`remark-parse`/`remark-gfm`, `gray-matter`, `sharp` (+rehype-стек в варианте A).
- Убраны: marked, highlight.js, katex. DOMPurify — по варианту 10.7 (A: в рантайме ~8 КБ gzip; B: не нужен вовсе).
### 10.7 Санитизация на стороне зрителя (итерация 3)
Модель доверия: доверенный корень = билд расширения (CDN Twitch) + конфиг-сегмент
(хранит Twitch, правит только аккаунт стримера). Всё, что приходит с jsDelivr (даже
запиннованное на SHA) — недоверенные данные: компрометация репо/CDN/GitHub-аккаунта не
должна приводить к исполнению чего-либо у зрителя. Важно: билдер живёт в том же репо и
тоже подменяем — поэтому граница доверия обязана находиться в коде, который раздаёт Twitch.
**Вариант B — рекомендуемый: JSON-AST вместо HTML, никакого `{@html}`**
- билдер сериализует проверенный mdast в компактное мини-дерево:
`["h2","Заголовок"]`, `["p","текст ",["strong","жирный"]]`, `["ul",[["li","пункт"]]]`,
`["a","https://…","текст"]`, `["img","https://…","alt"]`, `["blockquote",[…]]`, `["hr"]`;
- вьювер — один рекурсивный Svelte-компонент (~80–120 строк): строка = текст
(авто-эскейп Svelte), неизвестный узел = пропустить с warning в консоль,
URL проверяется по схеме (img: только https на cdn.jsdelivr.net / raw.githubusercontent.com;
a: только http/https), атрибуты кроме href/src/alt/title не мапятся вообще;
- санитайзер не нужен: в DOM физически попадают только узлы, которые отрисовал наш код.
Zero зависимостей — это и есть «попроще»: нечего чистить.
**Вариант A — запасной: HTML в выдаче + DOMPurify во вьювере**
~8–9 КБ gzip, одна строка вызова. Tight-конфиг: `ALLOWED_TAGS` по белому списку,
`ALLOWED_ATTR = ['href','src','alt','title']`, `ADD_ATTR: ['target']`. Стандарт индустрии,
mXSS закрыты, поддерживается активнее всех.
**Дополнительный слой — SHA-256 пиннинг (напрямую закрывает «подменят файлы»)**
- билдер считает SHA-256 каждого файла выдачи → index.json → конфиг-сегмент;
- вьювер: `const buf = await (await fetch(url)).arrayBuffer();`
`const digest = await crypto.subtle.digest('SHA-256', buf);` → сравнить со значением из
сегмента; не совпало → экран «контент изменился, обновите список в панели управления».
~10 строк, ноль зависимостей, работает во всех браузерах (secure context).
- согласуется с @sha-пиннингом: старый контент остаётся доступен по старому SHA, поэтому
push без нажатия «Обновить список» ничего не ломает — зритель видит прежнюю версию.
- картинки через `<img>` так не проверить; для них достаточно host-allowlist в рендерере;
максимум паранойи — тот же fetch+hash+objectURL.
Остаточный риск (честно): компрометация Twitch-аккаунта = компрометация доверенного корня
(атакующий правит сегмент и хеши). Это граница любой схемы без EBS.
### 10.8 Итерация 4 — финальные решения
- **SHA-256 слой отклонён**: лишний обязательный шаг («обновила файл — зайди в панель и
нажми кнопку») перевешивает пользу. Подмена ловится схемой B + host-allowlist.
- **Принят вариант B**: JSON-AST, рекурсивный рендерер, никакого `{@html}` и санитайзера.
- **Доставка упрощена** (см. 10.4): index.json вьювер берёт прямо с raw@published
контентные пуши доезжают до зрителей сами за ≤5 минут; сегмент — только исключения.
- Реализован скелет: два single-file билда (viewer/config), вьювер с пейджером,
конфиг-панель (скрыть/порядок), билдер (валидация + JSON-AST + sharp), sample-контент, CI.