Начни здесь

Из конспекта в обучающий сайт: skills-edu-site

Практический курс по @dzhechkov/skills-edu-site для того, кто впервые видит пакет. Вместе с Леной, преподавателем основ сетей, ты установишь пак навыков, разберёшь, что именно легло в проект, пройдёшь восемь шагов конвейера генератора, выберешь типы упражнений под своё содержание, поймёшь, где живут данные курса и как работает слой достижений, опубликуешь сайт на GitHub Pages и проверишь сборку перед тем, как показать его студентам.

Знакомься: Лена

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

Содержание курса

1. Что делает skills-edu-site

Пакет одной фразой: из документации — интерактивный обучающий сайт с тестами, карточками и достижениями.

Ключевая мысль: генератор обучающего сайта из документации

Лена преподаёт основы сетей и уже три года раздаёт студентам одну и ту же папку конспектов в markdown. Читают её, по её же подсчёту, человек пять из тридцати. Её вопрос звучал так же, как, возможно, и твой: «а можно из этого сделать что-то, во что захочется кликать?»

Можно, и ровно для этого существует пакет. @dzhechkov/skills-edu-site — это генератор обучающего сайта из документации: пак навыков для Claude Code, который превращает конспекты, руководства и базы знаний в интерактивное одностраничное приложение с тестами, карточками, достижениями, полосой прогресса и готовой публикацией на GitHub Pages.

Разберём слова, потому что дальше они будут встречаться постоянно:

  • Навык (skill) — папка с инструкциями для Claude Code: прочитав её, ассистент умеет выполнять новую задачу. Пакет ставит ровно один навык — edu-site-generator.
  • Одностраничное приложение (SPA) — сайт, который загружается один раз, а дальше переключает разделы без перезагрузки страницы. Именно такой сайт получит Лена.
  • Геймификация — очки, значки-достижения и полоса прогресса, которые превращают чтение в прохождение.

Где пакет живёт: страница на npm, исходники — в репозитории на GitHub. Пакет входит в экосистему Keysarium — о соседстве с ней будет отдельная секция.

Что важно понять с первой минуты: генератор не пишет сайт «в общих чертах». Он идёт по фиксированному конвейеру из восьми шагов — от разбора твоих документов до проверки сборки — и после каждого шага останавливается и спрашивает «ok?». Лена сначала решила, что это лишняя церемония. К концу курса ты поймёшь, почему она передумала.

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

2. Установка за одну команду

npx init, открыть Claude Code, вызвать /edu-site-generator — три шага до первого сайта.

Ключевая мысль: команда init устанавливает пак

Хватит вводных — Лена уже открыла терминал в папке своего проекта. Открывай и ты.

Путь до первого сайта — три шага:

  1. Установи пак навыков: npx @dzhechkov/skills-edu-site init. Команда init устанавливает пак в текущий проект. Голый вызов npx @dzhechkov/skills-edu-site без слова init делает то же самое — init подразумевается по умолчанию, и вопросов команда не задаёт.
  2. Открой Claude Code в этой же папке. Навык регистрируется под именем своей папки — edu-site-generator.
  3. Вызови навык: /edu-site-generator ./docs/ — и генератор начнёт разбирать твои документы.

Есть и глобальный вариант: npm install -g @dzhechkov/skills-edu-site, а потом skills-edu-site init в нужном проекте. Лена выбрала npx — ничего не остаётся в системе, кроме файлов в проекте.

Два флага, которые стоит знать до первого запуска:

  • init --dry-run — покажет, что будет сделано, ничего не записывая. Лена запустила его первым и увидела список файлов до того, как они появились.
  • init --force — перезапишет существующие файлы. Без него повторный init в уже установленный проект честно откажет: «уже установлено, используй update или --force» — и выйдет с кодом 1.

Грабли Лены — не повторяй: она первый раз запустила init не в папке проекта, а в домашней. Пакет кладёт файлы относительно текущей директории, а не «куда-нибудь в проект». Сначала cd в проект, потом init.

