# Архитектура шаблона документов Scientia ## Системный контекст ```text ┌────────────────────┐ редактирует ┌──────────────────────────────────┐ │ Автор документа │ ────────────▶ │ main.typ + chapters/ + assets/ │ └─────────┬──────────┘ └────────────────┬─────────────────┘ │ читает │ один import фасада ▼ ▼ ┌────────────────────┐ ┌──────────────────────────────────┐ │ docs/ │ │ .template/lib/ │ │ инструкции и │ │ domain → application → │ │ примеры │ │ infrastructure → presentation │ └────────────────────┘ └────────────────┬─────────────────┘ │ ┌────────────────────┐ условный import │ PDF │ .private/ │ ────────────▶ main.typ ─────────┤ │ PNG + settings.typ │ один boolean ▼ ┌────────────────────┐ │ document.pdf │ └────────────────────┘ ┌────────────────────┐ сопровождает ┌─────────────────────────────────┐ │ Разработчик │ ─────────────▶ │ .template/development/ │ │ шаблона │ │ docs + modules + tests + tools │ └────────────────────┘ └─────────────────────────────────┘ ``` Публичная и developer-документация физически разделены. Автору не требуется открывать `.template/`, а разработчик не использует `docs/` как описание внутренних контрактов. ## Архитектурные принципы 1. **Один файл ежедневной настройки**. Компания, режим, metadata и `#include` находятся в `main.typ`. 2. **Режим — значение, а не entrypoint**. `final`, `draft` и `clean-copy` являются вариантами одной переменной. 3. **Публичное видно**. Инструкции и примеры находятся в `docs/`, который VS Code не скрывает. 4. **Разработка скрыта**. Библиотека, tests, ADR и tools находятся в `.template/`. 5. **Пример является исполняемой документацией**. Один исходник одновременно обучает, компилируется в CI и устанавливается как тип документа. 6. **Важные параметры явные**. Даже отключённые значения показаны как `none`, `false` или `()`. 7. **Приватное не отслеживается**. Вся папка `.private/`, включая настройки offsets, исключена из Git; tracked placeholders никогда не заменяются реальными файлами. 8. **Один переключатель**. При `false` условный import не читает `.private`; при `true` обычный preview и обычная build task используют её настройки. 9. **Фасад скрывает реализацию**. Пользователь импортирует только `/.template/lib/index.typ`. 10. **Domain не зависит от layout**. Presentation использует domain contracts, но обратной зависимости нет. ## Целевая структура ```text README.md # короткий маршрут автора main.typ # единственная точка входа и настройки chapters/ # пользовательский текст assets/ # изображения, данные и bibliography docs/ # публичная документация ├── README.md ├── documents.md ├── formatting.md ├── vscode.md ├── git.md ├── private-assets.md ├── writing-style.md ├── troubleshooting.md └── examples/ ├── README.md ├── private/ │ └── settings.typ ├── documents/ │ ├── report/ │ ├── letter/ │ ├── commercial-offer/ │ └── contract/ └── formatting/ .vscode/ # tracked workspace configuration ├── extensions.json ├── settings.json └── tasks.json .private/ # ignored settings, private media и backups ├── settings.typ ├── executors/ ├── scientia/ ├── technology/ └── too/ .template/ # скрытая реализация ├── lib/ ├── companies/ └── development/ ├── docs/ ├── modules/ ├── tests/ ├── tools/ └── vscode/ ``` В чистом fork каталог `.private/` не обязателен. Обычная компиляция использует placeholders из `.template/lib/assets/placeholders/`. ## Компоненты | Компонент | Ответственность | Публичный интерфейс | |-----------|-----------------|---------------------| | Рабочее пространство автора | Один входной файл, главы и ресурсы | `main.typ`, `chapters/`, `assets/` | | Публичная документация | Обучение без знания реализации | `README.md`, `docs/*.md` | | Публичные примеры | Исполняемые примеры и источники выбора типа | `docs/examples/**/main.typ` | | VS Code workspace | Рекомендации, автосохранение и задачи | `.vscode/*.json` | | Публичный фасад | Единственный пользовательский Typst import | `.template/lib/index.typ` | | Document Domain | Профиль, context и render options | `document-profile()`, `render-options()` | | Company Domain | Реквизиты и firm resources | `company-profile()` | | Parties Domain | Адресаты, подписанты и стороны | `recipient()`, `signer()`, `party()` | | Attachments Domain | Порядок и идентичность приложений | `attachment()`, `attachment-set()` | | Render Application | Resolve, normalize, validate, render | `render-document()` | | Company Adapter | JSON profiles и resource overrides | `load-company()` | | Presentation | Foundation, components и четыре renderer | `profiles.report/letter/commercial_offer/contract` | | Employee/Private Adapter | Справочник сотрудников, private media и offsets | `report-executor()`, `private-company-media()` | | Test Harness | Static, compile, semantic и visual gates | `run-tests.py` | ## Data flow ### Обычная сборка 1. Автор выбирает `company-id` и `document-mode` в `main.typ`. 2. `main.typ` создаёт profile, company overrides, bibliography и attachments. 3. `#show: document.with(...)` передаёт последующие `#include` как тело документа. 4. Facade вызывает application use case. 5. Application разрешает компанию, нормализует и валидирует profile metadata. 6. Renderer применяет foundation, нумерацию, media policy и компонует страницы. 7. При `none` для подписи или печати final renderer использует круг или крест. 8. Typst создаёт `document.pdf`. ### Выбор типа документа 1. Задача VS Code получает `report`, `letter`, `commercial-offer` или `contract`. 2. Tool сохраняет текущие `main.typ` и `chapters/` в `.private/starter-backups//`. 3. Tool копирует соответствующий публичный пример из `docs/examples/documents/`. 4. Автор проверяет явно перечисленные параметры нового `main.typ`. ### Приватная сборка 1. Пользователь копирует готовую папку `.private` с `settings.typ` и PNG. 2. В `main.typ` значение `use-private-assets` меняется с `false` на `true`. 3. Условный import загружает `.private/settings.typ`. 4. Adapter сопоставляет публичный идентификатор сотрудника с фиксированным именем PNG и применяет private offset. 5. Отсутствующая или отключённая запись возвращает `none`; строка подписи остаётся пустой. 6. Обычная build task и Tinymist preview используют один и тот же `main.typ`. ## Ключевые интерфейсы ```typst // Единственный пользовательский вход. #let company-id = "scientia" // scientia | technology | too #let document-mode = "final" // final | draft | clean-copy #let use-private-assets = false // true, если скопирована .private ``` ```typst // Порядок и состав глав видны внизу main.typ. #include "chapters/00-introduction.typ" #pagebreak() #include "chapters/10-main.typ" ``` ```typst // Отсутствующая .private не читается при false. #let private-settings = if use-private-assets { import "/.private/settings.typ": settings settings } else { empty-private-settings } ``` ```typst #show: document.with( company: company, profile: profiles.report(..), options: ( mode: document-mode, watermark: if document-mode == "draft" { "ЧЕРНОВИК" } else { none }, media-policy: if document-mode == "final" { "placeholder" } else { "reserve-space" }, diagnostics: true, ), ) ``` ## Публичная и developer-документация | Слой | Расположение | Содержит | Не содержит | |------|--------------|----------|-------------| | Публичный | `README.md`, `docs/` | первый запуск, Git, типы, formatting, папку `.private`, troubleshooting | DDD, ADR, snapshots, migration internals | | Developer | `.template/development/docs/`, `modules/` | архитектуру, решения, границы и тестирование | обязательный маршрут обычного автора | Корневой README обязан ссылаться на каждую публичную тему. Developer README доступен одной отдельной ссылкой и не конкурирует с пользовательской навигацией. ## Публичные примеры | Пример | Обязательное покрытие | |--------|----------------------| | Report | титул, stage/volume, executors, includes, рисунки, таблица, formula, references, bibliography, appendix | | Letter | recipient, исходящий номер, основной текст, attachment list, signer | | Commercial offer | recipient, subject, price, tax, сроки, payment, scope appendix | | Contract | parties, representatives, sections, requisites, signing, appendix | | Formatting | варианты изображений, grid, простые/сложные таблицы, CSV, формулы, labels, lists | Публичный пример не должен ссылаться на скрытый developer asset. Допустим только импорт фасада `/.template/lib/index.typ`. ## Политика приватных ресурсов | Ресурс | Git | Поведение | |--------|-----|-----------| | Публичные реквизиты и логотипы | tracked | Загружаются из `.template/companies/` | | Векторные placeholders | tracked | Используются обычной сборкой по умолчанию | | `docs/examples/private/settings.typ` | tracked | Полный безопасный пример с `enabled: false` | | `.template/lib/infrastructure/employees.typ` | tracked | ФИО, обычные роли и фиксированные имена PNG | | `.private/` | ignored | Настройки доступности, offsets, реальные изображения и backups | Typst 0.15 не предоставляет проверки существования файла. Поэтому отсутствие подписи моделируется отсутствующей записью или `enabled: false`; только включённая запись создаёт private path. ## Технологические решения | Решение | Выбор | Обоснование | |---------|-------|-------------| | Compiler | Typst 0.15.1+ | Проверенный baseline и path type | | Архитектура | Модульный монолит | Один процесс сборки без лишней инфраструктуры | | Root entry | Один `main.typ` | Минимум выбора и все параметры в одном месте | | Режимы | Переменная `document-mode` | Варианты видны комментариями, нет дублирования файлов | | Public docs | Видимый `docs/` | Автор находит примеры в Explorer | | Examples | Executable documentation | Код и объяснение не расходятся | | Private activation | Literal `use-private-assets` + conditional import | Один понятный параметр, clean fork не читает отсутствующий каталог | | Internal boundary | `.template/` | Реализация и developer docs не мешают автору | | Testing | Python + Typst + Poppler | Контракты, PDF semantic и визуальный layout | ## Режимы отказа | Сбой | Влияние | Митигация | |------|---------|-----------| | Автор меняет режим не в `main.typ` | Ожидаемый вариант PDF не получается | README и comments показывают единственную переменную | | Пример использует скрытый developer asset | После очистки development сборка падает | Static path audit и compile всех public examples | | Выбор типа уничтожает текст | Потеря работы | Timestamp backup до удаления `chapters/` | | Подпись ещё не получена | Private compile падает при прямом path | Запись отсутствует или `enabled: false`, resolver возвращает `none` | | Включённая запись не имеет PNG | Ошибка Typst с точным path | Включать запись только после копирования PNG; troubleshooting | | Реальный файл попадает в Git | Утечка подписи | `.gitignore`, ignored `.private/`, no overwrite tracked placeholders | | Сложная таблица переполняет страницу | Нарушение layout | Formatting example, stress fixture, repeated header tests | | Публичная ссылка устарела | Автор теряет маршрут | Automated Markdown link audit | | Изменение Typst меняет layout | Тихая регрессия | Version gate и snapshot review | ## Вне области видимости v1 - Поддержка старых root-файлов `document.typ`, `draft.typ` и `clean-copy.typ`. - Автоматическое определение существования private files внутри Typst. - Хранение реальных подписей и печатей в Git или Git LFS. - Публикация внутренних VSIX в этом репозитории. - Юридическая экспертиза договора. - Научная верификация пользовательского содержания. - Генерация DOCX. - Публичная публикация в Typst Universe и open-source лицензирование.