Адаптивный тренажёр для подготовки к собеседованиям на Java/Kotlin backend. Вдохновлён Duolingo, но не инфантильный: это ежедневный тренажёр для инженеров.
Живая версия: https://olluorg.github.io/bkdojo/ (деплой на GitHub Pages из main)
Главная ценность — не «выучить факт», а уметь внятно объяснить его вслух на интервью. Поэтому открытые вопросы оценивает AI по рубрике, а не по совпадению ключевых слов, и после ответа начинается короткий диалог с уточняющими вопросами — как у живого интервьюера.
| Доменов | 5 — Java Core, Spring Boot, Databases, Message Brokers, System Design |
| Вопросов | 647 (java-core 315, spring-boot 122, databases 78, system-design 75, brokers 57) |
| Из них открытых | 95 — те, что оценивает AI по рубрике |
| Уроков | 120 |
| Терминов в словаре | 132 |
| Тестов | 418 в 73 файлах (bun test) |
Типы вопросов: single (один вариант), multiple (несколько), open
(развёрнутый ответ, оценивает AI по рубрике), плюс вопросы с кодом.
Ежедневный цикл
- Сегодня — одна миссия дня: фокус-тема, объяснение почему именно она (слабое место / самый большой разрыв до цели / накопились повторения), план из 5 шагов со ссылками на экраны и ожидаемый прирост готовности.
- Цель — грейд (Junior/Middle/Senior), опционально компания и дата собеседования с обратным отсчётом. Готовность считается относительно выбранного грейда: поднял цель — честно упала готовность.
- Разрывы до цели формулируются работой, а не процентом: «До Middle по Spring Boot: 4 темы».
- Стрик, история, аналитика прогресса, предсказания.
- Перенос прогресса между устройствами: выгрузка в JSON и импорт, который сливает данные с текущими, а не затирает их (плюс опциональная синхронизация).
Обучение
- Курсы и уроки с интерактивными виджетами (пошаговая симуляция
HashMapс рехешем и treeify, поколения GC с продвижением в old, ростArrayListи демо Integer-кеша), подсветкой терминов из словаря и практикой после урока. - Защита темы (
lesson defense) — чтобы закрыть урок, нужно пройти все его вопросы за один заход, без подсказок и пропусков. Галочка должна стоить дорого. - Словарь с дриллом терминов, закладки, «Мои ответы» — история своих формулировок открытых ответов.
Практика и оценка
- Диагностика уровня (placement) — работает без AI, на вариантах ответа.
- Адаптивный подбор: Elo-подобное обновление ability (1..5), подбор вопросов с «желаемой сложностью» +0.5 к текущему уровню. Сильного не гоняют по основам.
- Интервал повторений — Leitner, боксы 1/3/7/16/30 дней; неверный ответ сбрасывает в первый бокс.
- Слабые места — детекция концептов, которые стабильно не раскрываются в открытых ответах.
- Мок-интервью по домену: спред по сложностям с рампой easy → hard.
- Интервьюер-спарринг: после открытого ответа — до 3 уточняющих вопросов, нацеленных на самый слабый пробел; вердикт выносится по всему диалогу, и давление не может понизить уже полученную оценку.
- Голосовой ввод ответов с таймером и полосой темпа (30–90 секунд — интервьюерский формат ответа).
Скриншоты сняты с живой версии после диагностики уровня и одной сессии практики.
Каждый разбор написан как ответ на собеседовании: короткая версия, развёрнутая, частые ошибки и вероятные доп. вопросы.
Уроки — не просто текст: там, где статикой не объяснить, стоит интерактивный
виджет. Ниже — степпер HashMap.put на шаге, где таблица выросла с 4 до 8
бакетов и всё перехешировалось.
Открытый ответ можно надиктовать вслух — как на реальном интервью — и поправить расшифровку перед отправкой.
Прогресс — это не только проценты: есть прогноз «сколько дней до цели при текущем темпе», отдельно по каждому домену.
Ещё экраны: диагностика уровня, курсы, словарь, самопроверка, настройки
Диагностика — 10 вопросов с вариантами, без AI. Сложность подстраивается на ходу: ответил верно на лёгкий — следующий будет тяжелее. Здесь же задаётся цель.
Словарь открывается по мере роста уровня, а термины подсвечиваются прямо в текстах уроков.
Когда AI недоступен (не Chrome и облачная оценка не подключена), открытый ответ уходит в самопроверку по эталону — оценка всё равно идёт по пунктам рубрики.
Способ оценки выбирается в настройках; там же видно, что доступно прямо сейчас. Тёмная тема — на скриншоте.
Всё скрыто за абстракцией AnswerEvaluator; UI и слои ability/review знают
только про EvaluationResult / AnswerOutcome.
Цепочка (режим auto, настраивается в Settings):
Chrome Built-in AI (на устройстве) → LLM-прокси (если задан) → самопроверка
- Chrome AI — Prompt API, модель работает локально, ничего не уходит наружу.
- Сервер — micro-platform LLM proxy (
VITE_EVAL_ENDPOINT→/functions/llm). Ключа провайдера на серверах bkdojo нет: пользователь сам выбирает провайдера (OpenRouter / OpenAI / routerai.ru), модель и вводит свой ключ в настройках — всё хранится только в localStorage и уходит в прокси заголовкамиX-Provider-Base-Url/X-Provider-Key. - Самопроверка — всегда доступный бэкстоп: пользователь сам отмечает раскрытые пункты по эталону.
Балл считается из весов рубрики, а не берётся у модели на веру — одинаково для
локального и облачного пути (buildEvaluationPrompt + parseEvaluation).
Совпадение по ключевым словам (RuleBasedFallbackEvaluator) — не продуктовый
механизм, он остаётся только для тестов и аварийного режима.
Нужен Bun.
bun install
bun run dev # http://localhost:5173Всё остальное:
bun test # 418 тестов
bun run typecheck # tsc --noEmit
bun run build # typecheck + сборка в dist/
bun run start # раздать dist/ одним Bun-процессом (server/prod.ts)Приложение работает без всякой настройки: прогресс хранится локально
(localStorage + IndexedDB), бэкенда и авторизации нет. .env нужен только чтобы
включить серверную оценку или синхронизацию — см. .env.example.
Chrome-first: полная функциональность гарантируется только в Google Chrome Desktop (из-за Built-in AI и Web Speech API). В других браузерах открытые ответы уходят в самопроверку или в облачный оценщик.
src/
app/ — оболочка, hash-роутер, тема
components/ — переиспользуемый UI (карточки вопросов, ридер урока, виджеты)
features/ — экраны: today, courses, lessons, practice, review, interview,
glossary, stats, history, bookmarks, answers, settings, onboarding
domain/ — вся логика, чистый TypeScript без React
ability/ Elo-обновление уровня
selection/ адаптивный подбор, план дня, микс типов, placement
review/ интервальное повторение, детекция слабых мест
evaluation/ цепочка оценщиков, промпты, разбор ответа модели
interview/ мок-интервью и цикл уточняющих вопросов
goal/ цель, разрывы до цели
lesson/ защита темы, корректирующие раунды, follow-up'ы
progress/ mastery, стрик, аналитика, журнал событий
content/ загрузка и валидация контента
data/ — контент: questions/*.json, lessons/*.json, glossary.json
storage/ — localStorage + IndexedDB
sync/ — опциональная синхронизация через @ollu micro-platform
server/prod.ts — единственный серверный код: статика dist/
Принципы: модули маленькие, домен отделён от UI, глобального мутабельного состояния нет, алгоритмы подбора и скоринга покрыты тестами.
Инвариант, который делает оценщик заменяемым: единственные файлы, которые трогают
window / LanguageModel, — это domain/evaluation/aiCapability.ts и
ChromePromptEvaluator.ts. Подробности и рецепт добавления своего оценщика — в
EXTENDING.md.
Вопросы лежат в src/data/questions/<domain>.json и валидируются при загрузке
(domain/content/questionValidation.ts):
single— ровно одинcorrectOptionIdsmultiple— один или большеopen— непустаяrubric(AI оценивает каждый концепт отдельно)- у каждого вопроса обязателен
answerGuide
Невалидные вопросы отбрасываются (с предупреждением в dev), поэтому битый контент
не роняет приложение. bun test проверяет, что во встроенном контенте нет ошибок
валидации.
Vite вшивает в бандл только переменные с префиксом VITE_. Секретов на стороне
bkdojo нет.
| Переменная | Назначение |
|---|---|
VITE_EVAL_ENDPOINT |
URL LLM-прокси micro-platform. Пусто = серверная оценка выключена. |
VITE_BKDOJO_SYNC |
1 включает опциональную синхронизацию прогресса между устройствами. Без неё SDK синка вообще не попадает в бандл. |
VITE_OLLU_SERVER |
Адрес сервера синхронизации. |
VITE_OLLU_GOOGLE_CLIENT_ID |
Google OAuth client ID для входа в синк. Пусто = синк офлайн-only. |
VITE_BASE |
База путей для сборки (для GitHub Pages — /bkdojo/). |
- GitHub Pages — автоматически на каждый push в
main(.github/workflows/deploy-pages.yml). ЗначенияVITE_*берутся из repo Variables; они не секретные, поскольку всё равно попадают в клиентский бандл. - Docker / VPS —
docker compose up -d --build, либо Bun + systemd + reverse-proxy. Полная инструкция: DEPLOY.md.
Осознанные решения, а не недоделки:
- нет собственного бэкенда и базы данных;
- нет авторизации и платежей;
- прогресс живёт в браузере пользователя;
- ключей провайдеров на серверах bkdojo нет ни в каком виде;
- Chrome-first.
Требования к вкладу в проект и границы («не добавлять бэкенд, auth, платежи, не тащить крупные зависимости») зафиксированы в CLAUDE.md.
React 19 + TypeScript 6 + Vite 8, Bun как пакетный менеджер и тест-раннер.
Зависимостей в проде ровно три: react, react-dom и опциональный @ollu SDK
для синхронизации.