Что нужно на машине: Claude Code, Node.js не ниже 16 и npm — он понадобится позже, чтобы собрать сгенерированный сайт.

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

3. Что легло в проект: навык и манифест

Папка .claude/skills/edu-site-generator/ с восемью модулями и четырьмя справочниками — плюс манифест в корне, без которого остальные команды слепы.

Ключевая мысль: манифест установки .skills-edu-site.json

Лена не любит, когда что-то «само появилось». Поэтому после init она первым делом открыла дерево проекта — давай посмотрим вместе.

В проекте появилась папка .claude/skills/edu-site-generator/, и внутри неё ровно то, что перечисляет README:

  • SKILL.md — сам навык, он же оркестратор: описывает, когда навык включается и в каком порядке читать модули.
  • modules/ — 8 модулей, шаги 00–07: от разбора содержания до проверки сборки. Каждый шаг конвейера — один файл.
  • references/ — 4 справочника: шаблоны компонентов, схемы данных, каталог упражнений, описание стека.
  • examples/ — 1 пример структуры готового курса на пять секций.

И ещё один файл, не в папке навыка, а в корне проекта: .skills-edu-site.json. Это манифест установки — список того, что пакет положил, с версией и датой. Лена чуть не удалила его как мусор. Не делай так: команды update, remove, list и doctor читают именно манифест, чтобы понять, что установлено. Удалишь манифест — и list скажет «пак не установлен», хотя папка навыка на месте.

Зачем манифест, если можно просто посмотреть папку? Потому что папка .claude/skills/ общая: рядом могут лежать навыки других пакетов. Манифест отвечает на вопрос «какие из этих файлов — мои», и только по нему remove может убрать своё, не тронув чужого.

Компромисс: отдельный файл-манифест — это ещё один артефакт в корне проекта, который надо коммитить вместе с навыком; взамен каждая команда пакета точно знает свою границу и никогда не удалит соседский навык.

4. Жизненный цикл пака: update, remove, list, doctor

Как обновлять, проверять и удалять пак — и почему update из старой глобальной установки ничего не обновит.

Ключевая мысль: update сверяет с шаблонами запущенной версии

Через месяц Лена услышала от коллеги: «в новой версии пакета поправили модуль проверки». Она запустила skills-edu-site update из своей глобальной установки — и увидела «всё актуально». Обновления не произошло. Прежде чем читать дальше, реши: пакет соврал, обновление ещё не вышло — или Лена спросила не у того?

Ответ — третье, и это главное, что нужно знать про update. Команда update сверяет установленные файлы с шаблонами, которые лежат внутри той версии пакета, которую ты запустил. В реестр npm она не ходит и «есть ли что новее» не проверяет. Устаревшая глобальная установка предложит только саму себя. Поэтому обновляться надо так: npx @dzhechkov/skills-edu-site@latest update — @latest заставляет npx взять свежую версию, и уже её шаблоны становятся эталоном. update --dry-run покажет, какие файлы добавятся и изменятся, прежде чем что-то трогать.

Остальные команды жизненного цикла:

  • list — таблица установленных компонентов, версия и дата установки. Всё это из манифеста.
  • doctor — шесть проверок здоровья: все файлы манифеста на месте; папка навыка и SKILL.md есть; модули есть; справочники есть; пример есть; состояние интеграции с Keysarium. К каждой провалившейся проверке — подсказка, как чинить (обычно — update).
  • remove — удаляет папку навыка и манифест, но сначала спрашивает подтверждение. Если пак не установлен, просто сообщает об этом.

Про remove есть измеренная деталь, которая важна для скриптов. Когда команда запущена не из терминала, а из конвейера или пайпа (ввод — не TTY), спросить она не может. Без --force она отказывает и выходит с кодом 1, а не делает вид, что всё удалила: README приводит воспроизводитель — printf '' | node bin/cli.js remove; echo $? даёт 1 и все файлы на месте; с --force — удаляет и выходит с 0. Лена оценила это как сетевик: тихий «успех» без действия хуже громкого отказа.

