Imported from maximk247/tsarstva (
AGENTS.md). Install upstream withnpx skills add maximk247/tsarstva. Copyright stays with the author.
AGENTS.md — Чтение Царств
Что это
Статическая читалка книг 1–4 Царств (1sm, 2sm, 1kgs, 2kgs), 1–2 Паралипоменон (1ch, 2ch) и ветхозаветных книг, на которые есть параллельные места (gn, ex, dt, js, ezr, ne, ps, is, jr). Деплоится на Vercel как next export. Монорепо на Bun workspaces.
Структура
/
├── data/ # @tsarstva/data — данные и утилиты (Node/server)
│ ├── src/
│ │ ├── types.ts # Только типы: BookMeta, CrossRef, SearchVerse и пр.
│ │ ├── books.ts # Списки книг и секции: READER_BOOKS, READER_BOOK_SECTIONS
│ │ ├── index.ts # Публичный клиентский API ("@tsarstva/data")
│ │ ├── bibleMeta.ts # getBook, getChapterCount, formatRef, getBookName
│ │ └── server/ # server-only код — "@tsarstva/data/server"
│ │ ├── chapters.ts # getChapter, getVerseText, getVerseRange (readFileSync + кэш)
│ │ ├── crossRefs.ts # getParallelsForVerse, getChaptersWithParallels, getChapterParallels
│ │ └── index.ts # публичный серверный API
│ └── json/
│ ├── bible/index.json # метаданные всех книг
│ ├── bible/books/ # тексты глав по книгам
│ └── cross-refs/manual.json # ручная разметка параллелей
│
└── frontend/ # @tsarstva/frontend — Next.js 16, React 19, Tailwind 4
└── src/
├── app/ # App Router: page.tsx — thin re-exports из modules/
├── modules/
│ ├── home/ # pages/, components/
│ └── reader/ # pages/, components/, hooks/, constants/, utils/
├── features/
│ ├── navigate-chapter/ # BookSelector, ChapterNav
│ ├── reading-progress/ # прогресс чтения
│ └── theme-toggle/ # ThemeToggle
├── entities/
│ ├── verse/ # VerseItem
│ └── cross-ref/ # ParallelCard
└── shared/
├── components/ # BracketedText и пр.
├── hooks/ # useDebouncedValue и пр.
├── utils/ # cn() (clsx + tailwind-merge), dom.ts
└── configs/theme-provider.tsx
Архитектура — ключевые паттерны
Статический экспорт. next.config.ts задаёт output: "export". Все данные читаются на этапе сборки через Server Components — клиент не делает запросов к файловой системе.
Предвычисление параллелей. ReaderPage (Server Component) на этапе сборки:
- Читает все стихи главы через
getChapter(book, chapter) - Вызывает
getChapterParallels→ получает Map<verse, CrossRef[]> - Для каждого ref вызывает
getVerseRangeиformatRef→ формируетPrecomputedParallel[] - Передаёт
parallelsMap: Record<number, PrecomputedParallel[]>как пропс в Client ComponentReaderLayout
Важно: getChapter/getVerseRange используют readFileSync — их можно вызывать только в Server Components (через @tsarstva/data/server). В клиентских компонентах используй только @tsarstva/data (без /server).
Ref формат. Ключи в manual.json и индексах: "book:chapter:verse" → "1sm:1:1". Читаемые книги живут в READER_BOOKS (Царства, Паралипоменон плюс ВЗ-книги с параллелями: gn, ex, dt, js, ezr, ne, ps, is, jr); разделы сайдбара — в READER_BOOK_SECTIONS. Псалтирь хранится в синодальной (LXX) нумерации. Новозаветные отсылки (lk) остаются в панели параллелей, но не добавляются в библиотеку чтения. В читалку добавляются только книги, участвующие в параллелях.
Визуализация параллелей в тексте. В обычном состоянии стих с параллелью помечается маленькой точкой; не заменяй её бейджем или постоянной заливкой. Если параллель покрывает несколько стихов исходного текста, диапазон задаётся через fromEnd в manual.json и подсвечивается только когда пользователь выбирает связанный стих. Подсветка диапазона должна идти сплошным блоком без внутренних разделителей; активная заливка не должна съедать внешнюю рамку диапазона.
Когда широкая параллель из manual.json попадает на стих, у которого есть своя точечная параллель, не удаляй широкий ref из данных ради панели. Разводи отображение в UI: панель может показывать только точечные refs активного стиха, а подсветка основного текста должна сохранять широкий исходный диапазон.
Тексты Библии. Локальные JSON можно обновить из JustBible API:
cd data && bun run sync:justbible
Архитектура компонентов (FSD-подобная): app → modules → features → entities → shared. Импорты только вниз по слоям. Для внутренних папок используй словарь как в tyres-frontend: components, pages, hooks, constants, utils, configs, types. В React-коде use... файлы клади в hooks. Не заводи model, lib, config, composables.
Навигация по книгам. Desktop-список книг живёт в modules/reader/components/sidebar/SidebarBookNav, мобильный горизонтальный список — в features/navigate-chapter/components/BookSelector. При баге "сайдбара" или списка книг сначала уточняй/проверяй нужную поверхность и не откатывай фикс другой поверхности, если он не конфликтует. BookSelector должен реагировать на смену currentBook, а не на смену главы.
Поиск. Основная выдача поиска открывается inline в центральной области ридера, а не уводит пользователя на отдельную страницу. /search сохраняется как прямой отдельный маршрут. Клиентский поиск (и в ридере, и на /search) читает статический frontend/public/search-index.<hash>.json, который генерируется перед dev/build скриптом фронтенда вместе с константой пути features/word-search/constants/searchIndexPath.ts (оба в .gitignore). Файл индекса хранится компактно (book → chapter → verse → text, без повторяющихся имён полей); плоский список с label и названиями книг клиент восстанавливает из метаданных в useSearchIndex. Не передавай индекс с сервера пропсами — он сериализуется в HTML страницы целиком.
Команды
# Dev-сервер (из корня монорепо)
bun run dev
# Сборка фронтенда
cd frontend && bun run build
# Установка зависимостей
bun install
Добавление параллелей
Параллели живут в data/json/cross-refs/manual.json. Формат записи:
{
"from": "1kgs:3:5",
"fromEnd": 6,
"to": "2ch:1:7",
"toEnd": 12,
"theme": "same_event",
"note": "Описание параллели",
"source": "Крокетт, Part I, §18(2)"
}
fromEnd необязателен и нужен только когда в основном тексте параллель охватывает диапазон стихов; формат такой же, как у toEnd.
Темы: same_event, textual_reference, textual_parallel, narrative_parallel, fulfillment, prophecy, theological, genealogy, historical_context, tsk.
same_event связывает два повествования об одном историческом материале, даже если одно из них короче или добавляет детали. textual_reference означает направленную цитату, отсылку, аллюзию или словесное эхо; textual_parallel — совпадающий текст, формулу, список или сводку без доказуемого направления зависимости; narrative_parallel — разные, но сходно построенные эпизоды. historical_context связывает тексты одного периода, которые не пересказывают одно событие. source хранит проверяемое происхождение записи и не заменяет пояснение в note.
Для fulfillment запись односторонняя: from — место, где пророчество уже исполнилось, to — место пророчества/обетования. Такие refs не индексируются обратно и не должны показывать точку на пророческом стихе до исполнения.
После изменения manual.json — пересборка не нужна для dev (файл читается при старте). Для production — bun run build.
Деплой
Vercel, конфиг в vercel.json:
buildCommand:bun run --filter @tsarstva/frontend buildoutputDirectory:frontend/outinstallCommand:bun install --frozen-lockfile- Фреймворк:
null(не детектить Next.js автоматически — статический экспорт в папку out)
Стиль кода
- TypeScript, strict
- Tailwind 4 (postcss плагин
@tailwindcss/postcss) cn()из@/shared/utils/cnдля условных классов- Цветовая схема: stone + amber-900 акценты,
#FAF9F7фон - При точечных визуальных правках по скриншоту меняй только указанную поверхность/состояние. Не меняй палитру, глобальные токены или соседние компоненты, если пользователь просит только шрифт, цвет выделения, отступы или радиус конкретного элемента.
"use client"только там, где нужен state/effect (ReaderLayout, Sidebar, и пр.)
Коммиты
Без Co-Authored-By строк.
Ретроспектива сессии
Перед финальным ответом после длинной или существенной сессии проверь research/codex-retrospective.md: кратко оцени, нужно ли улучшить инструкции, скрипты, данные или рабочие заметки. Если пользователь пишет кодовое слово Ретроспектива, запусти этот протокол явно. Вноси изменения только когда есть конкретная повторяемая проблема или явно полезное уточнение; не раздувай правила ради правил.