Компромисс: отсутствие сетевой проверки делает update быстрым и предсказуемым — эталон всегда на диске, — но перекладывает на тебя обязанность запускать его через @latest; отказ remove без TTY защищает автоматизацию от ложного зелёного, но требует явного --force в скриптах.

5. Соседство с Keysarium

Пакет работает сам по себе, но узнаёт Keysarium в проекте — по одному файлу, без сети.

Ключевая мысль: автоопределение Keysarium по файлу .keysarium.json

В README дважды упоминается Keysarium, и Лена спросила: «мне это обязательно?» Короткий ответ — нет. Длинный — интереснее.

Keysarium — соседний пакет того же автора, большой набор навыков для Claude Code, в котором есть свой исследовательский конвейер Casarium. skills-edu-site работает и без него: всё, что ты прошёл в прошлых секциях, ни разу не потребовало Keysarium.

Но если Keysarium в проекте уже стоит, генератор об этом узнает — и вот как. При init пакет проверяет, есть ли в корне проекта файл .keysarium.json — манифест Keysarium, точно такой же по назначению, как наш .skills-edu-site.json. Нашёл — печатает рамку «Keysarium обнаружен», показывает его версию и сообщает, что оба пака делят одну папку .claude/skills/. Это и есть автоопределение Keysarium по файлу — никакого запроса в сеть, никакого сканирования системы: один файл в корне.

Лена, привыкшая к тому, что «обнаружение» обычно означает опрос сервисов, удивилась простоте. Здесь решение честнее: файл-манифест либо есть, либо нет, и результат воспроизводим на любой машине.

Что даёт соседство:

  • навык edu-site-generator становится частью общего инструментария Keysarium;
  • генератор умеет делать обучающий сайт из исследовательских артефактов, которые производит конвейер Casarium, — а не только из твоих конспектов;
  • doctor включает отдельную проверку состояния этой интеграции.

Порядок установки, если хочешь обоих: сначала npx @dzhechkov/keysarium init, потом npx @dzhechkov/skills-edu-site init — тогда второй увидит манифест первого. Поставишь в обратном порядке — ничего не сломается, просто рамки «обнаружен» при init не будет.

Компромисс: определение по файлу дёшево и воспроизводимо, но и наивно: если манифест Keysarium остался от удалённой установки, пакет всё равно скажет «обнаружен» — он верит файлу, а не проверяет соседа.

6. Восемь шагов конвейера

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

Ключевая мысль: восемь шагов конвейера

Лена набирает /edu-site-generator ./docs/ — и вместо «сайт готов» получает: «Шаг 0/7: анализ содержания завершён. Тем найдено: 9. Язык: ru. «ok» — продолжить, или напиши, что поправить». Она озадачена. Ты — уже нет, потому что знаешь из первой секции: генератор идёт по конвейеру.

Конвейер — это восемь шагов, по одному файлу-модулю на каждый, modules/00-… — modules/07-…:

  1. 00 Анализ содержания — читает твои документы, выделяет темы, определяет язык, прикидывает число секций.
  2. 01 Структура курса — раскладывает темы по секциям от простого к сложному и назначает каждой тип упражнения.
  3. 02 Генерация данных — пишет файлы данных: секции, упражнения, вопросы тестов, достижения.
  4. 03 Каркас проекта — package.json, конфигурация Vite, index.html, тема в CSS.
  5. 04 Компоненты — интерфейс: раскладка, шесть интерактивных компонентов, страницы.
  6. 05 Геймификация — хранилище состояния, прогресс, достижения, уведомления.
  7. 06 Публикация — конфигурация GitHub Pages и рабочий процесс GitHub Actions.
  8. 07 Проверка — npm run build и перекрёстные проверки.

Три взгляда на один и тот же конвейер — выбери тот, что тебе ближе:

  • Общая картина: первые три шага — про содержание, следующие три — про код, последние два — про доставку.
  • По шагам: каждый модуль заканчивается контрольной точкой «Step N/7 … Complete», списком артефактов и вопросом «ok?».
  • Как артефакт: после шага 02 у тебя есть файлы данных, которые можно открыть и прочитать ещё до того, как появится хоть один компонент.

Зачем остановка после каждого шага? Лена поняла на шаге 01: генератор назначил её теме «команды диагностики» карточки, а она хотела конструктор команд. Одно слово в ответ — «шаг 3 сделай builder» — и структура поправлена до того, как написан код. Без контрольной точки она узнала бы об этом на готовом сайте.

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

7. Шесть типов упражнений — и какой к чему

Тест, карточки, сопоставление, порядок, конструктор команд, сценарий: как генератор выбирает тип по природе содержания.

Ключевая мысль: шесть типов упражнений

На шаге 01 генератор показал Лене таблицу: тема — тип упражнения. Она посмотрела и спросила: «а по какому принципу?» Принцип есть, и он записан в справочнике references/exercise-catalog.md.

В пакете шесть типов упражнений, и у каждого — своя природа содержания:

  • Тест (quiz) — факты, правила, лучшие практики. Вопросы с вариантами и объяснением к правильному ответу.
  • Карточки (flashcards) — термины и определения, всё, что запоминают парами. Карточка переворачивается по клику.
  • Сопоставление (matching) — связи: инструмент — назначение, причина — следствие. Левая колонка на месте, правая перемешана.
  • Порядок (drag-and-drop) — процессы с определённой последовательностью шагов. Перетаскиваешь, потом «Проверить».
  • Конструктор команд (builder) — синтаксис команд и вызовов, где важен порядок частей. Собираешь из кусочков, подсказки идут по нарастающей.
  • Сценарий (scenario) — решения в ситуации: несколько шагов, у каждого два-три варианта с оценкой «хорошо / нейтрально / плохо».

Значения interactiveType — ровно эти шесть слов. Старые имена ordering, simulation и drag-order модуль анализа прямо запрещает: они остались как псевдонимы в компонентах, но в новых данных им не место.

Теперь правило, ради которого эта секция цитирует принцип разнообразия. У генератора есть запрет на три одинаковых типа подряд и требование использовать все шесть типов, если секций шесть и больше. Лена спросила, зачем это в коде, а не в рекомендациях. Ответ в таблице анти-паттернов навыка: «все упражнения одного типа — монотонный интерфейс». Внимание студента устаёт от однообразия быстрее, чем от сложности. Каталог даже даёт раскладку: для 10–15 секций — 3 теста, 2 карточек, 2 сопоставления, 2 порядка, 1 конструктор, 2 сценария.

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

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

8. Ты — методист: сколько секций и из чего

Четыре формы входа, границы 3–20 секций, 150–400 слов теории: реши за Лену, как разложить её конспекты.

Ключевая мысль: от трёх до двадцати секций

В этой секции решения принимаешь ты. Лена принесла папку: 14 файлов markdown про основы сетей, два из них — по абзацу, один — на сорок страниц. Генератор ждёт от тебя не «ok», а методических решений. Правила, в которых ты их принимаешь, записаны в SKILL.md и модулях 00–01.

Что можно подать на вход — одно из четырёх:

  1. адрес документации в сети — генератор её загрузит;
  2. путь к файлу или папке — прочитает напрямую;
  3. вставленный текст — возьмёт как есть;
  4. описание темы словами — сгенерирует содержание из знаний модели.

Четвёртый способ — самый соблазнительный и самый рискованный: содержание будет правдоподобным, но не твоим. Лена отказалась от него сразу: её студенты сдают её экзамен, а не экзамен модели.

Сколько секций. Границы жёсткие: от трёх до двадцати секций. Меньше трёх — навык предупредит, что содержания не хватает для геймификации, и предложит добавить документов. Больше двадцати — предложит сгруппировать темы или разбить на два курса. Золотая середина по модулю анализа — 5–15.

Сколько теории в секции. Модуль структуры просит 150–400 слов: контекст, суть, применение. Анти-паттерн «теория слишком длинная» — больше 500 слов — лечится разбиением на подсекции. Сорокастраничный файл Лены — это не одна секция, а четыре-пять.

Что ещё решается на шаге 01:

  • порядок от простого к сложному, синтез в конце;
  • тип упражнения по природе содержания (прошлая секция);
  • отдельная эмодзи-иконка на секцию, без повторов;
  • по одному вопросу финального теста на секцию, четыре варианта.

Вопрос без единственно верного ответа, над которым Лена думала дольше всего: два файла по абзацу — это две тонкие секции, одна общая или материал для FAQ? Правильного ответа нет; есть решение, которое ты сможешь объяснить студентам.

Компромисс: жёсткие границы 3–20 и 150–400 слов отсекают заведомо плохие курсы, но внутри границ генератор не знает твоих студентов — тонкую резку тем делаешь ты, на контрольных точках шагов 00 и 01.

9. Где живёт содержание: src/data

Секции, упражнения, вопросы и достижения — в файлах данных, а не в компонентах. И почему дерево в README — эскиз, а схема — контракт.

Ключевая мысль: данные курса живут в src/data

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

Ответ генератора однозначен, и он записан в таблице анти-паттернов навыка: «содержание в JSX вместо data/» — ошибка; всё содержание обязано жить в файлах данных. Компоненты только показывают то, что в них подали. Поэтому данные курса живут в src/data, и опечатку Лена правит в одном файле, не открывая ни одного компонента.

Что лежит в src/data по обязывающему справочнику references/data-schemas.md:

  • sections.js — массив секций: id, порядок, заголовок, иконка, тип упражнения, теория; рядом TOTAL_SECTIONS.
  • exercises.js — данные упражнений по типам: карточки, пары сопоставления, порядок, конструктор, сценарии — и faqData для FAQ.
  • quizQuestions.js — вопросы тестов по секциям и finalTestQuestions: по одному на секцию, с sectionId.
  • achievements.js — достижения с условиями открытия.

А теперь честное наблюдение, которое Лена сделала раньше меня. В README, в разделе «Output Structure», дерево проекта нарисовано иначе: src/data/modules.json, exercises.json, stores/useProgress.js. В SKILL.md и схемах — sections.js, store/useAppStore.js, hooks/. Два документа одного пакета расходятся. Что считать правдой?

Правило простое: обязывающий документ — тот, по которому генератор пишет файлы, а это data-schemas.md и модуль 02: именно их навык читает на шаге генерации данных. Дерево в README — эскиз для первого взгляда. Если сомневаешься, открой сгенерированный проект: он и есть окончательный ответ.

Ещё одно, что README говорит верно: файла tailwind.config.js в проекте нет и не будет — об этом в секции про стек.

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

10. Слой достижений и прогресса

Хранилище Zustand с сохранением в localStorage, пять стандартных достижений, всплывающие уведомления и финальный тест на 70%.

Ключевая мысль: достижения и прогресс хранятся в localStorage

Студент Лены закрыл вкладку на шестой секции, а назавтра открыл сайт — и прогресс на месте: шесть галочек, два значка, полоса на 46%. Лена спросила: «где это хранится, если у нас нет сервера?»

Ответ — в браузере самого студента. Достижения и прогресс хранятся в localStorage, а управляет ими хранилище состояния Zustand с промежуточным слоем persist. Zustand — маленькая библиотека, которая держит состояние приложения в одном объекте; persist при каждом изменении записывает выбранные поля в localStorage под ключом вида <имя-курса>-progress и восстанавливает их при открытии сайта.

Что именно сохраняется (модуль 05, partialize):

  • пройденные секции и балл за каждую (0–100 по результату упражнения);
  • открытые достижения;
  • результат финального теста и ответы;
  • выбор тёмной темы — при загрузке класс dark восстанавливается ещё до первого кадра.

Достижения живут в src/data/achievements.js. Пять стандартных есть в каждом курсе: first-step (первая секция), halfway (половина), perfectionist (секция на 100), full-course (все секции), test-passed (финальный тест на 70 и выше). К ним — три-пять тематических, привязанных к группам секций; всего 8–12. Хук useAchievements после каждого изменения состояния проверяет условия и открывает новые; всплывающее уведомление появляется справа сверху и само исчезает через четыре секунды.

Финальный тест — по одному вопросу на секцию, порог прохождения 70%. Результат считается отдельно от баллов секций.

Теперь вопрос на самонаблюдение. Ты только что прошёл девять секций этого курса и, скорее всего, видел уведомления о своих значках. Вспомни момент, когда всплыло первое, — заметил ли ты, что после него прошёл следующую секцию внимательнее? Это и есть механизм, который Лена включает для своих студентов: прогресс, который видно, меняет поведение.

Компромисс: хранение в браузере не требует сервера и работает с GitHub Pages, где сервера и нет, — но прогресс привязан к одному браузеру на одном устройстве, и «сброс прогресса» — это кнопка resetProgress, а не восстановление из резервной копии.

11. Стек: React 19, Vite, TailwindCSS v4

Пять технологий сгенерированного сайта — и две «примечания мелким шрифтом», которые на деле главное: нет tailwind.config.js и только HashRouter.

Ключевая мысль: TailwindCSS v4 настраивается в CSS через @theme

Лена захотела перекрасить сайт в цвета колледжа и полезла искать tailwind.config.js. Файла нет. Она решила, что генератор его забыл, и почти создала свой. Хорошо, что дочитала README до примечания под таблицей.

Сначала таблица стека — пять технологий, из которых собран сгенерированный сайт:

  • React 19 — библиотека интерфейса.
  • Vite — сборщик и сервер разработки.
  • TailwindCSS v4 — CSS-каркас с утилитарными классами.
  • Zustand — хранилище состояния (прошлая секция).
  • React Router v7 — переключение страниц на стороне браузера.

Плюс @dnd-kit для перетаскивания в упражнениях «порядок» и «сопоставление».

А теперь два примечания, которые обычно пропускают, — и которые в этом пакете важнее таблицы. Здесь и цитируется принцип «врезки — не украшение»: то, что напечатано мелким шрифтом под таблицей, и есть главное.

Примечание первое. Файла tailwind.config.js нет, потому что TailwindCSS v4 настраивается в CSS через директиву @theme. Цвета, шрифты и прочие токены темы объявляются прямо в src/index.css; свои утилиты — через @utility, а не через @layer utilities; подключение — одной строкой @import "tailwindcss". Лена перекрасила сайт, поправив несколько строк в index.css, и никакой конфигурационный файл ей не понадобился. Создай она tailwind.config.js — он бы просто молча ничего не делал.

Примечание второе. Маршрутизатор — обязательно HashRouter, не BrowserRouter. GitHub Pages отдаёт index.html только по корню и не умеет отвечать на пути вроде /section-3 — сервера, который перехватил бы такой запрос, там нет. HashRouter кладёт раздел после #, и браузер никогда не спрашивает сервер о нём. По той же причине навигация по секциям сделана через scrollIntoView(), а не через ссылки <a href="#section-…"> — они конфликтуют с хеш-маршрутизацией.

Компромисс: TailwindCSS v4 убирает конфигурационный файл и держит тему рядом со стилями — удобно, но всё, что ты знал о tailwind.config.js из v3, здесь не работает; HashRouter даёт совместимость с GitHub Pages ценой символа # в адресах.

12. Публикация на GitHub Pages

deploy.yml, base по имени репозитория, HashRouter, push в main — и почему 404 на Pages почти всегда про base.

Ключевая мысль: base равен имени репозитория

Сайт собран, Лена запушила его в репозиторий networks-101 — и получила белую страницу с ошибками 404 в консоли. Посмотри на схему под текстом: путь до работающей страницы — четыре узла, и Лена споткнулась на втором.

Шаг 06 конвейера готовит публикацию так:

  1. .github/workflows/deploy.yml — рабочий процесс GitHub Actions: при каждом push в ветку main он ставит Node 20, выполняет npm ci и npm run build, загружает папку dist как артефакт Pages и разворачивает его. Параметр enablement: true сам включает Pages в репозитории, если они ещё не включены.
  2. base в vite.config.js — здесь и была ошибка Лены. base равен имени репозитория: для https://github.com/lena/networks-101 это base: '/networks-101/'. Генератор выводит его из адреса git remote; если удалённого репозитория ещё нет, спрашивает имя у тебя. Лена создала репозиторий после генерации, и base остался от заготовки. Все пути к скриптам и стилям смотрели не туда — отсюда 404.
  3. HashRouter — из прошлой секции: разделы после #, сервер о них не спрашивают.
  4. Push в main — и через минуту-две сайт открывается по адресу https://<пользователь>.github.io/<репозиторий>/.

Почему схема здесь важнее текста: 404 на Pages выглядит одинаково при любой из трёх причин — неверный base, BrowserRouter вместо HashRouter, не включённые Pages. Глядя на четыре узла, ты проверяешь их по очереди, а не гадаешь. Таблица анти-паттернов навыка отдельно называет «неверный base путь → 404 на GitHub Pages» и лечение: «выводить из имени репозитория».

Проверь себя: сайт по адресу https://lena.github.io/networks-101/ — какой base? Если ответил '/networks-101/' со слешами с обеих сторон — ты уже не наступишь на грабли Лены.

Компромисс: публикация через Actions полностью автоматическая и бесплатная, но привязана к имени репозитория: переименуешь репозиторий — обязан поменять base, иначе снова белая страница.

13. Проверка перед показом студентам

npm run build без ошибок, перекрёстные проверки данных и три известные ловушки — шаг 07 и честный итог курса.

Ключевая мысль: сборка npm run build подтверждает готовность

Лена собралась показывать сайт группе в понедельник. Последний шаг конвейера — 07, проверка — существует ровно для того, чтобы понедельник прошёл без сюрпризов.

Что подтверждает готовность: сборка. npm run build обязан завершиться без единой ошибки. Не «сайт открывается у меня в dev-режиме», а именно сборка: Vite в режиме разработки прощает то, что production-сборка не пропустит. Если ошибка есть, модуль 07 даёт таблицу «ошибка → причина → починка»: не найден модуль — нет файла или неверный путь; не экспортируется имя — расходятся имена в файле данных; не найден @dnd-kit — доустановить; класс TailwindCSS не применяется — токен не объявлен в @theme. Протокол: прочитать ошибку целиком, найти в таблице, починить, собрать заново — пока не станет чисто.

Перекрёстные проверки — то, что сборка не ловит, потому что это не ошибки синтаксиса, а расхождения данных:

  • у каждой секции из SECTIONS есть свой компонент SectionN.jsx;
  • у каждого interactiveType есть данные упражнения;
  • в finalTestQuestions — ровно по одному вопросу на секцию с верным sectionId;
  • файлы robots.txt и sitemap.xml на месте, и в карте сайта есть адрес каждой секции.

Три известные ловушки, которые модуль велит проверять поимённо, потому что они уже случались:

  1. В компоненте теста счётчик правильных ответов при переходе к следующему вопросу не должен прибавляться дважды.
  2. Навигация по секциям — через scrollIntoView(), не через ссылки с # (конфликт с HashRouter).
  3. Тёмная тема восстанавливается при загрузке через onRehydrateStorage.

Лена прошла всё это в субботу. Сборка упала один раз — на неверном имени экспорта в exercises.js после её правки опечатки из девятой секции. Таблица назвала причину, починка заняла минуту.

И честный итог курса, в четырёх строках, как мы закрывали каждую секцию. Сильные стороны пакета: полный маршрут от документа до опубликованного сайта, восемь контрольных точек, шесть типов упражнений, достижения без сервера. Слабые: нужен Claude Code; содержание не улучшается, только упаковывается; README местами эскиз, а не контракт; прогресс живёт в одном браузере. Оценка Лены: 4/5 — «понедельник прошёл, студенты кликали». Одной строкой: сборка npm run build подтверждает готовность — всё остальное до неё было обещанием.

Компромисс: проверка по списку занимает полчаса, которые хочется сэкономить; но каждая ловушка в списке стоила кому-то сломанного показа, и полчаса дешевле.

Частые вопросы

Пак уже установлен. Как обновиться до свежей версии?

npx @dzhechkov/skills-edu-site@latest update. Без @latest команда update сверяет файлы с шаблонами той версии, которую ты запустил, и в реестр не ходит — устаревшая глобальная установка предложит только саму себя. Сначала можно посмотреть план: update --dry-run.

Я случайно удалил .skills-edu-site.json. Что делать?

Это манифест установки — его читают update, remove, list и doctor. Папка навыка на месте, но команды её не «видят». Проще всего переустановить: npx @dzhechkov/skills-edu-site init --force — манифест будет записан заново.

Обязательно ли ставить Keysarium?

Нет. Пакет работает сам по себе. Если Keysarium стоит, init найдёт его манифест .keysarium.json в корне проекта и сообщит об интеграции; появится возможность строить сайт из артефактов конвейера Casarium.

Сколько документов нужно, чтобы генератор согласился работать?

Минимум на три секции; максимум рекомендованный — двадцать; золотая середина — 5–15. Меньше трёх — предупреждение и просьба добавить содержания, больше двадцати — предложение сгруппировать или разбить на два курса.

Где править текст и вопросы уже сгенерированного сайта?

В файлах данных src/data: sections.js (секции и теория), exercises.js (упражнения и FAQ), quizQuestions.js (тесты и финальный тест), achievements.js (достижения). Компоненты только показывают данные; обязывающая схема — references/data-schemas.md.

Почему на GitHub Pages белая страница и 404 в консоли?

Почти всегда — base в vite.config.js: он должен быть равен имени репозитория со слешами с обеих сторон, например '/networks-101/'. Вторая причина — BrowserRouter вместо HashRouter, третья — не включённые Pages (deploy.yml с enablement: true включает их сам).

Где tailwind.config.js? Его нет в проекте.

И не должно быть: сайт использует TailwindCSS v4, где тема настраивается в CSS через директиву @theme в src/index.css, а свои утилиты — через @utility. Созданный вручную tailwind.config.js ничего не сделает.

Как удалить пак из скрипта без терминала?

npx @dzhechkov/skills-edu-site remove --force. Без --force при вводе не из терминала команда откажет с кодом 1 и ничего не удалит — это измерено и описано в README, чтобы автоматизация не видела ложный успех.

Бесплатный интерактивный курс · aicoding.space

Лицензии шрифтов

Onest

Copyright 2021 The Onest Project Authors (https://github.com/googlefonts/onest)

This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
https://scripts.sil.org/OFL


-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------

PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.

The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded, 
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.

DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.

"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).

"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).

"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.

"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.

PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:

1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.

2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.

3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.

4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.

5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.

TERMINATION
This license becomes null and void if any of the above conditions are
not met.

DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.

JetBrainsMono

Copyright 2020 The JetBrains Mono Project Authors (https://github.com/JetBrains/JetBrainsMono)

This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
https://scripts.sil.org/OFL


-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------

PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.

The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded, 
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.

DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.

"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).

"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).

"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.

"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.

PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:

1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.

2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.

3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.

4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.

5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.

TERMINATION
This license becomes null and void if any of the above conditions are
not met.

DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.