This is the full developer documentation for FastOffice
# FastOffice
> Редакторы документов и таблиц внутри вашего продукта, под вашим именем и в вашем
контуре. Файлы Word и Excel остаются у вас: ни чужого облака, ни чужого интерфейса.
Microsoft Office ушёл из России, а договоры, сметы и акты никуда не делись: они по-прежнему живут в DOCX и XLSX. Замену продают целыми пакетами на рабочее место — но встроить её в свой продукт нельзя. **FastOffice продаёт именно это: документный и табличный процессоры внутрь чужого продукта**, под его именем и в его контуре. A4 работает с документами Word, F1 — с таблицами Excel.
## Чем это доказано
[Заголовок раздела «Чем это доказано»](#чем-это-доказано)
Ниже — не планы. Каждое число снято с установленных сборок, а не выписано из дорожной карты: при каждом изменении документации сборка сверяет их с кодом продуктов и не проходит, если они разошлись.
**28**команд документного сеанса — все принимает ядро выпуска
**203**операции табличного контракта; 185 из них принимает ядро последнего выпуска
**13**табличных форматов, включая XLSX, XLSB, XLSM, ODS и CSV
**1 000 000**строк открываются в браузере — прокрутите и увидите цену кадра
Смыслом документа владеет собственное ядро на Rust: оно разбирает файл, считает вёрстку и формулы, ведёт историю и записывает результат. Это не обёртка вокруг чужого редактора — [что это значит рядом с Excel](/f1/atlas/).
## Попробовать прямо здесь
[Заголовок раздела «Попробовать прямо здесь»](#попробовать-прямо-здесь)
Оба редактора открываются прямо на сайте, в браузере, без установки: это не запись экрана, а работающая программа. Документ и книга живут в вашей собственной кабинке — правки видите только вы. Если площадка в этот момент не поднята, на месте рамки будет снимок и подпись об этом.
Документ в A4
Договор на две страницы: заголовки, списки, таблица со сроками и суммой. Поправьте абзац — и увидите, как это выглядит внутри чужого приложения.
[Открыть живой редактор →](/a4/demo/)
Смета в F1
Таблица с формулами. Измените количество или цену — суммы, доли и оба итога пересчитает ядро, а не разметка страницы.
[Открыть живую таблицу →](/f1/demo/)
## Два движка, одна модель встраивания
[Заголовок раздела «Два движка, одна модель встраивания»](#два-движка-одна-модель-встраивания)
A4 — документы Word
Редактор и процессор документов: открывает файл Word, размечает страницы, даёт править, сохраняет и открывает заново — всё внутри вашего окна. Снаружи это 28 команд одного протокола, и другого пути к документу нет.
[Обзор A4](/a4/overview/) · [Быстрый старт](/a4/quickstart/) · [Справочник](/reference/a4/overview/)
F1 — таблицы Excel
Табличный процессор: формулы, история правок, чтение и сохранение книг. В память берётся только то, что нужно сейчас, поэтому большие книги открываются. Снаружи это один контракт операций — тот же самый и на рабочем месте, и в браузере, и на сервере: 203 операции описаны, 185 принимает ядро последнего выпуска.
[Обзор F1](/f1/overview/) · [Быстрый старт](/f1/quickstart/) · [Справочник](/reference/f1/overview/)
## Что это меняет для вашего продукта
[Заголовок раздела «Что это меняет для вашего продукта»](#что-это-меняет-для-вашего-продукта)
Ваш продукт, а не чужое окно
Пользователь открывает документ из привычной карточки. Редактор появляется в том же окне и под тем же брендом; новая версия возвращается в существующий контур хранения и аудита.
Один источник правды о документе
Модель, формулы, история и запись файла живут в ядре. Второго движка на стороне страницы нет — значит документ значит одно и то же на рабочем месте, в браузере и на сервере.
Отказ вместо тихой порчи
Устаревшая ревизия, защищённый лист или неподдержанная операция дают явный отказ с причиной — **до** того, как файл изменится. Документа, изменённого наполовину, не возникает.
Агент работает как одна сделка
Он ходит тем же путём, что и человек: посмотреть, составить план, показать предпросмотр, дождаться согласия, применить, сохранить и открыть заново, записать в аудит. Не щелчки по чужому интерфейсу и не правка файла в обход редактора.
## С чего начать: выберите, куда встраиваете
[Заголовок раздела «С чего начать: выберите, куда встраиваете»](#с-чего-начать-выберите-куда-встраиваете)
Три пути, и они разные с первого шага. Откройте свой — там всё по шагам, от «что взять из поставки» до «какой экран увидит пользователь». А если разбираться не хочется — [отдайте задание Клоду](/start/kit/), и он подключит редактор за вас.
В веб-приложение
Ваш продукт живёт в браузере. Редактор открывается прямо на вашей странице, файл остаётся в вашем контуре.
[Четыре шага →](/start/web/)
В настольное приложение
Учётная система, отраслевая платформа, надстройка над 1С. Ядро работает рядом отдельным процессом и говорит с вами строками JSON.
[Три шага →](/start/desktop/)
В мобильное приложение
Телефон и планшет: что работает сегодня, каким путём это делается и чего пока нет — без обещаний, которые нечем подтвердить.
[Рабочий путь →](/start/mobile/)
Сборки есть для macOS, Windows и Linux. Под Windows — обе архитектуры, x64 и arm64; под Linux пока только x86-64. Для macOS у F1 есть сборки и под Apple Silicon, и под Intel, у A4 сейчас выложена только под Apple Silicon. Продукт в активной работе, поэтому сборки мы отдаём по заявке и вместе с ними предупреждаем об ограничениях текущей версии. Что делать после получения — [установка A4](/a4/install/) и [установка F1](/f1/install/).
## Примеры кода
[Заголовок раздела «Примеры кода»](#примеры-кода)
Двадцать четыре проекта показаны целиком, файл за файлом: видно, сколько кода нужно написать для каждой задачи. В листах на месте ядра стоит дублёр, поэтому смотреть в них стоит на объём кода, а не на числа в ячейках — настоящий пересчёт живёт в [живой таблице](/f1/demo/) выше.
* [Живой пример A4: редактор в браузереработающий редактор документов прямо на странице и весь проект, которым он подключается](/a4/demo/)
* [Минимальный листсколько строк нужно, чтобы получить работающий лист](/f1/examples/minimal/)
* [Лист с лентойкак добавить нашу ленту, если она нужна](/f1/examples/ribbon/)
* [Встраивание в чужую страницучто будет с чужой страницей вокруг листа: её шрифт, фон и прокрутка](/f1/examples/embedded/)
* [Миллион строкцена трёх жестов на предельной книге — прокрутки, протяжки и масштаба: кадр p50 и p95 и обращения к ядру числами, а не обещанием](/f1/examples/stress/)
* [Перекрестьеподсветка строки и столбца активной ячейки: выключена по умолчанию и включается свойством](/f1/examples/crosshair/)
* [Только чтениекнига видна, но не правится: команды серые с причиной, а не спрятаны, и правка не начинается вовсе](/f1/examples/read-only/)
* [Свои заголовкиподписи строк и столбцов задаёт встраивающий, а ширина полосы считается по его тексту](/f1/examples/custom-headers/)
* [Лист без полос заголовковполосы прячутся одним свойством, и освободившееся место занимает сам лист](/f1/examples/no-headings/)
* [Свой пункт менючужой пункт в нашем меню: за чертой, под именем владельца и со своей командой — притвориться нашим он не может](/f1/examples/custom-menu/)
* [Своё сочетание клавишчужое сочетание срабатывает, а занятые листом (Ctrl+D, Ctrl+R, F2, Shift+Пробел) остаются за листом — отказ виден словами](/f1/examples/custom-shortcut/)
* [Свой языкязыка нет в наших трёх — его приносит хозяин страницы: подписи переводятся, непереведённое идёт запасной цепочкой, а не пропадает](/f1/examples/custom-locale/)
* [Перетащите книгу сюдачужой документ открывается байтами: оболочка доносит файл, разбор остаётся за швом контракта, а отказ приходит словами того, кто открывал — на витрине это заглушка, она берёт csv, tsv и txt, а на xlsx честно отказывает](/f1/examples/open-bytes/)
* [Двое в одном документедве рамки рядом: двигаете выделение в одной — во второй виден чужой курсор с именем; и прямо на экране написано, что видны только окна этого браузера](/f1/examples/presence/)
* [Помощники ведут листдо десяти внешних участников, каждый в своём процессе: ходит по своей колонке, выделяет участок и вписывает итог — намерениями через тот же канал; отказ в лишнем виден и ему, и человеку; не запустили — страница так и говорит](/f1/examples/driver/)
* [Своё событиедве точки расширения наружу: onCommand отдаёт любое намерение поверхности (наше и чужое), onGesture — цену закончившегося жеста числами](/f1/examples/custom-event/)
* [Гиперссылкассылка — свойство ячейки: одна операция ядра ставит её вместе с текстом, снимает, оставляя текст, и сама называет ссылки листа](/f1/examples/hyperlink/)
* [Найти и заменитьпоиск — один запрос, замена — два, и первый книгу не меняет: ядро вычисляет план правок, а пишет их та же дорога, что ввод](/f1/examples/find-replace/)
* [Примечанияшесть пунктов «Рецензирования» — одна операция контракта и поле intent; текст, номер и нормализованный адрес называет ядро](/f1/examples/annotations/)
* [Структура листавосемь действий — одна операция: оболочка называет диапазон, ось и действие, а уровни и что спрятать считает ядро](/f1/examples/outline/)
* [Разметка страницгде ляжет разрыв — единственный метод SDK, который спрашивает и ничего не меняет; саму печать делает хост, и это граница, а не пробел](/f1/examples/page-breaks/)
* [Разрешить изменение диапазоновшесть намерений одной операции: пароль уходит за шов и не возвращается — обратно приходит только способ защиты](/f1/examples/protected-ranges/)
* [Связи книгиформула считает по чужой книге: список связей, их состояние и адрес источника приходят от ядра, а открывает книгу хозяин окна](/f1/examples/workbook-links/)
* [Голова без экранакнига открывается, правится и читается из Node — без единого обращения к DOM](/f1/examples/headless/)
Все примеры по продуктам: [A4](/a4/examples/) и [F1](/f1/examples/).
## Куда идти дальше
[Заголовок раздела «Куда идти дальше»](#куда-идти-дальше)
[Что вы получаете](/platform/story/)Продуктовая формула: что вы приносите, что делает платформа, что получается на выходе.
[Функциональный паритет](/platform/parity/)Что означает паритет с Word и Excel и чем он подтверждается.
[Пилот и приёмка](/platform/pilot/)Шестинедельный план, что проверяется на каждом слое и по чему решают — берём или нет.
[AI-инструменты и MCP](/start/ai/)Подключить документацию к Claude, Cursor или ChatGPT как MCP-сервер.
[Связаться с нами](/start/contact/)Запросить пилот, попасть в бета-тестирование или задать вопрос по встраиванию.
# Страница не найдена
> Такого адреса на сайте нет. Ниже — точки входа в документацию FastOffice: продукты, справочники и способ связаться.
Такого адреса здесь нет. Возможно, страницу переименовали или ссылка пришла из старой переписки. Поиск вверху ищет по всей документации, а ниже — точки входа.
## Начать заново
[Заголовок раздела «Начать заново»](#начать-заново)
* [Что такое FastOffice](/start/intro/) — платформа за пять минут.
* [Как читать эту документацию](/start/how-to-read/) — маршруты чтения под разные задачи.
* [Глоссарий](/start/glossary/) — если непонятен термин.
## Продукты
[Заголовок раздела «Продукты»](#продукты)
* [A4 — редактор документов DOCX](/a4/overview/): [установка](/a4/install/), [быстрый старт](/a4/quickstart/), [справочник](/reference/a4/overview/).
* [F1 — табличный процессор XLSX](/f1/overview/): [установка](/f1/install/), [быстрый старт](/f1/quickstart/), [справочник](/reference/f1/overview/).
## Если оценивают платформу
[Заголовок раздела «Если оценивают платформу»](#если-оценивают-платформу)
* [Свой офис вместо Microsoft Office](/platform/replace-office/) — зачем это нужно и что даёт.
* [Риски и честные границы](/platform/risks/) — что пока не закрыто.
* [Пилот и приёмка](/platform/pilot/) — как проверить на своих документах.
## Не нашли нужного
[Заголовок раздела «Не нашли нужного»](#не-нашли-нужного)
Напишите на — подскажем, где это описано. Если сюда привела ссылка с самого сайта, сообщите её: [письмо с готовой темой](mailto:support@plantype.ru?subject=%D0%B4%D0%BE%D0%BA%D1%83%D0%BC%D0%B5%D0%BD%D1%82%D0%B0%D1%86%D0%B8%D1%8F).
# Архитектура: объекты и связи
> Семь объектов решения, граница ответственности между ядром на Rust и поверхностью редактора и правило, по которому решается, кто владеет операцией.
*Для первого понимания достаточно семи объектов. Каждый имеет одну ясную ответственность.*
```plaintext
[Пользователь / агент]
| действие
v
[Ваш продукт: бренд, права, карточка, процесс]
| встроить, открыть, сохранить, получать события
v
[Поверхность] <--> [Мост к приложению и контракт сеанса]
| типизированные команды
v
[Ядро на Rust]
модель → вёрстка → сцена → запись в файл
|
v
[Байты DOCX и хранилище]
```
| Объект | Что делает | Владелец |
| ----------------------------------- | ------------------------------------------------------------------ | ------------------------------- |
| Ваше приложение | Окно продукта, бренд, права, карточка документа и рабочий процесс. | Вы |
| Поверхность редактора (Web Surface) | Лента, страницы, ввод, выделение, диалоги и отклик на действия. | A4, настраивается под ваш бренд |
| Мост к приложению | Связывает ваше приложение и редактор; сам документ не хранит. | Общая граница |
| Сеанс документа | Жизненный цикл одного документа и его ревизии. | Контракт A4 |
| Ядро на Rust | Читает, моделирует, вычисляет, проверяет и записывает документ. | A4 |
| Хранилище | Исходный файл, версии, сроки хранения и журнал обращений. | Вы |
| Профиль возможностей | Доступные функции и ограничения конкретной поставки. | Согласуется |
Упрощающее правило
Один документ — один главный сеанс. Поверхность показывает документ и отправляет намерения; ядро решает, допустима ли операция и каким станет сохранённый файл.
*Это не дублирование. Rust и Web решают разные классы задач и соединены узким контрактом.*
| Ядро на Rust — процессор | Web Surface — поверхность |
| ---------------------------------------------------------------- | ----------------------------------------------------------------------------------- |
| Владеет главной моделью документа и его ревизией. | Показывает страницы, команды, выделение и состояния. |
| Разбирает DOCX и сохраняет поддержанную структуру. | Собирается как обычное веб-приложение и работает внутри WebView. |
| Считает вёрстку, строит печатную сцену и геометрию. | Получает готовую сцену и рисует то, что разрешено контрактом. |
| Проверяет команды, защиту, устаревшую ревизию и ограничения. | Превращает жест, щелчок или ввод в типизированное намерение. |
| Выполняет тяжёлые операции — второго движка на JavaScript нет. | Управляет фокусом, языком, темой, режимом чтения и событиями для вашего приложения. |
| Возвращает результат, предупреждение или явный отказ с причиной. | Не записывает DOCX напрямую и не подменяет собой документ. |
## Почему Rust
[Заголовок раздела «Почему Rust»](#почему-rust)
Документ — это не только видимый текст. Внутри DOCX находятся связи, стили, секции, таблицы, колонтитулы, рецензирование и объекты. Чтобы записать это без потерь, нужен один владелец смысла, строгие типы и одно и то же ядро для настольного, серверного и мобильного вариантов.
## Почему Web
[Заголовок раздела «Почему Web»](#почему-web)
Web-технологии позволяют встроить одну поверхность в Electron, WebView2, WKWebView, Android WebView или другой совместимый контейнер. Вам не нужно переписывать редактор под каждый набор родных элементов интерфейса, а внешний вид продукта остаётся вашим.
Тест границы
Если действие меняет смысл документа или то, что попадёт в файл, — решает ядро. Если действие меняет только показ и способ взаимодействия — этим управляют поверхность и ваше приложение.
## Дальше
[Заголовок раздела «Дальше»](#дальше)
* Что происходит от открытия документа до сохранения — [Жизненный цикл](/a4/lifecycle/).
* Что продукт делает с документом — [карта возможностей](/a4/capabilities/).
* Первый вызов на своей машине — [Быстрый старт](/a4/quickstart/).
# Что A4 умеет с документом
> Карта возможностей документной части по задачам: текст и таблицы, оформление и колонтитулы, поля и оглавления, рецензирование, рассылка, печать и защита — с вызовами, которыми это делается.
Сравнивать редакторы по числу кнопок бессмысленно, а «поддерживаем DOCX» ничего не обещает. Ниже — карта по задачам, ради которых документ вообще открывают: что A4 делает и каким вызовом. Список собран из самого процессора, а не из планов: он перечисляет 96 одноразовых команд по `--help`, и вместе с 28 командами сеанса это всё, что доступно снаружи.
## Пока документ открыт
[Заголовок раздела «Пока документ открыт»](#пока-документ-открыт)
Это делает сеанс: открыли файл, работаете, сохранили. Подробности — в [объектах запросов и ответов](/a4/envelope/).
| Задача | Чем делается |
| -------------------------------------------------------------------- | ------------------------------ |
| Правка текста: вставка, удаление, разрывы строк, объединение абзацев | `mutate` с видом правки |
| Выделение и его положение | `set-selection` |
| Отмена и повтор | `undo`, `redo` |
| Смысловая модель документа, вёрстка, страницы | `model`, `compose`, `paginate` |
| Примечания, исправления, стили | проекции `review` и `style` |
| Сохранение и контрольная точка | `save`, `save-checkpoint` |
## Оформление документа
[Заголовок раздела «Оформление документа»](#оформление-документа)
| Задача | Чем делается |
| ------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
| Колонтитулы: создать, связать с предыдущим разделом, отвязать, изменить, убрать | `header-footer-action-docx` |
| Разделы: ориентация страницы, поля, размер бумаги | `section-page-setup-action-docx` |
| Водяной знак с выбором текста, шрифта и размера, прочее оформление | `design-tools-docx` |
| Таблицы: копирование и вставка содержимого ячеек, работа с диапазоном строк и столбцов | `table-docx` |
| Списки и их проверка | `lists-proof-docx` |
| Изображения, фигуры и надписи: вставка, замена картинки, правка текста внутри фигуры, размер, стиль и выравнивание | девять вызовов, среди них `insert-image-object`, `insert-shape-object`, `replace-image-media`, `resize-drawing-object`, `distribute-drawing-objects` |
## Поля, оглавления, указатели
[Заголовок раздела «Поля, оглавления, указатели»](#поля-оглавления-указатели)
| Задача | Чем делается |
| ------------------------------------------------ | ---------------------------------------------------------------- |
| Оглавление: обновить или убрать | `update-toc-docx`, `remove-toc-docx` |
| Список иллюстраций по подписи | `update-table-of-figures-docx` |
| Библиография и стиль ссылок | `apply-bibliography-command-docx`, `set-bibliography-style-docx` |
| Указатель и таблица судебных источников | `apply-index-authorities-command-docx` |
| Поля: гиперссылки, коды и результаты, блокировка | `field-tools-docx` |
## Совместная работа над договором
[Заголовок раздела «Совместная работа над договором»](#совместная-работа-над-договором)
| Задача | Чем делается |
| ----------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- |
| Сличить две версии и получить документ с исправлениями | `review-compare-combine-docx --mode compare` |
| Свести правки нескольких рецензентов | `review-compare-combine-docx --mode combine` |
| Примечания: добавить, ответить, разрешить; принять или отклонить правку | `review-docx` с `--insert-comment`, `--reply-comment`, `--resolve-comment`, `--accept-change`, `--reject-change` |
| Состояние записи исправлений | `track-revisions-state` |
| Проверка правописания | `proofing-docx` |
| Рассылка по списку получателей из CSV | `mail-merge-business-docx` |
## Печать и выпуск
[Заголовок раздела «Печать и выпуск»](#печать-и-выпуск)
| Задача | Чем делается |
| ---------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Печать с выбором страниц, чётных или нечётных, порядка и режима показа исправлений | `print-docx` |
| Проверка комплекта печати и профилей устройств | `validate-print-bundle`, `validate-print-devices` |
| Выгрузка в PDF, в том числе со встроенными шрифтами | `export-pdf`, `export-pdf-embedded` |
| Остальные форматы — ODT, RTF, HTML, текст, EPUB, FB2 | [что с каким файлом можно](/a4/overview/#%D1%81-%D0%BA%D0%B0%D0%BA%D0%B8%D0%BC%D0%B8-%D1%84%D0%B0%D0%B9%D0%BB%D0%B0%D0%BC%D0%B8-%D1%80%D0%B0%D0%B1%D0%BE%D1%82%D0%B0%D0%B5%D1%82) |
## Безопасность файла
[Заголовок раздела «Безопасность файла»](#безопасность-файла)
| Задача | Чем делается |
| ------------------------------------------ | -------------------- |
| Посмотреть состав макросов, не запуская их | `inspect-vba` |
| Узнать, чем защищён документ | `inspect-protection` |
| Проверить готовность к архивному PDF/A-1b | `preflight-pdfa-1b` |
Как читать эту карту
Здесь перечислено то, что вызывается снаружи и потому проверяется на ваших файлах. Это не обещание полного совпадения с Word: где границы проходят сегодня, сказано в [Готовности и границах](/a4/status/), а что не закрыто — в [Рисках](/platform/risks/).
Полное описание каждого вызова с параметрами — в [справочнике](/reference/a4/overview/). Для таблиц то же самое собрано [картой возможностей рядом с Excel](/f1/atlas/).
Дальше по документной части: [как получить структуру документа](/a4/document-structure/), [определение попадания по координате](/a4/hit-test/), [рецепты интеграции](/a4/recipes/) и [история версий](/a4/changelog/).
# История версий A4
> Что появлялось, менялось и исправлялось в A4 от версии к версии.
Каждая строка ниже — изменение, которое видно в самом продукте. Версии идут от новых к старым.
Верхняя запись может опережать то, что выложено: список изменений пополняется по мере готовности выпуска, а выкладка идёт отдельным шагом. Какая версия доступна сейчас, видно в каталоге выпусков — на него ведёт страница установки.
Номер версии складывается из года, квартала и номера исправления внутри квартала: в `2026.3.4` это четвёртое исправление к выпуску третьего квартала 2026 года. Счёт исправлений начинается заново с каждым кварталом, поэтому за `2026.3.19` следует `2026.4.0`, а не `2026.3.20`.
В именах скачиваемых файлов к номеру добавлена ещё одна часть — сквозной номер сборки, например `2026.3.4.25`. На сравнение версий он не влияет и нужен, чтобы отличать файлы одной версии, собранные в разное время. Кандидаты и тестовые сборки помечены суффиксом: `2026.4.0-rc1`, `2026.4.0-beta1`.
Установленную версию приложение сверяет с той, что предлагает канал обновлений, и предлагает обновиться, когда выходит старшая. Проверить вручную можно пунктом меню «Проверить обновления…».
Всего выпусков: 64, счёт идёт с квартала `2023.2`.
## Выпуски 2026.3
[Заголовок раздела «Выпуски 2026.3»](#выпуски-20263)
### 2026.3.19
[Заголовок раздела «2026.3.19»](#2026319)
**Появилось**
* Ctrl+Backspace и Ctrl+Delete удаляют слово целиком.
* Размеченный при записи исправлений текст вставляется общим путём правки.
**Изменилось**
* Сохранение в ODT переносит свойства ячеек и строк таблицы, геометрию таблицы, объединение ячеек по вертикали, дробные проценты ширины, разрывы страниц, участки без проверки правописания, положение и кернинг знаков.
* Стрелки перехода по результатам поиска убраны из титульной полосы; назад по результатам ведёт Shift+Enter.
* Лента «Главная» приведена к макету.
**Исправлено**
* Масштаб знаков и отступ абзаца применяются и внутри ячеек таблиц.
* Явно заданная высота строки больше не переносится на соседнюю таблицу.
* Повторяющиеся пробелы сохраняются при переносе текста в ячейке.
* Списки внутри ячеек таблицы не теряются при повторном открытии ODT.
* Смена регистра доходит до файла, а не остаётся только на экране.
* Заливка абзаца рисуется одной полосой вместо двух.
### 2026.3.18
[Заголовок раздела «2026.3.18»](#2026318)
**Появилось**
* Плотные документы средней величины отрисовываются по частям: страница появляется, не дожидаясь остального документа.
**Исправлено**
* Встроенные картинки и объекты переживают разделение и слияние абзацев, сохранение и повторное открытие.
* Закладки переживают отмену разделения абзаца и последующее сохранение.
* Правка абзацев с письмом справа налево ставит контрольную точку, и её можно отменить.
* Отказ при открытии показывается поверх окна работы с файлом, а не прячется за ним.
* Окно «Файл изменился на диске» приведено к общему виду остальных окон.
* Принятое форматирование рисуется под живым выделением, а не поверх него.
### 2026.3.17
[Заголовок раздела «2026.3.17»](#2026317)
**Появилось**
* Возврат к последней точке правки.
* Переход по результатам поиска и обход плавающих объектов с клавиатуры.
* Смена регистра выделенного текста по Shift+F3 и через галерею «Регистр».
* Ctrl+Z сразу после автозамены отменяет только автозамену, а не набранное слово.
* Поле даты удаляется как одно целое; текст вокруг него переживает набор, удаление и отмену.
* Правка списка получателей при слиянии писем.
**Изменилось**
* Сохранение в ODT переносит цвет текста, подчёркивание с цветом, зачёркивание, выделение цветом, ПРОПИСНЫЕ и малые прописные, подстрочный и надстрочный знак, сочетания начертаний.
* Открытие EPUB и FB2 пропускает картинки и разбирает таблицы вместо отказа от всего файла.
* Концевые сноски встают в конец документа и в конец раздела.
**Исправлено**
* Новый документ создаётся с одним размером шрифта: буквы больше не прыгают, первые набранные знаки не пропадают.
* Строка перестала менять высоту при наборе.
### 2026.3.16
[Заголовок раздела «2026.3.16»](#2026316)
**Появилось**
* Вставка полей слияния общим путём правки; слияние писем переведено на общий механизм работы с файлом.
* Сохранение в EPUB и FB2 переносит сноски и концевые сноски отдельным разделом, заголовки, нумерованные списки, картинки и адреса ссылок.
* Сохранение в HTML, RTF и EPUB переносит многоколоночную вёрстку, вертикальное выравнивание раздела, начальный номер страницы и отступы колонтитулов.
**Изменилось**
* Составные поля Word и поле времени пересчитываются при открытии документа.
* Время пометки исправления идёт за последней правкой, как в Word.
**Исправлено**
* Удаление сноски при включённой записи исправлений помечает её, а не вырезает молча.
* Отказ выделения объясняется человеку понятной фразой.
* Выделить всё в документе с пустым телом больше не приводит к сбою.
### 2026.3.15
[Заголовок раздела «2026.3.15»](#2026315)
**Исправлено**
* Вставка внутрь размеченного текста и набор на границе удаления при включённой записи исправлений.
* Примечания к диапазону внутри ячеек таблицы разделяются так же, как в Word.
* Длинные примечания листаются постранично и не выходят за лист.
* Enter поверх выделения ведёт себя как в Word.
* Быстрая замена содержимого ячейки таблицы и отмена набора нелатинского текста в ячейке.
* Неактивные кнопки перестали отзываться на нажатие.
### 2026.3.14
[Заголовок раздела «2026.3.14»](#2026314)
**Исправлено**
* Примечание к диапазону переживает сохранение, разделение абзаца и правку рядом с ним.
* Быстрый Enter больше не накапливает отставание и не глушит отрисовку.
* Набор сразу после закладки, правка пустого абзаца и форматирование через разрыв страницы.
* Меню вставки, галерея объектов и палитра цвета шрифта больше не пропускают нажатие на документ под собой.
* Правка переживает повторное открытие того же файла.
### 2026.3.13
[Заголовок раздела «2026.3.13»](#2026313)
**Появилось**
* Сохранение в HTML, RTF, ODT, EPUB и FB2 переносит оформление: выравнивание, отступы и интервалы абзаца, цвет текста и выделение цветом, разрядку, ПРОПИСНЫЕ и малые прописные, подстрочный и надстрочный знак, контур, тень, приподнятый и утопленный текст.
* Таблицы в этих форматах сохраняют ширину столбцов и всей таблицы, высоту строк, границы и заливку ячеек, вертикальное выравнивание.
**Исправлено**
* Пунктирное, штриховое и волнистое подчёркивание читаемо при стопроцентном масштабе и рисуется своим стилем, а не сплошной линией.
### 2026.3.12
[Заголовок раздела «2026.3.12»](#2026312)
**Появилось**
* Вставка даты обновляемым полем: поле переживает сохранение и обновляется при открытии документа.
* Кнопка списка умеет снять список, как в Word.
**Изменилось**
* При включённой записи исправлений помечаются также вставка и удаление строк и столбцов таблицы, свойства раздела, поля страницы и замена текста; исправление в ячейке подписывается именем рецензента.
* Ответ на примечание получил собственную личность: удаление примечания уносит его записи целиком, а удаление вопроса не оставляет ответ без родителя.
* У примечания появились постоянный номер и время.
* Под листом появилась тень бумаги вместо нарисованной рамки.
**Исправлено**
* Ложное предупреждение о несохранённых изменениях снято.
* Шрифт документа больше не подменяется на глазах при запуске.
* Окно перестало менять ширину на каждой отрисовке.
* Значение «Авто» у цвета шрифта снимает цвет и не прерывает правку.
* Нумерованный список получает все девять уровней, Tab уводит на следующий.
### 2026.3.11
[Заголовок раздела «2026.3.11»](#2026311)
**Появилось**
* При включённой записи исправлений помечаются начертание, шрифт, цвет, размер, ПРОПИСНЫЕ, зачёркивание, выделение цветом, выравнивание, отступ и интервал абзаца, стиль абзаца и заливка.
* У исправления два времени: местное и всемирное.
**Изменилось**
* Одна команда форматирования даёт одно исправление, а не несколько.
**Исправлено**
* Приёмка удалённого знака абзаца склеивает абзацы так же, как в Word.
* Страницы длинных документов ложатся как в Word: уровень нумерации больше не перебивает отступ, заданный стилем.
* Знаки символьных шрифтов восстанавливаются как обычный текст.
* Малые прописные и рамка абзаца доходят до колонтитула.
### 2026.3.10
[Заголовок раздела «2026.3.10»](#2026310)
**Появилось**
* Режим записи исправлений: набор одновременно печатает и помечает, как в Word; подпись исправления несёт имя пользователя и настоящее время.
* Шаблон «Рецепт» и карточки с содержимым на стартовом экране.
**Изменилось**
* Сочетания клавиш навигации не зависят от раскладки клавиатуры.
* Окно расширенного поиска и замены и панель навигации переведены.
* Колонтитул переносится по словам настоящими метриками шрифта.
**Исправлено**
* Кнопка «Исправления» больше не уводит документ с экрана.
* Кнопки размера шрифта видят выделение, сделанное с клавиатуры.
* Вставка поверх выделенного целиком абзаца.
* Список в новом документе больше не блокирует сохранение.
* Молчаливая потеря знака при записи исправлений заменена названным отказом.
* Набор в абзаце с полем формы и в абзаце с табуляцией больше не запрещён.
### 2026.3.9
[Заголовок раздела «2026.3.9»](#202639)
**Появилось**
* Окно «Перейти» по сочетаниям клавиш Word: закрывается Escape, не зависит от раскладки.
* Переход на предыдущую и следующую страницу.
* Вставка эмодзи тем же путём, каким её делает Word.
**Изменилось**
* Отрисовка стала быстрее: убраны лишние пересчёты на каждой перерисовке.
* Контрольная точка сохраняет ссылку на сноску, и абзац со сноской остаётся доступным для правки.
**Исправлено**
* Выделение протяжкой дотягивается до слов, как в Word.
* Решённое примечание больше не подсвечивает текст; карточка примечания растёт под объём текста.
* Полужирное начертание для сложных письменностей больше не делает полужирной латиницу.
* Переключение форматирования работает, когда каретка стоит внутри слова.
* PageUp снова прокручивает экран.
* Набор в переносимом абзаце больше не рисует текст поверх текста.
* Вставка символа из диалога больше не ломает отмену и положение каретки.
* Окно «Добавить название» закрывается по Escape при любом фокусе.
### 2026.3.8
[Заголовок раздела «2026.3.8»](#202638)
**Появилось**
* Переход в начало и в конец документа.
* Заливка абзаца с выбором цвета, включая произвольный.
**Исправлено**
* Вставка из буфера действительно вставляет, а не рапортует об успехе.
* Вырезание удаляет текст, а не только копирует его.
* Удаление выделенного больше не стирает вдвое больше выделенного.
* Shift и щелчок расширяют выделение, а не схлопывают его.
* Сочетания перемещения каретки на macOS доходят до документа.
* Tab работает и в начале абзаца, и в середине текста.
* Причина отказа доходит до человека вместо молчания или общей фразы.
* Экранный диктор больше не читает «недоступно» о работающей кнопке.
* Два документа с переносами или с примечаниями больше не пишутся в один общий файл.
* Диаграмма рисует оси и подписи категорий.
### 2026.3.7
[Заголовок раздела «2026.3.7»](#202637)
**Появилось**
* Примечания на полотне документа: карточки на полях, ответы, прокрутка длинных обсуждений, отметка «Нравится».
* Свойства таблицы как в Word: границы и заливка, обтекание текстом, положение на странице, альтернативный текст, преобразование таблицы в текст, параметры сортировки.
**Исправлено**
* В поле ответа на примечание работают Enter, вставка, копирование, вырезание и «Выделить всё».
* Черновик ответа не теряется при переключении окна.
* Один отказ ядра больше не запирает форматирование до конца сеанса.
* Многоуровневый список больше не ломает остальные действия.
* Предупреждение «Файл изменился на диске» больше не всплывает на документ, которого никто не трогал.
* Кнопки правки не активны там, где документ править нельзя.
* Зачёркивание и выделение цветом работают там же, где полужирное начертание.
### 2026.3.6
[Заголовок раздела «2026.3.6»](#202636)
**Появилось**
* Помощница поддержки: обращение заводится прямо из разговора, вместе с документом, о котором идёт речь, и с выбором того, что уйдёт в отчёте.
**Изменилось**
* Первая отрисовка больших документов ограничена по времени.
* Отмена в открытом сеансе идёт постоянным путём и переживает перезапуск.
**Исправлено**
* Список, созданный в новом документе, доходит до файла.
* Причины недоступности действий с таблицей называются словами.
* Обращение из формы уходит вместе с документом.
### 2026.3.5
[Заголовок раздела «2026.3.5»](#202635)
**Появилось**
* Переход к следующему и предыдущему слову по сочетанию клавиш.
* Мягкий перенос по штатному сочетанию клавиш Word.
* Смена регистра выделенного текста.
**Изменилось**
* Мягкий перенос и неразрывный дефис записываются на диск теми же элементами, что пишет Word.
* Списки, выделение цветом и зачёркивание идут общим путём правки.
**Исправлено**
* Собственная запись в файл больше не выглядит как правка со стороны.
### 2026.3.4
[Заголовок раздела «2026.3.4»](#202634)
**Появилось**
* Неразрывный дефис по штатному сочетанию клавиш Word.
* Переход в конец видимой строки как в Word.
**Изменилось**
* Заливка и границы абзаца идут общим путём правки.
**Исправлено**
* Обновление на macOS показывает ход загрузки, вызывается из меню и говорит на шести языках.
* Номер страницы в колонтитуле больше не выключает правку документа.
* В сообщении об ошибке — понятная фраза, а не внутренний текст.
### 2026.3.3
[Заголовок раздела «2026.3.3»](#202633)
**Появилось**
* Обращение в поддержку работает в любой установке продукта.
**Исправлено**
* Выравнивание ширины столбцов срабатывает и там, где ширина ячейки не задана, и не меняет таблицу при повторном применении.
* Щелчок по перенесённой строке ставит каретку туда же, куда её ставит Word.
* Пакет для Linux больше не конфликтует с F1 на одной машине.
### 2026.3.2
[Заголовок раздела «2026.3.2»](#202632)
**Изменилось**
* Отмена и повтор стали единой историей: действия с таблицами, направление текста в ячейке, подстрочный и надстрочный знак идут тем же путём, что и остальная правка.
* Правка нескольких абзацев отменяется целиком, а не по частям.
**Исправлено**
* Повтор структурного изменения таблицы возвращает именно то, что было отменено.
### 2026.3.1
[Заголовок раздела «2026.3.1»](#202631)
**Появилось**
* Значок приложения во всех размерах и для всех экранов.
**Изменилось**
* Описание выпуска доходит до канала обновлений вместе с самим обновлением.
### 2026.3.0
[Заголовок раздела «2026.3.0»](#202630)
Первый выпуск линии. Ниже — то, с чем продукт в неё вошёл.
**Появилось**
* Открытие документов DOCX и правка прямо в открытом файле.
* Сохранение в тот же файл: запись целиком либо отсутствие записи, резервная копия предыдущей версии, восстановление после аварийного завершения.
* Понятные отказы при конфликте с изменением на диске и при файле только для чтения.
* Постраничная вёрстка и печать.
* Таблицы, в том числе рисование таблицы мышью.
* Отмена и повтор действий.
* Набор текста и перемещение каретки по правилам Word: границы слов, границы строк, переход между страницами.
* Разрыв строки и неразрывный пробел по штатным сочетаниям клавиш.
* Параметры страницы.
* Автообновление приложения.
* Обращение в поддержку прямо из приложения.
**Изменилось**
* Отклик на набор в больших документах ускорился примерно в восемь раз.
## Выпуски 2026.2
[Заголовок раздела «Выпуски 2026.2»](#выпуски-20262)
### 2026.2.14
[Заголовок раздела «2026.2.14»](#2026214)
**Изменилось**
* Открытие плотного документа не ждёт разбора всего файла: первая страница появляется сразу, остальные догружаются по мере прокрутки.
* Память при работе с документом в несколько сотен страниц держится ровнее: разобранные страницы, до которых читатель не дошёл, освобождаются.
**Исправлено**
* Курсор не сбивается в начало, когда фоновая вёрстка догоняет место правки.
### 2026.2.13
[Заголовок раздела «2026.2.13»](#2026213)
**Появилось**
* Автосохранение с восстановлением после аварийного завершения: при следующем запуске предлагается вернуться к несохранённой работе.
**Исправлено**
* Копия для восстановления пишется целиком или не пишется вовсе — наполовину записанного файла не остаётся.
### 2026.2.12
[Заголовок раздела «2026.2.12»](#2026212)
**Появилось**
* Проверка правописания для русского и английского с учётом участков, помеченных как не требующие проверки.
### 2026.2.11
[Заголовок раздела «2026.2.11»](#2026211)
**Изменилось**
* Печать и экспорт в PDF используют ту же вёрстку, что и экран, поэтому разбиение на страницы совпадает.
**Исправлено**
* Колонтитулы первой страницы не подставляются на остальные, когда для них задан отдельный вид.
### 2026.2.10
[Заголовок раздела «2026.2.10»](#2026210)
**Появилось**
* Слияние с источником данных: подстановка полей и постраничный просмотр результата до записи файла.
## Выпуски 2026.1
[Заголовок раздела «Выпуски 2026.1»](#выпуски-20261)
### 2026.1.9
[Заголовок раздела «2026.1.9»](#202619)
**Появилось**
* Чтение и запись ODT: текст, абзацы, стили, таблицы и списки.
**Изменилось**
* Формат файла определяется по содержимому, а не только по расширению.
### 2026.1.8
[Заголовок раздела «2026.1.8»](#202618)
**Появилось**
* Запись исправлений: правки помечаются автором, их можно принять или отклонить по одной.
### 2026.1.7
[Заголовок раздела «2026.1.7»](#202617)
**Появилось**
* Примечания к выделенному участку текста с ответами в ветке.
**Исправлено**
* Привязка примечания переживает разделение и слияние абзацев.
### 2026.1.6
[Заголовок раздела «2026.1.6»](#202616)
**Изменилось**
* Отмена и повтор действий ведут единую историю на весь документ, а не отдельную на каждый вид правки.
## Выпуски 2025.4
[Заголовок раздела «Выпуски 2025.4»](#выпуски-20254)
### 2025.4.8
[Заголовок раздела «2025.4.8»](#202548)
**Появилось**
* Готовая поверхность редактора, которую можно открыть в браузере или встроить в своё окно.
### 2025.4.7
[Заголовок раздела «2025.4.7»](#202547)
**Появилось**
* Тот же контракт через WebSocket: процесс сам выбирает свободный порт на локальном интерфейсе и выдаёт одноразовый пропуск.
### 2025.4.6
[Заголовок раздела «2025.4.6»](#202546)
**Изменилось**
* Отказ приходит до изменения документа, а не после: устаревшая ревизия или неподдерживаемая операция отклоняются целиком.
### 2025.4.5
[Заголовок раздела «2025.4.5»](#202545)
**Появилось**
* Оформление под чужой бренд: тема, язык интерфейса, режим только для чтения.
### 2025.4.4
[Заголовок раздела «2025.4.4»](#202544)
**Появилось**
* Постоянный сеанс: документ остаётся открытым между командами, состояние не собирается заново на каждый вызов.
## Выпуски 2025.3
[Заголовок раздела «Выпуски 2025.3»](#выпуски-20253)
### 2025.3.7
[Заголовок раздела «2025.3.7»](#202537)
**Появилось**
* Типизированная правка текста с проверкой ожидаемой ревизии документа и выделения.
### 2025.3.6
[Заголовок раздела «2025.3.6»](#202536)
**Появилось**
* Снимок документа в виде дерева объектов: абзацы, участки форматирования, таблицы, изображения.
### 2025.3.5
[Заголовок раздела «2025.3.5»](#202535)
**Изменилось**
* Позиция в документе адресуется устойчивым идентификатором узла, а не порядковым номером, который менялся при любой правке.
### 2025.3.4
[Заголовок раздела «2025.3.4»](#202534)
**Появилось**
* Разовые команды над файлом без запуска интерфейса — для пакетной обработки.
## Выпуски 2025.2
[Заголовок раздела «Выпуски 2025.2»](#выпуски-20252)
### 2025.2.6
[Заголовок раздела «2025.2.6»](#202526)
**Появилось**
* Поиск и замена по документу с учётом регистра и целых слов.
### 2025.2.5
[Заголовок раздела «2025.2.5»](#202525)
**Появилось**
* Стили абзаца и знака: применение, изменение и создание своих.
**Исправлено**
* Изменение стиля доходит до файла, а не остаётся на экране.
### 2025.2.4
[Заголовок раздела «2025.2.4»](#202524)
**Появилось**
* Многоуровневые списки с нумерацией, продолжающейся через весь документ.
### 2025.2.3
[Заголовок раздела «2025.2.3»](#202523)
**Исправлено**
* Интервалы до и после абзаца не складываются дважды на границе страницы.
## Выпуски 2025.1
[Заголовок раздела «Выпуски 2025.1»](#выпуски-20251)
### 2025.1.5
[Заголовок раздела «2025.1.5»](#202515)
**Появилось**
* Колонтитулы, включая отдельный вид для первой страницы и для чётных страниц.
### 2025.1.4
[Заголовок раздела «2025.1.4»](#202514)
**Появилось**
* Разрывы страниц и разделов; параметры страницы задаются на раздел.
### 2025.1.3
[Заголовок раздела «2025.1.3»](#202513)
**Изменилось**
* Вёрстка строк учитывает кернинг и масштаб знаков.
### 2025.1.2
[Заголовок раздела «2025.1.2»](#202512)
**Исправлено**
* Перенос слов не разрывает неразрывный пробел и дефис.
## Выпуски 2024.4
[Заголовок раздела «Выпуски 2024.4»](#выпуски-20244)
### 2024.4.4
[Заголовок раздела «2024.4.4»](#202444)
**Появилось**
* Изображения в тексте: вставка, замена, размер и обтекание.
### 2024.4.3
[Заголовок раздела «2024.4.3»](#202443)
**Появилось**
* Объединение и разделение ячеек таблицы.
**Исправлено**
* Ширина столбцов в процентах не округляется до целых.
### 2024.4.2
[Заголовок раздела «2024.4.2»](#202442)
**Появилось**
* Границы и заливка ячеек, повтор строки заголовка на каждой странице.
### 2024.4.1
[Заголовок раздела «2024.4.1»](#202441)
**Исправлено**
* Таблица, начинающаяся в конце страницы, переносится целиком, если помещается на следующей.
## Выпуски 2024.3
[Заголовок раздела «Выпуски 2024.3»](#выпуски-20243)
### 2024.3.3
[Заголовок раздела «2024.3.3»](#202433)
**Появилось**
* Таблицы: чтение, отображение и правка содержимого ячеек.
### 2024.3.2
[Заголовок раздела «2024.3.2»](#202432)
**Изменилось**
* Разметка, которую редактор не понимает, сохраняется без изменений и возвращается в файл при записи.
### 2024.3.1
[Заголовок раздела «2024.3.1»](#202431)
**Исправлено**
* Повторное открытие сохранённого файла даёт тот же документ, а не приблизительно похожий.
## Выпуски 2024.2
[Заголовок раздела «Выпуски 2024.2»](#выпуски-20242)
### 2024.2.3
[Заголовок раздела «2024.2.3»](#202423)
**Появилось**
* Запись DOCX: собранный пакет открывается сторонними редакторами.
### 2024.2.2
[Заголовок раздела «2024.2.2»](#202422)
**Изменилось**
* Файл записывается целиком во временный и заменяет исходный одним действием — прерывание не оставляет испорченный документ.
### 2024.2.1
[Заголовок раздела «2024.2.1»](#202421)
**Исправлено**
* Порядок частей внутри пакета не меняется при каждом сохранении.
## Выпуски 2024.1
[Заголовок раздела «Выпуски 2024.1»](#выпуски-20241)
### 2024.1.2
[Заголовок раздела «2024.1.2»](#202412)
**Появилось**
* Правка текста: ввод, удаление, разделение и слияние абзацев.
### 2024.1.1
[Заголовок раздела «2024.1.1»](#202411)
**Появилось**
* Выделение мышью и клавиатурой, перемещение курсора по словам и строкам.
## Выпуски 2023.4
[Заголовок раздела «Выпуски 2023.4»](#выпуски-20234)
### 2023.4.2
[Заголовок раздела «2023.4.2»](#202342)
**Появилось**
* Отображение страниц с полями, отступами и выравниванием абзацев.
### 2023.4.1
[Заголовок раздела «2023.4.1»](#202341)
**Изменилось**
* Разбиение на строки и страницы считается в ядре, а не в интерфейсе.
## Выпуски 2023.3
[Заголовок раздела «Выпуски 2023.3»](#выпуски-20233)
### 2023.3.1
[Заголовок раздела «2023.3.1»](#202331)
**Появилось**
* Начертания и размеры шрифта, цвет текста и выделение цветом.
### 2023.3.0
[Заголовок раздела «2023.3.0»](#202330)
**Появилось**
* Показ содержимого документа с сохранением структуры абзацев.
## Выпуски 2023.2
[Заголовок раздела «Выпуски 2023.2»](#выпуски-20232)
### 2023.2.1
[Заголовок раздела «2023.2.1»](#202321)
**Изменилось**
* Разбор пакета документа перенесён в ядро на Rust.
### 2023.2.0
[Заголовок раздела «2023.2.0»](#202320)
**Появилось**
* Чтение DOCX: части пакета, связи между ними, поток текста.
## До перехода на нумерацию по кварталам
[Заголовок раздела «До перехода на нумерацию по кварталам»](#до-перехода-на-нумерацию-по-кварталам)
Самые ранние сборки выходили под номерами другого вида и раздавались как альфа — для показа, а не для работы. Отдельной истории изменений по ним нет: тогда продукт менялся быстрее, чем выходили версии.
# Живой пример: редактор в браузере
> Работающий редактор A4 на странице документации и весь маленький проект, которым он подключается: страница, код рамки и хост.
Ниже — настоящий редактор A4, тот же, что и в поставке, с обычным договором: заголовки, списки, таблица со сроками и суммой. Когда демонстрационная площадка поднята, им можно пользоваться прямо здесь: документ открывается в вашей собственной кабинке, правки видите только вы, они никуда не сохраняются, а следующий посетитель получит документ исходным. Когда площадка не поднята, на её месте — снимок того же редактора, и об этом сказано под рамкой.
[Редактор A4 с открытым договором](/demo/?doc=demo)
На весь экран[открыть отдельно ↗](/demo/?doc=demo)Настоящий маленький проект: рамка выше запускает именно те файлы, что показаны ниже, из `examples/a4-embed`
## Из чего состоит этот пример
[Заголовок раздела «Из чего состоит этот пример»](#из-чего-состоит-этот-пример)
В рамке выше работает тот же редактор и тот же способ встраивания, но площадка поднята у нас: документ ей даёт наша демонстрационная служба. Чтобы то же самое заработало у вас, нужны три вещи — страница с местом под рамку, код подключения и ваш сервер с двумя адресами. Ровно они и показаны ниже: это цельный проект, который запускается сам, а не выдержки из него.
Смотреть его стоит в таком порядке: сначала страница, потом подключение, потом сервер. Первые два файла отвечают на вопрос «где появляется редактор», третий — на вопрос «откуда документ и куда уходят правки».
Страница вашего приложения. Редактора в ней нет — есть карточка документа и место, куда он встанет.
index.html
```html
Договор №42 — документыДокументы…
Договор №42
docx · загружен 14 марта · только просмотр
```
Всё подключение целиком. Рамка на адрес вашего домена; никакого модуля в браузер не загружается.
src/embed.js
```js
/**
* Подключение редактора — весь клиентский код примера.
*
* Редактор открывается рамкой на адрес `/fastdoc/?doc=<идентификатор>`.
* Никакого модуля в браузер не загружается: документ считает ядро на
* сервере, а рамка показывает его страницу.
*
* Событий наружу редактор не шлёт, и слушать здесь нечего. Связь с вашим
* приложением идёт другой дорогой: правки редактор отправляет на ваш же
* сервер, в «файл по идентификатору». Что произошло с документом, вы
* узнаёте оттуда, а не из браузера.
*/
const DOC_ID = "contract-42";
const stage = document.querySelector("[data-stage]");
const edit = document.querySelector("[data-edit]");
const state = document.querySelector("[data-state]");
const who = document.querySelector("[data-who]");
/* Имя пользователя берём из своего же API — того самого, которое потом
спросит редактор. Одна личность на всё приложение. */
fetch("/v1/me")
.then((res) => (res.ok ? res.json() : null))
.then((user) => {
who.textContent = user ? user.name : "не выполнен вход";
})
.catch(() => {
who.textContent = "не выполнен вход";
});
edit.addEventListener("click", () => {
const frame = document.createElement("iframe");
frame.src = `/fastdoc/?doc=${encodeURIComponent(DOC_ID)}`;
frame.title = "Редактор документа";
/* Буфер обмена нужен явным разрешением: без него копирование между
документом и остальным браузером работать не будет. */
frame.allow = "clipboard-read; clipboard-write";
stage.replaceChildren(frame);
stage.hidden = false;
edit.disabled = true;
state.textContent = "открыт на правку";
});
```
Оформление вашей страницы. Редактор его не касается: внутри рамки свой документ, снаружи ваш интерфейс.
src/styles.css
```css
/* Оформление вашей страницы. Редактор его не касается: внутри рамки свой
документ, снаружи — ваш интерфейс. */
:root {
color-scheme: light;
font-family: system-ui, -apple-system, "Segoe UI", sans-serif;
}
body {
margin: 0;
background: #f6f7f9;
color: #1a1d21;
}
.bar {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0.75rem 1.25rem;
background: #fff;
border-bottom: 1px solid #e3e6ea;
}
.brand {
font-weight: 600;
}
.who {
color: #6b7280;
font-size: 0.875rem;
}
.card {
max-width: 64rem;
margin: 1.5rem auto;
padding: 1.25rem;
background: #fff;
border: 1px solid #e3e6ea;
border-radius: 0.5rem;
}
h1 {
margin: 0 0 0.25rem;
font-size: 1.25rem;
}
.meta {
margin: 0 0 1rem;
color: #6b7280;
font-size: 0.875rem;
}
button {
padding: 0.5rem 1rem;
border: 0;
border-radius: 0.375rem;
background: #2563eb;
color: #fff;
font: inherit;
cursor: pointer;
}
button[disabled] {
background: #9aa4b2;
cursor: default;
}
/* Рамке нужна заданная высота: редактор занимает её целиком, а сам её не
назначает — снаружи виднее, сколько места ему отдать. */
.stage {
margin-top: 1rem;
height: 75vh;
border: 1px solid #e3e6ea;
border-radius: 0.375rem;
overflow: hidden;
}
.stage iframe {
display: block;
width: 100%;
height: 100%;
border: 0;
}
```
Ваш сервер: кто пришёл, файл по идентификатору, приём правок. Здесь же проброс на веб-хост A4 — в рабочем хозяйстве это правило nginx.
server/host.mjs
```js
/**
* Ваш сервер: два адреса, которых редактору достаточно, и приём правок.
*
* Редактор своих учётных записей не имеет и своего хранилища тоже. Кто
* пришёл и какой файл ему отдать — решаете вы, этими двумя ответами:
*
* GET /v1/me — кто пришёл
* GET /v1/files/{id}/content — сам документ, если этому человеку можно
* POST /v1/files/{id}/replace — сохранение правок обратно к вам
*
* Если у вас есть личный кабинет и файлы, оба первых адреса уже написаны.
*
* Здесь же для полноты картины лежит и то, что в вашем хозяйстве обычно
* делает nginx: раздача страницы и проброс `/fastdoc/` на веб-хост A4.
* Отдельным сервером это или правилом в вашем — дело вкуса; важно, что
* редактор и ваш API отвечают на одном домене и с теми же куками.
*/
import { createServer } from "node:http";
import { readFile, writeFile } from "node:fs/promises";
import { extname, join, normalize } from "node:path";
const arg = (name, fallback) => {
const at = process.argv.indexOf(name);
return at > 0 && process.argv[at + 1] ? process.argv[at + 1] : fallback;
};
const docPath = arg("--doc", "./document.docx");
const port = Number(arg("--port", "8080"));
/* Адрес веб-хоста A4 — того самого процесса, который поднимает кабинки. */
const webhost = arg("--webhost", "http://127.0.0.1:8090");
const root = new URL("../", import.meta.url).pathname;
const TYPES = {
".html": "text/html; charset=utf-8",
".js": "text/javascript; charset=utf-8",
".css": "text/css; charset=utf-8",
};
const DOCX =
"application/vnd.openxmlformats-officedocument.wordprocessingml.document";
/**
* Правки приходят как `multipart/form-data` — одной частью с именем `file`.
*
* Тело обязательно РАЗБИРАТЬ. Если записать в документ сырое тело запроса,
* к началу файла приклеятся служебные строки формы, и .docx перестанет
* открываться — молча, без ошибки в журнале. Признак этой беды при
* распаковке: «extra bytes at beginning».
*/
const filePart = (body, contentType) => {
const boundary = /boundary=(?:"([^"]+)"|([^;]+))/i.exec(contentType ?? "");
if (!boundary) return null;
const mark = Buffer.from(`--${boundary[1] ?? boundary[2]}`);
let at = body.indexOf(mark);
while (at !== -1) {
const headEnd = body.indexOf("\r\n\r\n", at);
if (headEnd === -1) return null;
const head = body.subarray(at, headEnd).toString("utf8");
const next = body.indexOf(mark, headEnd);
if (next === -1) return null;
/* Перед следующей границей стоит CRLF, который к содержимому не
относится: он разделитель, а не байт документа. */
if (/name="file"/i.test(head)) return body.subarray(headEnd + 4, next - 2);
at = next;
}
return null;
};
const readBody = (req) =>
new Promise((resolve, reject) => {
const chunks = [];
req.on("data", (chunk) => chunks.push(chunk));
req.on("end", () => resolve(Buffer.concat(chunks)));
req.on("error", reject);
});
const server = createServer(async (req, res) => {
const url = new URL(req.url, `http://127.0.0.1:${port}`);
/* Кто пришёл. В настоящем приложении здесь ваша сессия; отказ отвечайте
кодом 401 или 403 — редактор покажет его человеку словами. */
if (url.pathname === "/v1/me") {
res.writeHead(200, { "content-type": "application/json; charset=utf-8" });
res.end(JSON.stringify({ id: "u-1", name: "Анна Петрова" }));
return;
}
/* Файл по идентификатору. Право проверяете вы: не отдали файл — редактор
не откроется, и это правильный ответ. */
if (url.pathname.endsWith("/content") && req.method === "GET") {
res.writeHead(200, {
"content-type": DOCX,
/* Имя файла отсюда попадёт в заголовок вкладки и в имя копии, если
документ понадобится второму человеку. */
"content-disposition":
"attachment; filename*=UTF-8''" + encodeURIComponent("Договор №42.docx"),
});
res.end(await readFile(docPath));
return;
}
/* Правки обратно. Здесь — ваше хранилище: версия, аудит, уведомления. */
if (url.pathname.endsWith("/replace") && req.method === "POST") {
const body = await readBody(req);
const bytes = filePart(body, req.headers["content-type"]);
if (!bytes) {
res.writeHead(400, { "content-type": "application/json" });
res.end(JSON.stringify({ error: "ожидалась часть с именем file" }));
return;
}
await writeFile(docPath, bytes);
console.log(`сохранено ${bytes.length} байт`);
res.writeHead(200, { "content-type": "application/json" });
res.end(JSON.stringify({ ok: true }));
return;
}
/* Проброс на веб-хост A4. Куки идут насквозь: по ним он и спросит у вас,
кто пришёл. В рабочем хозяйстве это правило nginx. */
if (url.pathname === "/fastdoc/" || url.pathname.startsWith("/fastdoc/")) {
const target = new URL(url.pathname + url.search, webhost);
const upstream = await fetch(target, {
method: req.method,
headers: { ...req.headers, host: new URL(webhost).host },
body: req.method === "GET" || req.method === "HEAD" ? undefined : req,
redirect: "manual",
duplex: "half",
}).catch(() => null);
if (!upstream) {
res.writeHead(502, { "content-type": "text/plain; charset=utf-8" });
res.end("веб-хост A4 не отвечает");
return;
}
res.writeHead(upstream.status, Object.fromEntries(upstream.headers));
res.end(Buffer.from(await upstream.arrayBuffer()));
return;
}
/* Ваша страница и её файлы. */
const name = url.pathname === "/" ? "/index.html" : url.pathname;
const path = join(root, normalize(name).replace(/^(\.\.[/\\])+/, ""));
const body = await readFile(path).catch(() => null);
if (!body) {
res.writeHead(404);
res.end();
return;
}
res.writeHead(200, { "content-type": TYPES[extname(path)] ?? "application/octet-stream" });
res.end(body);
});
server.listen(port, () => {
console.log(`пример открыт: http://127.0.0.1:${port}/`);
console.log(`редактор берётся с ${webhost}`);
});
```
## На чём легко ошибиться
[Заголовок раздела «На чём легко ошибиться»](#на-чём-легко-ошибиться)
Правки приходят к вам как `multipart/form-data` — одной частью с именем `file`. Тело обязательно разбирать. Если записать в документ сырое тело запроса, к началу файла приклеятся служебные строки формы, и документ перестанет открываться — молча, без ошибки в журнале. Признак этой беды при распаковке: «extra bytes at beginning».
На этом попадались и мы сами, поэтому разбор в примере выписан целиком, а не сведён к вызову библиотеки.
## Чем этот пример отличается от рабочего встраивания
[Заголовок раздела «Чем этот пример отличается от рабочего встраивания»](#чем-этот-пример-отличается-от-рабочего-встраивания)
| Что | В этом примере | В вашем приложении |
| ------------------- | ------------------------------------------------ | --------------------------------------------------- |
| Кто пользователь | Гость: личность одна на всех посетителей | Ваш вошедший пользователь |
| Откуда документ | Один и тот же демонстрационный файл | Ваш файл по идентификатору |
| Куда идут правки | Никуда: сохранение принимается и не записывается | В ваше хранилище, вашим же API |
| Права | Не проверяются | Проверяете вы, в том числе повторно во время работы |
| Проброс `/fastdoc/` | Сделан тем же процессом, для краткости | Обычно правило nginx |
Всё остальное совпадает: тот же редактор, то же ядро, тот же способ встраивания.
## Дальше
[Заголовок раздела «Дальше»](#дальше)
* Как подключить у себя, с тремя шагами и обязательными условиями — [Редактор в браузере](/a4/webhost/).
* Где проходят границы ответственности — [Ответственность и безопасность](/a4/responsibility/).
* Другие способы разместить A4 — [Платформы и размещение](/a4/deployment/).
# Платформы и размещение
> Шесть способов разместить A4 и что доказано для каждого: настольное приложение, готовая сборка, сервер без интерфейса, браузер, Android и iOS.
*Меняется адаптер размещения. Документная истина и базовый контракт остаются в ядре на Rust.*
| Профиль | Как устроено | Что доказано на сегодня |
| ----------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Внутри настольного приложения | Ваше приложение запускает Surface Host, открывает его адрес в системном окне просмотра веб-страниц и получает события. | На WKWebView пройден полный круг: документ готов → появились несохранённые изменения → сохранён → открыт заново. Для WebView2 адаптер предусмотрен. |
| Готовое приложение | Оболочка на Electron поставляется как приложение A4. | Самый полный набор возможностей для пользователя. Внутренние каналы этого приложения публичным интерфейсом не становятся. |
| Сервер без интерфейса | Ваш сервис управляет сеансом ядра через строки JSON или WebSocket и сам отвечает за хранилище; интерфейс не нужен. | Процессор работает. Обслуживание многих клиентов сразу, проверка доступа, квоты и отказоустойчивость — отдельная работа. |
| Браузер | Поверхность работает в браузере, а документ считает ядро на сервере: каждому пользователю поднимается отдельный процесс. См. [Редактор в браузере](/a4/webhost/). | В браузер уходит обычный код страницы и отрисовка на полотне. Сборки ядра под WebAssembly у A4 нет, и для этого пути она не нужна. |
| Android | Ядро собирается библиотекой, подключается через JNI и работает под Android WebView. | Пройден сквозной путь: открыть файл, изменить, сохранить и проверить результат. Набор операций в мобильном ядре пока узкий — это доказательство пути, а не редактор на телефоне. |
| iOS | Ядро собирается библиотекой, подключается из Swift и работает под WKWebView. | Обвязка для сборки готова; работоспособность на устройствах пока не заявляется. |
## Серверный вариант
[Заголовок раздела «Серверный вариант»](#серверный-вариант)
```plaintext
[Клиент: браузер или телефон] → [Ваш сервис: вход, хранилище, аудит]
| команды и события
v
[Адаптер сеанса A4]
|
v
[Ядро на Rust]
```
Важно
A4 можно использовать как процессор, не обращаясь ни к какому облаку с нашей стороны. Но если сервис смотрит в интернет и обслуживает нескольких клиентов сразу, отдельно согласуются безопасность, квоты, изоляция процессов, наблюдаемость и обязательства по доступности.
## Где про это подробнее
[Заголовок раздела «Где про это подробнее»](#где-про-это-подробнее)
* Редактор внутри веб-приложения, без установки чего-либо пользователю — [Редактор в браузере](/a4/webhost/).
* Встраивание поверхности в WebView или iframe — [Встраивание Surface](/a4/embedding/), запуск — [Web Surface](/a4/surface/).
* Что доступно внутри окна настольного приложения и почему это не публичный SDK — [Внутренние объекты окна приложения](/a4/electron/).
* Управление сеансом из службы без интерфейса — [Тот же контракт через WebSocket](/a4/websocket/).
* Общая картина режимов для обоих продуктов — [Режимы встраивания](/platform/deployment/).
# Диагностика и контрольный список
> Что проверить, если первый запуск не работает: шесть типовых симптомов и контрольный список из десяти шагов перед передачей в работу.
Если первый запуск не идёт, найдите симптом в таблице: рядом с ним — что именно проверить.
| Симптом | Что проверить |
| ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Приложение не найдено | Путь установки: `/Applications/A4.app` на macOS, `C:\Program Files\A4` на Windows (каталог можно было сменить при установке), `/opt/A4` на Linux — если ставили пакетом `deb` или `rpm`. У поставки `AppImage` каталога установки нет: файлы лежат внутри самого образа, и достать их можно распаковкой — `./A4-….AppImage --appimage-extract`, дальше `squashfs-root/resources/`. Проверьте также, что скачана сборка под вашу архитектуру |
| Процессор не запускается | Пакет приложения повреждён или скопирован не полностью. На macOS и Linux проверьте, что у файла есть право на запуск |
| В ответе `ok: false` | Прочитайте поля `error`, `cmd` и `id`: в них сказано, какая команда отклонена и почему. Проверьте путь к файлу и поля операции |
| Файл не изменился | Сохраняете ли вы в новый путь; что показывают ревизия и признак несохранённых изменений; прочитайте результат заново командой `model-docx` |
| Поверхность отключилась | Перезагрузка страницы, пропуск, порт локального интерфейса, жизненный цикл дочернего процесса и переподключение после разрыва |
| `window.fastdocShell` не определён | Код выполняется вне окна самого приложения A4 — там этих объектов нет. Для встраивания используйте внешний контракт: обмен строками JSON, WebSocket или поверхность |
## Контрольный список первой интеграции
[Заголовок раздела «Контрольный список первой интеграции»](#контрольный-список-первой-интеграции)
1. Найти установленное приложение и `fastdoc-cli` внутри него — пути для трёх систем даны в [Установке и проверке](/a4/install/).
2. На macOS и Linux проверить, что у `fastdoc-cli` стоит флаг исполняемого файла; на Windows такого флага нет.
3. `ping` → `ok: true`, `pong: true`.
4. `open` с указанием `document_id`.
5. `model` — сохранить исходный текст для сравнения.
6. Правка с `transaction_order: 1`.
7. Сохранить в новый `output.docx`.
8. Повторно прочитать `output.docx` тем же процессором.
9. Открыть `output.docx` в A4 и увидеть изменение.
10. `shutdown` и завершение процесса.
Минимальная интеграция поднята
Если обмен строками JSON и обмен через WebSocket проходят, сохранённый DOCX читается с диска заново, а A4 показывает изменение на экране — минимальная интеграция поднята. Следующий шаг: выбрать набор нужных вам операций и закрепить их отдельной версионированной схемой контракта.
## Что приложить к обращению
[Заголовок раздела «Что приложить к обращению»](#что-приложить-к-обращению)
Если по таблице выше причина не нашлась, напишите на . Разбор идёт быстрее, когда в письме сразу есть:
* версия сборки A4 и система, на которой это видно;
* имя команды (`cmd`) и текст отказа (`error`) целиком, вместе с `id` запроса;
* одна строка запроса и одна строка ответа как есть, без пересказа;
* наименьший обезличенный DOCX, на котором это повторяется;
* повторяется ли это в новом процессе — после `shutdown` и запуска заново.
## Куда смотреть дальше
[Заголовок раздела «Куда смотреть дальше»](#куда-смотреть-дальше)
* Формат запроса и ответа, включая поля, которых может не быть, — [Объекты запросов и ответов](/a4/envelope/).
* Полный перечень команд с параметрами — [справочник Session Protocol](/reference/a4/session-protocol/).
* Коды и тексты отказов транспорта — [Транспорт и формат сообщений A4](/reference/a4/transport/).
* Поведение после разрыва соединения — [Встраивание Surface](/a4/embedding/).
# Структура документа: как её получить
> Архитектура и границы ответственности, путь одного пользовательского действия, смысловая модель и текущий Rust Session API для чтения.
## Архитектура и границы ответственности
[Заголовок раздела «Архитектура и границы ответственности»](#архитектура-и-границы-ответственности)
Путь данных один и тот же — в настольном приложении, в системном окне просмотра веб-страниц и при работе без интерфейса:
| Слой | Что делает | Чего не делает |
| ----------------------------------- | -------------------------------------------------------------------------------------------------- | ------------------------------------------------- |
| Ваше приложение | Окно, бренд, вход, хранилище, рабочие процессы и их жизненный цикл | Не владеет моделью документа и его вёрсткой |
| Поверхность редактора (Web Surface) | Полотно, лента, ввод, показ меню, доставка событий | Не правит DOCX напрямую |
| Адаптер к приложению | Запуск процесса, разбор приветственной строки с адресом, обмен по JSON или WebSocket, события окна | Не разбирает разметку документа |
| Сеанс документа | Команды, выделение, история, проекции модели и вёрстки, сохранение | Не отдаёт произвольный доступ к разметке страницы |
| Ядро на Rust | Пакет OOXML, смысл документа, вёрстка, запись обратно в файл | Не зависит от оформления оболочки |
| Файл DOCX | То, что сохраняется и открывается заново | Не подменяется снимком HTML |
### Путь одного пользовательского действия
[Заголовок раздела «Путь одного пользовательского действия»](#путь-одного-пользовательского-действия)
* **Событие указателя.** Поверхность получает координаты щелчка, щелчка правой кнопкой или двойного щелчка.
* **Определение попадания.** Координата сопоставляется со страницей, строкой и позицией курсора, а затем с объектом документа.
* **Сбор контекста.** Собираются сам объект, выделение, его родители в структуре документа, номер ревизии и список разрешённых команд.
* **Решение интерфейса.** A4 и ваша сторона составляют контекстное меню — документ при этом не меняется.
* **Типизированная команда.** Выбранный пункт превращается в команду с указанием ожидаемой ревизии.
* **Изменение документа.** Ядро сверяет ревизию и разрешения, меняет модель и возвращает подтверждение с новой ревизией.
* **Перерисовка и сохранение.** Проекции обновляются; сохранение пишет DOCX, который открывается заново той же моделью.
Главное правило расширения
Вокруг процессора можно добавлять любое поведение, но нельзя заводить вторую истину документа — в DOM, в состоянии JavaScript или в вручную изменённом OOXML.
## Как получить структуру документа
[Заголовок раздела «Как получить структуру документа»](#как-получить-структуру-документа)
Термин «структура документа» неоднозначен. A4 разделяет пять представлений, чтобы вы запрашивали только то, что действительно нужно.
| Представление | Команда | Назначение | Пример данных |
| ---------------------- | -------------------------------------- | ----------------------------------------------------------------------------------------- | ---------------------------------------------------- |
| Смысловая модель | `model`, `model-docx` | Абзацы, участки форматирования, таблицы, ссылки на стили и нумерацию, пометки о поддержке | `body.blocks[]`, `paragraph_index`, `text`, `runs[]` |
| Вёрстка | `compose`, `paginate` | Разделы, страницы, строки, переносы, геометрия | `page_index`, границы строк, порядок страниц |
| Сцена | `print`, `print-window`, `print-delta` | Готовые к отрисовке страницы и примитивы рисования | сцены страниц и их отпечатки |
| Рецензирование и стили | `review`, `style` | Примечания, записанные исправления, разрешение стилей | проекции рецензирования и стилей |
| Состояние правки | `status`, `set-selection` | Ревизия, наличие несохранённых изменений, отмена и повтор, выделение и состояние вида | `accepted_revision`, `selection_revision` |
### Пример: смысловая модель
[Заголовок раздела «Пример: смысловая модель»](#пример-смысловая-модель)
```bash
# $CLI — путь к процессору, см. «Установка и проверка»
"$CLI" model-docx /data/contract.docx --format json
```
```json
{
"model_version": "fastdoc-d1-document-model-v1",
"block_count": 1,
"paragraph_count": 1,
"body": {"blocks": [{
"kind": "paragraph",
"block_index": 0,
"text": "A4 basic paragraph.",
"style_ref": null,
"numbering_ref": null,
"runs": [{
"run_index": 0,
"text": "A4 basic paragraph.",
"direct_format": {"bold": null, "italic": null}
}]
}]}
}
```
Что считать стабильной ссылкой
Индекс абзаца удобен для простых команд, но для расширений нужен устойчивый идентификатор узла вместе с номером ревизии: индекс сдвигается при любой правке выше по документу. Публичный способ адресации стоит зафиксировать отдельно и не выдавать наружу внутренние селекторы или узлы разметки.
## Как запустить сеанс сегодня и прочитать документ
[Заголовок раздела «Как запустить сеанс сегодня и прочитать документ»](#как-запустить-сеанс-сегодня-и-прочитать-документ)
### Сеанс JSONL: обмен через стандартный ввод и вывод
[Заголовок раздела «Сеанс JSONL: обмен через стандартный ввод и вывод»](#сеанс-jsonl-обмен-через-стандартный-ввод-и-вывод)
Процесс печатает первую строку готовности со схемой `fastdoc.session-serve.v1`. Дальше каждая строка на входе — запрос в JSON, каждая строка на выходе — ответ на него, связанный по идентификатору.
```json
{"id":"open-1","cmd":"open",
"path":"/data/contract.docx","document_id":"contract-42"}
{"id":"model-1","cmd":"model"}
{"id":"pages-1","cmd":"paginate"}
{"id":"review-1","cmd":"review"}
{"id":"state-1","cmd":"status"}
```
```json
{
"id": "model-1",
"ok": true,
"cmd": "model",
"elapsed_ms": 2,
"result": { "model_version": "...", "body": { "blocks": [] } }
}
```
### WebSocket на локальном адресе
[Заголовок раздела «WebSocket на локальном адресе»](#websocket-на-локальном-адресе)
```bash
"$CLI" serve --ws 0
# stdout: {"schema":"fastdoc.session-ws-bootstrap.v1",
# "host":"127.0.0.1","port":49172,"token":"..."}
```
* Привязка — только 127.0.0.1. Порт 0 выбирается автоматически.
* Токен — секрет длиной 256 бит из системного источника случайности. Его нельзя писать в журналы и передавать в аналитику.
* Команды одинаковы. Обмен строками JSON и обмен через WebSocket идут через один и тот же сеанс и одну схему запросов.
Рекомендация для первого пилота
Начните с отдельного процесса ядра и обмена строками JSON или через локальный WebSocket: это проверяемая граница, не зависящая от языка вашего приложения. Вызов ядра прямо внутри процесса можно зафиксировать позже, не смешивая внутренние типы с публичным интерфейсом.
## Дальше
[Заголовок раздела «Дальше»](#дальше)
* Определение объекта по координате — [Попадание по точке](/a4/hit-test/).
* Что ещё делает процессор — [карта возможностей](/a4/capabilities/).
# Внутренние объекты окна приложения A4
> Что доступно внутри окна настольного A4, зачем это нужно и почему внутренний контракт окна не является публичным интерфейсом.
Граница доверия
Эти объекты существуют только внутри окна самого приложения A4. В Safari, Chrome, в вашем `iframe` или на произвольном домене их не будет. Для встраивания используются обмен строками JSON, WebSocket или поверхность редактора.
| Объект | Назначение | Примеры методов |
| --------------------- | ------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------- |
| `window.fastdocShell` | Документ, команды редактора, состояние, сохранение, меню | `getState()`, `getCurrentDocumentTruth()`, `openDocxPath()`, `onStateChanged()` |
| `window.a4Update` | Обновления приложения | `status()`, `check()`, `start()`, `dismiss()`, `reveal()`, `openNotes()`, `onState()` |
| `window.a4Help` | Версия и поддержка | `version()`, `reportProblem()`, `supportBundle()`, `checkUpdates()`, `reportBadge()`, `onReportBadge()` |
| `window.a4Vizor` | Заметки встроенной записи окна: она живёт в памяти вкладки около минуты и уходит только вместе с отчётом о проблеме | `onNote()` |
## Примеры внутри окна самого приложения
[Заголовок раздела «Примеры внутри окна самого приложения»](#примеры-внутри-окна-самого-приложения)
```ts
const state = await window.fastdocShell.getState();
const truth = await window.fastdocShell.getCurrentDocumentTruth();
const version = await window.a4Help.version();
const stopState = window.fastdocShell.onStateChanged((next) => {
console.log("document state", next);
});
const stopUpdate = window.a4Update.onState((next) => {
console.log("update state", next);
});
await window.a4Update.check();
// При размонтировании компонента обязательно отписаться.
stopState();
stopUpdate();
```
## Как выбрать правильный API
[Заголовок раздела «Как выбрать правильный API»](#как-выбрать-правильный-api)
* Пишете своё настольное приложение или серверную обвязку — `fastdoc-cli serve` с обменом строками JSON.
* Оболочка на веб-технологиях и нужен асинхронный обмен — `serve --ws 0`.
* Нужно показать встраивание на машине пользователя — команда `surface` и окно WebView или `iframe`; правка в этой поверхности ограничена первым абзацем.
* Ваш продукт живёт в браузере, а ядро должно работать на сервере — [редактор в браузере](/a4/webhost/).
* Дорабатываете само приложение A4 — внутренний контракт его окна, по отдельному согласованию.
# Встраивание Surface
> Как вставить поверхность редактора в своё приложение: iframe и WebView, вставка и удаление, подписка на события, переподключение и рабочая установка.
Поверхность запускается локально и открывается по адресу из bootstrap. Эта страница — о том, как встроить её в своё приложение.
Поверхность правит только первый абзац
Встраивать её как полноценный редактор нельзя: она объявляет открытие, правку **первого абзаца**, сохранение и события. Это проверяемая демонстрация пути, а не редактор для пользователя — см. [Запуск Web Surface](/a4/surface/). Полный редактор в браузере — это [веб-хост](/a4/webhost/).
Два разных способа через iframe
Здесь описано встраивание поверхности, которая запущена **рядом с вашим приложением** — на той же машине, что и пользователь: настольная программа поднимает Surface Host и открывает его адрес в своём окне.
Если ваш продукт живёт в браузере и ядро должно работать **на сервере**, нужен другой путь — [Редактор в браузере](/a4/webhost/). Там пользователю не нужно ничего устанавливать, а каждому открытому документу поднимается отдельный процесс на сервере.
Сначала запустите демо
Если Surface ещё ни разу не запускалась, начните с [запуска](/a4/surface/): там показан полный путь от установки до сохранённого файла.
## Как запустить поверхность
[Заголовок раздела «Как запустить поверхность»](#как-запустить-поверхность)
```bash
# $CLI — путь к процессору, см. «Установка и проверка»
"$CLI" surface --port 0 \
--doc /data/input.docx --save /data/output.docx
# первая строка stdout: fastdoc.surface-host-bootstrap.v1
# адрес из bootstrap.url открыть в WKWebView, WebView2 или браузере
```
## Вставить поверхность и убрать её
[Заголовок раздела «Вставить поверхность и убрать её»](#вставить-поверхность-и-убрать-её)
Что поверхность принимает молча, а что молча отбрасывает
В `cssVariables` попадают только имена, начинающиеся на `--fastdoc-`, и только строковые значения: `"16px"` дойдёт, а `16` — нет. Всё остальное поверхность пропускает без ошибки и без предупреждения. Список того, что она понимает, — на странице [Настройка под свой бренд](/platform/white-label/).
Поле `locale` тоже строгое: если передать не строку или не передать вовсе, язык поверхности станет английским.
`readOnly` здесь выключает ввод в самой поверхности — гасит поле правки и кнопку сохранения. Защитой документа это не является: команды, отправленные по сеансу напрямую, ему не подчиняются. За запрет на уровне ядра отвечает режим защиты самого документа — в нём отклоняется любая команда правки.
```ts
const handle = window.fastdocSurface.mount(container, {
theme: "light",
locale: "ru",
readOnly: false,
cssVariables: { "--fastdoc-accent": "#2f5bff" }
});
// при закрытии вкладки или документа
handle.unmount();
```
## Как получать события
[Заголовок раздела «Как получать события»](#как-получать-события)
```js
window.addEventListener("fastdoc-host-event", (event) => {
const message = event.detail;
if (message.schema !== "fastdoc.surface-host-event.v1") return;
switch (message.type) {
case "ready": console.log(message.documentPath); break;
case "dirty": setUnsavedBadge(message.dirty); break;
case "saved": console.log(message.path, message.byteCount); break;
case "error": showError(message.operation, message.message); break;
case "unmounted": releasePartnerResources(); break;
}
});
```
| Среда | Где встречается | Канал события |
| ---------------------------- | ----------------- | ----------------------------------------------------- |
| WKWebView, WebKitGTK | macOS, iOS, Linux | обработчик сообщений с именем `fastdocHost` |
| WebView2 | Windows | `window.chrome.webview`, событие `WebMessageReceived` |
| Обычный браузер, без обёртки | везде | событие DOM `fastdoc-host-event` |
Выбирать канал не нужно: каждое событие поверхность отправляет во все три сразу. Хост слушает тот, который есть у него, и не знает про остальные.
Честный статус Surface Host
Сегодня это переносимая поверхность с ограниченным набором: её можно вставить в своё окно и убрать обратно, а наружу она отдаёт события `ready`, `dirty`, `saved`, `error` и `unmounted`. Этого хватает, чтобы доказать границу встраивания, но полного внешнего интерфейса ко всем функциям редактора и к расширению контекстного меню здесь ещё нет.
## Встраивание
[Заголовок раздела «Встраивание»](#встраивание)
```html
```
Поверхность сообщает о своих состояниях — `ready`, `dirty`, `saved`, `error` и `unmounted` — событием окна `fastdoc-host-event`. Этого хватает, чтобы знать, что происходит с документом, но не чтобы им управлять: для полного доступа к модели и всему набору операций работайте параллельно через обмен строками JSON или WebSocket.
Известное поведение текущей сборки
При проверке одно долго простаивавшее соединение оборвалось, и страница сообщила, что связь с поверхностью потеряна. Перезагрузка страницы и сохранение сразу после неё прошли без потерь. В рабочей установке предусмотрите проверку живости соединения, автоматическое переподключение и видимый признак состояния — чтобы человек понимал, сохраняются его правки или нет.
## Что предусмотреть в рабочей установке
[Заголовок раздела «Что предусмотреть в рабочей установке»](#что-предусмотреть-в-рабочей-установке)
Поверхность поднимается на случайном свободном порту, а не на заранее известном: занятый порт и чужие подключения к нему — лишний способ помешать работе.
Пропуск выдаётся на каждый сеанс заново и не должен попадать в журналы веб-сервера вместе с адресом страницы.
После разрыва соединение восстанавливается, но правка вслепую не повторяется: сначала нужно убедиться, что она не применилась до обрыва, иначе документ получит её дважды.
Сохранение сначала пишет новую копию, и только по явному решению пользователя ею заменяется исходный файл.
После разрыва и после сохранения
**После разрыва соединения** не повторяйте последнюю правку вслепую: сначала прочитайте актуальное состояние документа. Соединение могло оборваться уже после того, как правка применилась, и повтор продублирует её.
**После события `saved`** откройте сохранённый DOCX заново и убедитесь, что изменение на месте. Это дешёвая проверка, которая ловит расхождение между тем, что показала поверхность, и тем, что легло на диск.
**Оператору показывайте состояние сеанса** — подключение, готовность, несохранённые правки, сохранение, сохранено, ошибка. Человек должен видеть, можно ли сейчас закрывать окно.
## Точные имена
[Заголовок раздела «Точные имена»](#точные-имена)
Имена событий, схем, глобального объекта и параметров запуска собраны в справочнике [Интерфейс поверхности в браузере](/reference/a4/surface-api/). Берите их оттуда: на этой странице они приведены для примера и могут отстать.
# Объекты запросов и ответов
> Минимальная последовательность команд, обязательные и рекомендуемые поля запроса, формат ответа и обработка ошибок.
Каждое сообщение — обычный JSON-объект. Поле `id` выбираете вы, ядро возвращает его без изменения. Поэтому ответы можно сопоставлять с запросами даже при параллельной работе.
## Минимальная последовательность
[Заголовок раздела «Минимальная последовательность»](#минимальная-последовательность)
```json
{"id":"ping-1","cmd":"ping"}
{"id":"open-1","cmd":"open","path":"/absolute/input.docx","document_id":"my-document"}
{"id":"model-1","cmd":"model"}
{"id":"insert-1","cmd":"mutate","operation_kind":"insert-text","paragraph_index":0,"offset":0,"text":"Привет из API. ","document_id":"my-document","transaction_order":1,"transition_kind":"api-insert"}
{"id":"save-1","cmd":"save","path":"/absolute/output.docx"}
{"id":"shutdown-1","cmd":"shutdown"}
```
## Обязательные и рекомендуемые поля
[Заголовок раздела «Обязательные и рекомендуемые поля»](#обязательные-и-рекомендуемые-поля)
| Поле | Назначение | Правило |
| ------------------- | ----------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `id` | Корреляция запроса и ответа | Необязательно. Любое значение JSON — строка, число. Возвращается без изменений, поэтому должно быть уникальным среди запросов, ответ на которые ещё не получен |
| `cmd` | Имя команды сеанса | Одно из имён, перечисленных в поле `supported_commands` приветственного сообщения |
| `path` | Входной или выходной файл | Только абсолютный путь |
| `document_id` | Стабильная идентичность документа | Передавайте в `open` и в правках |
| `transaction_order` | Порядок изменений | Монотонно растёт: 1, 2, 3… |
| `transition_kind` | Причина или тип перехода | Короткая диагностическая метка |
| `operation_kind` | Семантическая операция | Обязательно для `mutate`. Например `insert-text` |
| `paragraph_index` | Номер абзаца, к которому относится правка | Обязательно для `mutate`. Нумерация с нуля |
## Формат ответа
[Заголовок раздела «Формат ответа»](#формат-ответа)
Успех:
```json
{"id":"ping-1","ok":true,"cmd":"ping","elapsed_ms":0,"result":{"pong":true}}
```
Отказ:
```json
{"id":"bad-1","ok":false,"cmd":"mutate","elapsed_ms":0,"error":"..."}
```
Обработка ошибок
То, что ответ дошёл, ещё не значит, что команда выполнена: это разные вещи. Всегда проверяйте `ok === true`; при `false` сохраняйте `id`, `cmd` и `error` в журнал интеграции.
Для `mutate` одного `ok` мало. Правка, которую ядро не приняло — например с неизвестным `operation_kind`, — возвращает `ok: true`, а исход называет полем `result.mutation.result_kind`: `applied`, если правка применена, `invalid-request`, если отклонена. Проверяйте и его: иначе отклонённая правка сойдёт за выполненную, документ останется прежним, и вы об этом не узнаете.
Состав ответа
| Поле | Когда есть | Что означает |
| ----------------------------- | ---------------------- | -------------------------------------------------------------------------------------------------------------------- |
| `id` | всегда | Метка запроса, возвращается без изменений. Равна `null`, если запрос её не нёс или не был разобран. |
| `ok` | всегда | `true` — команда выполнена, `false` — отклонена. |
| `cmd` | почти всегда | Имя команды. Отсутствует у ответа на `shutdown` и при ошибке разбора запроса — в этом случае имя команды неизвестно. |
| `elapsed_ms` | почти всегда | Время обработки. Отсутствует у ответа на `shutdown`. |
| `result` | при `ok: true` | Результат команды. |
| `error` | при `ok: false` | Строка с причиной отказа. |
| `result.mutation.result_kind` | в ответе на `mutate` | Исход правки: `applied` — применена, `invalid-request` — отклонена. Конверт при этом остаётся `ok: true`. |
| `shutdown` | в ответе на `shutdown` | `true`. Признак того, что сеанс закрывается: другие поля, кроме `id` и `ok`, в этом ответе не приходят. |
Разбирая ответ, не считайте `cmd` и `elapsed_ms` обязательными: на завершении сеанса и на неразобранном запросе их не будет.
Какие бывают operation\_kind
Правка текста абзаца принимает восемь видов: `insert-text` и `delete-text`, их варианты с записью исправлений `insert-text-tracked` и `delete-text-tracked`, `split-paragraph` и `merge-with-next` для разрыва и склейки абзацев, `insert-line-break` и `delete-line-break` для переноса строки. Вид `replace-text` тоже принимается: ядро само раскладывает его на удаление и вставку.
Форматирование выделения идёт теми же запросами с другими видами: `set-bold`, `set-italic`, `set-underline`, `set-font-family`, `set-font-size`, `set-font-color`, `set-text-highlight`, `set-strikethrough` и другие свойства символа.
Для сложных изменений вместо `mutate` предназначена команда `apply-text-transaction` — см. [Типы типизированной правки](/reference/a4/typed-dto/).
# Примеры A4
> Работающие проекты: что именно нужно написать, чтобы встроить редактор документов в своё приложение.
Пример — это не выдержка из кода, а маленький проект целиком: страница, подключение и хост с двумя адресами. Весь его код показан на странице, файл за файлом.
* [Живой пример: редактор в браузереработающий редактор A4 прямо на странице и весь проект, которым он подключается](/a4/demo/)
## Дальше
[Заголовок раздела «Дальше»](#дальше)
* Как подключить у себя, с тремя шагами и обязательными условиями — [Редактор в браузере](/a4/webhost/).
* Из чего состоит система — [Архитектура](/a4/architecture/).
* Что можно вызвать сегодня — [справочник A4](/reference/a4/overview/).
# С чего начать пилот
> Что дать трём вашим специалистам, чтобы они решили — берём или нет: порядок чтения, пять вопросов для первой встречи и признаки удачного пилота.
## Как встроить работающий пример за 30 минут
[Заголовок раздела «Как встроить работающий пример за 30 минут»](#как-встроить-работающий-пример-за-30-минут)
*Цель первого запуска — не промышленная эксплуатация. Цель проще: открыть свой документ внутри своего приложения, изменить его, получить события и убедиться, что сохранённый файл в порядке.*
| Время | Действие | Ожидаемый результат |
| --------- | ------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 0–5 мин | Взять поверхность редактора и любой свой документ; выбрать, в каком окне будете показывать. | Известны три пути: сама программа, исходный документ и отдельный файл для результата. |
| 5–10 мин | Запустить поверхность на свободном локальном порту. | В ответ приходят адрес страницы, разовый пропуск к ней и имя схемы с номером версии — отдельного поля с версией в строке запуска нет, номер стоит в самом имени. |
| 10–15 мин | Создать окно просмотра веб-страниц и открыть в нём этот адрес. | Редактор загружается внутри вашего окна. |
| 15–20 мин | Подключить обработчик сообщений от редактора. | Приходит событие `ready` — можно показать, что документ открыт. |
| 20–25 мин | Изменить первый абзац и сохранить. | Приходят события `dirty` и `saved`, появляется файл с результатом. |
| 25–30 мин | Открыть сохранённый файл заново и остановить поверхность. | Правка на месте; подписки, соединение и процесс закрыты и ничего за собой не оставили. |
### Чем запускается поверхность
[Заголовок раздела «Чем запускается поверхность»](#чем-запускается-поверхность)
```bash
# $CLI — путь к процессору, см. «Установка и проверка»
"$CLI" surface --port 0 \
--doc /absolute/input.docx \
--save /absolute/output.docx
```
### Порядок вызовов на стороне хоста
[Заголовок раздела «Порядок вызовов на стороне хоста»](#порядок-вызовов-на-стороне-хоста)
```plaintext
bootstrap = startSurface(input, output)
webview.onMessage(event => handleA4Event(event))
webview.open(bootstrap.url)
waitFor('ready')
// пользователь редактирует документ
waitFor('dirty'); requestSave(); waitFor('saved')
webview.close(); stopSurface()
```
Что означает «30 минут»
Путь встраивания без Electron — через системный компонент просмотра веб-страниц — существует и описан; для него выдаётся заранее собранный комплект для оценки (Evaluation Kit) — он же упомянут ниже и разобран в [глоссарии](/start/glossary/). Публичный набор для разработчика, единый вход, адаптер хранилища и правила оформления под ваш бренд — отдельная работа после пилота.
## Что дать трём специалистам, чтобы решить — берём или нет
[Заголовок раздела «Что дать трём специалистам, чтобы решить — берём или нет»](#что-дать-трём-специалистам-чтобы-решить--берём-или-нет)
*Сначала общая картина и живой пример. Потом устройство изнутри, контракт сеанса и коммерческие условия.*
### Рекомендуемый порядок чтения
[Заголовок раздела «Рекомендуемый порядок чтения»](#рекомендуемый-порядок-чтения)
1. Раздел [A4 — что это](/a4/overview/) и страницы рядом с ним: объекты и связи, разделение Rust и Web, варианты размещения и [быстрый старт](/a4/quickstart/).
2. **Спецификация архитектуры** — отдельный документ поставки (см. [Состав поставки](/platform/delivery/)): протокол, жизненный цикл, безопасность, версионирование, развёртывание и приёмка в промышленной эксплуатации.
3. **Evaluation Kit** — отдельный артефакт поставки (см. [Состав поставки](/platform/delivery/)): Surface Host, примеры хоста, три DOCX, схемы и типы, известные ограничения и быстрый старт.
4. **Матрица возможностей** — что доказано для выбранной платформы и на ваших реальных документах.
5. **План пилота** — один маршрут «открыть → изменить → событие → сохранить → открыть заново» с условиями, по которым его признают удачным.
### Пять вопросов для первой встречи
[Заголовок раздела «Пять вопросов для первой встречи»](#пять-вопросов-для-первой-встречи)
1. Где должен работать редактор: на рабочих местах под Windows или macOS, в браузере, на телефонах или службой на ваших серверах?
2. Как ваше приложение передаёт документ и куда должно получить новую версию?
3. Какие десять пользовательских действий обязательны в первом пилоте?
4. Какие документы и какого размера ходят у вас на самом деле?
5. Что обязательно по единому входу, аудиту, сети, обновлениям, длительной поддержке версии и месту хранения данных?
### Критерий успешного первого пилота
[Заголовок раздела «Критерий успешного первого пилота»](#критерий-успешного-первого-пилота)
Готово к следующему разговору
Редактор открылся внутри вашего продукта; пользователь изменил настоящий документ; ваше приложение получило события `ready`, `dirty` и `saved`; сохранённый файл открылся заново; права, версии и аудит остались у вас; после закрытия не осталось ни висящих процессов, ни подписок.
### Что читать после этой страницы
[Заголовок раздела «Что читать после этой страницы»](#что-читать-после-этой-страницы)
Спецификация архитектуры встраиваемого редактора A4 — подробный документ для архитекторов и инженеров, к которому переходят, разобрав базовую модель продукта. Он выдаётся вместе с комплектом для оценки; на сайте те же темы разобраны по частям: [устройство редактора](/a4/architecture/), [жизненный цикл сеанса](/a4/lifecycle/), [контракт сеанса](/reference/a4/session-protocol/), [варианты размещения](/a4/deployment/) и [безопасность](/platform/security/).
### Что описано здесь, а что фиксируется отдельно
[Заголовок раздела «Что описано здесь, а что фиксируется отдельно»](#что-описано-здесь-а-что-фиксируется-отдельно)
Здесь описана проверенная архитектурная основа продукта. Публичный набор для разработчика, перечень команд, обязательства по доступности, лицензия и итоговый профиль возможностей фиксируются отдельными приложениями к пилоту и договору.
## Предлагаемый следующий шаг
[Заголовок раздела «Предлагаемый следующий шаг»](#предлагаемый-следующий-шаг)
* 60–90 минут: на одном реальном документе согласовать, какие объекты адресуются, какие события приходят и что показывается в контекстном меню.
* Зафиксировать версию контракта взаимодействия и профиль возможностей пилота.
* Собрать один сквозной пример: щелчок правой кнопкой по ячейке таблицы → ваше контекстное меню → типизированная правка → сохранение и открытие заново.
* После приёмки расширять список поддержанных объектов частями, а не обещать весь набор возможностей Word одним заявлением.
# Попадание по координате, выделение и правка документа
> Перевод координаты щелчка в позицию документа, простая точечная правка с сохранением и предпочтительная типизированная транзакция.
## Координата → позиция документа
[Заголовок раздела «Координата → позиция документа»](#координата--позиция-документа)
Единицы координат
Команда `hit-test-docx` принимает `--x` и `--y` в тысячных долях пикселя. Значение `100000` в примере ниже — это 100 px, а не сто тысяч.
```bash
# $CLI — путь к процессору, см. «Установка и проверка»
"$CLI" hit-test-docx /data/contract.docx \
--x 100000 --y 100000 --format json
```
```json
{
"model_version": "fastdoc-d6-hit-test-snapshot-v2",
"hit_test_mode": "body-line-boxes-line-edge-caret-only",
"result_kind": "body-line-hit",
"page_index": 0,
"paragraph_index": 0,
"paragraph_location": "word/document.xml:body.block[0]",
"line_index": 0,
"caret_slot_index": 1,
"line_scene_x_milli_px": 96000,
"line_scene_y_milli_px": 96000
}
```
Что этот вызов умеет и чего не умеет
Команда отвечает по строкам основного текста и возвращает позицию курсора в строке — это и означает `body-line-boxes-line-edge-caret-only` в поле `hit_test_mode`. Таблицы, врезки и графические объекты она не различает: щелчок по ячейке таблицы даст позицию в строке, а не саму ячейку.
В постоянном сеансе команды попадания нет вовсе — этот вызов отдельный, разовый, и открытого сеанса не требует.
Настольное приложение умеет больше: попадание в текст и в ячейку таблицы. Наружу эти режимы пока не выведены — прежде чем открывать их, нужно определить, какие виды объектов вправе трогать встроившее приложение.
## Простая точечная правка и сохранение
[Заголовок раздела «Простая точечная правка и сохранение»](#простая-точечная-правка-и-сохранение)
Команда `mutate` адресуется номером абзаца и смещением в нём — отсюда «точечная»: правка кладётся ровно в названное место и дальше него не выходит. Она ничего не проверяет: если между чтением и правкой документ изменился, правка ляжет по старым координатам. Годится для сценариев, где сеанс один и между чтением и записью ничего не происходит.
```json
{"id":"edit-1","cmd":"mutate",
"operation_kind":"insert-text","paragraph_index":0,
"offset":7,"text":"A4 "}
{"id":"save-1","cmd":"save","path":"/data/contract-edited.docx"}
```
## Предпочтительная типизированная транзакция
[Заголовок раздела «Предпочтительная типизированная транзакция»](#предпочтительная-типизированная-транзакция)
Команда `apply-text-transaction` несёт идентификаторы запроса, документа, сеанса и вида, а вместе с ними ожидаемые ревизии документа и выделения. Ядро сверяет их до изменения: если документ или выделение ушли вперёд, запрос отклоняется целиком, а не применяется наполовину. Поэтому для интеграции предпочтителен этот путь — см. [Типы типизированной правки](/reference/a4/typed-dto/).
```json
{
"id": "tx-1", "cmd": "apply-text-transaction",
"request": {
"request_id": "req-8", "document_id": "contract-42",
"session_id": "session-1", "view_id": "canvas-1",
"expected_revision": 42, "expected_selection_revision": 17,
"command": {"type":"insert_text",
"at":{"story_id":"body",
"node_id":"paragraph:body.block[0]",
"utf16_offset":7,"affinity":"downstream"},
"text":"A4 "}
}
}
```
# Host API: команды, события и агенты
> Минимальный устойчивый контракт для пилота: жизненный цикл, сохранение, вид, запросы состояния и команды; события ready, dirty, saved и error; маршрут агента.
Контракт для пилота, а не текущий API
Ниже — состав контракта, который предлагается зафиксировать с вами перед пилотом. Из перечисленных методов сегодня в поверхности A4 есть `mount` и `unmount`; остальные имена — предмет договорённости, а не готовые вызовы.
Что работает сейчас: [Rust Session Protocol](/reference/a4/session-protocol/) — 28 команд документного сеанса, и [Интерфейс поверхности в браузере](/reference/a4/surface-api/) — монтирование поверхности и события хоста.
*Для встраивания нужен небольшой устойчивый контракт, а не доступ к сотням внутренних каналов настольного приложения.*
## Минимальный Host API для пилота
[Заголовок раздела «Минимальный Host API для пилота»](#минимальный-host-api-для-пилота)
| Группа | Примеры | Зачем нужна |
| ----------------- | ---------------------------------------------------------------- | -------------------------------------------------- |
| Жизненный цикл | `mount`, `unmount`, `open`, `close` | Создать сеанс и корректно его закрыть. |
| Сохранение | `save`, `saveAs`, `checkpoint` | Вернуть подтверждённый результат в ваше хранилище. |
| Вид | `focus`, `setReadOnly`, `setLocale`, `setTheme` | Вписать поверхность в окружение вашего приложения. |
| Запросы состояния | `getVersion`, `getCapabilities`, `getStatus` | Узнать версию контракта и какие функции доступны. |
| Команды | Правка, форматирование, отмена и повтор, действия рецензирования | Выполнить только разрешённые изменения. |
## События, которые получает ваше приложение
[Заголовок раздела «События, которые получает ваше приложение»](#события-которые-получает-ваше-приложение)
* **`ready`** — документ разобран и поверхность готова принимать согласованные действия.
* **`dirty`** — изменение принято, появилась несохранённая ревизия.
* **`saved`** — запись завершена, результат можно регистрировать как версию у себя.
* **`error`** — операция не выполнена; приходит код и описание причины, в котором нет содержимого документа.
* **`unmounted`** — поверхность убрана из вашего окна; после него сеанс уже не отвечает.
## Агенты и автоматизация
[Заголовок раздела «Агенты и автоматизация»](#агенты-и-автоматизация)
Агент не должен изображать пользователя щелчками. Порядок другой: открыть сеанс, узнать доступные возможности, запросить ограниченный участок документа, найти устойчивую ссылку на нужное место, предложить точечное изменение, применить его с указанием ожидаемой ревизии, проверить результат, сохранить и открыть файл заново. Каждый шаг такого маршрута можно записать в журнал, ограничить правилами и повторить — с щелчками по экрану ничего из этого невозможно.
Office.js и VBA
Это знакомые примеры событий и автоматизации у Microsoft, но не способ встраивания A4. Выполнение макросов VBA и полная совместимость с ними не обещаются: надёжнее работать через типизированные команды, события и точки применения политик.
# Установка и проверка
> Где внутри установленного A4 находится Rust-процессор fastdoc-cli, как проверить сборку и какие маршруты интеграции доступны.
A4 — готовое приложение для macOS, Windows и Linux. Вместе с интерфейсом внутри пакета поставляется нативный исполняемый файл fastdoc-cli. Для встраивания это внешний процессор документов: его можно запускать из своего настольного приложения, серверной обвязки или локального веб-хоста.
## Скачать и проверить
[Заголовок раздела «Скачать и проверить»](#скачать-и-проверить)
Актуальная сборка публикуется на [dl.plantype.ru](https://dl.plantype.ru/a4/release/latest/). Там же лежит `SHA256SUMS` — список контрольных сумм всех файлов выпуска. Сверьте скачанное до установки:
```bash
base=https://dl.plantype.ru/a4/release/latest
curl -fsSLO $base/SHA256SUMS
# имя файла с номером версии берём из того же списка
curl -fLO $base/$(awk '/arm64.dmg$/ {print $2; exit}' SHA256SUMS)
shasum -a 256 -c SHA256SUMS --ignore-missing
```
Для Windows замените образец имени на `arm64.msi` или `x64.msi`, для Linux — на `amd64.deb`, и считайте сумму средствами своей системы: `Get-FileHash` в PowerShell, `sha256sum -c` в Linux.
Образец с `arm64.dmg` выбран не случайно: сборка A4 для macOS есть только под Apple Silicon, для Mac на процессоре Intel в каталоге выпуска её нет. Для Windows публикуются обе архитектуры, для Linux — только x86-64.
Часть поставки для Linux подписана отдельно: рядом с `deb` и `AppImage` лежат файлы `.asc`, и там же открытый ключ, которым подпись проверяется. У пакета `rpm` отделённой подписи в каталоге выпуска нет — для него остаётся сверка контрольной суммы.
```bash
curl -fsSLO $base/fastoffices-signing-key.asc
gpg --import fastoffices-signing-key.asc
curl -fsSLO $base/$(awk '/amd64.deb.asc$/ {print $2; exit}' SHA256SUMS)
gpg --verify A4-*-amd64.deb.asc A4-*-amd64.deb
```
На macOS и Windows подпись проверяет сама система при установке: там работает платформенная подпись, а не эта. Образ для macOS подписан сертификатом Developer ID и заверен у Apple — `spctl` отвечает `accepted, source=Notarized Developer ID`.
В сертификате стоит имя юридического лица, а не название продукта: `Developer ID Application: HEALTH INFORMATIC SECURITY SYSTEM SL (K58GV7E7X4)`. Так и должно быть — сверяйте с этим идентификатором команды, `K58GV7E7X4`.
Установщики для Windows в текущем выпуске цифровой подписи не несут: система покажет предупреждение о неизвестном издателе. Проверяйте такой файл контрольной суммой из `SHA256SUMS` — она для него есть.
* macOS
```bash
CLI="/Applications/A4.app/Contents/Resources/runtime/native/fastdoc-cli"
test -x "$CLI" && echo "процессор найден"
# Показать доступные одноразовые команды
"$CLI" --help | head -n 20
```
* Windows
```powershell
$CLI = "C:\Program Files\A4\resources\runtime\native\fastdoc-cli.exe"
if (Test-Path $CLI) { "процессор найден" }
# Показать доступные одноразовые команды
& $CLI --help | Select-Object -First 20
```
* Linux
```bash
CLI="/opt/A4/resources/runtime/native/fastdoc-cli"
test -x "$CLI" && echo "процессор найден"
# Показать доступные одноразовые команды
"$CLI" --help | head -n 20
```
Это путь для пакетов `deb` и `rpm`. Поставка `AppImage` работает без установки, каталога у неё нет — распакуйте образ и возьмите процессор изнутри:
```bash
./A4-….AppImage --appimage-extract
CLI="$PWD/squashfs-root/resources/runtime/native/fastdoc-cli"
```
Ожидаемый результат
В терминале появляется строка «процессор найден», а следом — начало списка команд. Если приложение установлено в другой каталог, замените путь в первой строке: всё, что после каталога установки, одинаково во всех системах — `resources/runtime/native`.
## Архитектура внешней интеграции
[Заголовок раздела «Архитектура внешней интеграции»](#архитектура-внешней-интеграции)
**Ваша оболочка** → **сеанс JSONL или WebSocket** → **`fastdoc-cli`** → **DOCX и модель документа**
| Маршрут | Когда использовать | Транспорт | Готовность |
| ---------------- | ----------------------------------------------------- | -------------------------- | ------------------------------------------------------------------------------------------------ |
| Сеанс JSONL | Первый прототип, настольная или серверная обвязка | stdin и stdout | Готов; с него стоит начинать |
| WebSocket | Веб- или настольная оболочка с асинхронными запросами | `ws://127.0.0.1` и пропуск | Готов |
| Web Surface | Посмотреть встраивание на живом документе | HTTP и WebSocket сеанса | Работает, но правка ограничена первым абзацем; после перезагрузки страницы нужно переподключение |
| Разовый вызов | Пакетные операции и автоматизация | аргументы процесса и JSON | Готов |
| Electron preload | Только внутри собственного окна A4 | contextBridge и IPC | Внутренний контракт, для встраивания не предназначен |
## Что дальше
[Заголовок раздела «Что дальше»](#что-дальше)
Процессор на месте — дальше есть три пути, в зависимости от того, что нужно проверить первым.
* **Убедиться, что API отвечает.** [Быстрый старт](/a4/quickstart/): открыть DOCX, изменить абзац командой и сохранить результат.
* **Посмотреть встраивание вживую.** [Запуск Web Surface](/a4/surface/): поверхность открывается в браузере поверх того же процессора. Правка в ней ограничена первым абзацем — это демонстрация пути, не редактор.
* **Понять формат запросов.** [Объекты запросов и ответов](/a4/envelope/) и полный перечень в [справочнике команд](/reference/a4/session-protocol/).
Если что-то не запускается, начните с [диагностики](/a4/diagnostics/).
# Контракт взаимодействия для пилота
> Устойчивая ссылка на место в документе, минимальный набор методов и события, которые фиксируются до начала интеграции.
Это предлагаемый контракт, а не текущий API
Ниже — контракт, который предлагается зафиксировать в спецификации и закрепить версиями. Сегодняшняя поверхность отдаёт наружу меньше: что именно из этого уже работает, сказано в [Готовности и границах](/a4/status/). Писать код по этой странице рано — по ней согласовывают, каким интерфейс станет.
## Семантическая цель
[Заголовок раздела «Семантическая цель»](#семантическая-цель)
Чтобы ваше приложение могло сказать «что сделать» вместо «куда щёлкнуть», нужна общая единица адресации: устойчивая ссылка на место в документе. `DocumentTarget` описывает именно её — вид элемента, его идентификатор и ревизию, в которой этот идентификатор действителен. Ссылок на DOM-узлы и координаты в этом контракте нет намеренно: они меняются при перерисовке.
```ts
export interface DocumentTarget {
kind: "paragraph" | "run" | "table" | "tableCell" |
"image" | "comment" | "field" | "header" | "footer" |
"hostObject";
nodeId: string; // стабильный ID в пределах revision
revision: number;
ancestors: Array<{ kind: string; nodeId: string }>;
capabilities: string[]; // read, editText, insertRow, openProperties...
hostData?: { type: string; id: string };
}
```
## Минимальный набор методов сеанса
[Заголовок раздела «Минимальный набор методов сеанса»](#минимальный-набор-методов-сеанса)
Набор методов, которого хватает для пилота: прочитать состояние документа, определить объект по точке, узнать, доступна ли команда, выполнить одну команду или пакет, сохранить. Всё, что меняет документ, возвращает подтверждение с новой ревизией — по нему вы понимаете, что изменение принято.
```ts
interface A4DocumentSession {
getDocumentSnapshot(options?: { depth: "semantic" }): Promise;
getProjection(request: ProjectionRequest): Promise;
hitTest(point: PagePoint): Promise;
getSelection(): Promise;
getCommandState(target: DocumentTarget): Promise;
execute(request: ExecuteRequest): Promise;
executeBatch(request: AtomicBatchRequest): Promise;
save(request: SaveRequest): Promise;
}
```
## События
[Заголовок раздела «События»](#события)
| Событие | Когда приходит | Обязательные поля |
| -------------------------- | ------------------------------------------------------------ | ------------------------------------------------------- |
| `selectionChanged` | Изменилось выделение или положение курсора | выделение, ревизия, источник изменения |
| `elementActivated` | Щелчок, двойной щелчок или активация с клавиатуры | вид жеста, объект, точка попадания, выделение |
| `contextMenuRequested` | Перед показом контекстного меню | идентификатор запроса, объект, точка, встроенные пункты |
| `contentChanged` | Команда изменила документ | новая ревизия, подтверждение, какие узлы изменились |
| `commandStateChanged` | Команда стала доступна или недоступна, изменилась её отметка | объект, новые состояния |
| dirtyChanged, saved, error | Изменения, сохранение и ошибки | ревизия, путь к файлу или описание ошибки |
## Дальше
[Заголовок раздела «Дальше»](#дальше)
* Как поверхность встраивается в ваше окно — [Встраивание Surface](/a4/embedding/).
* Что приходит в событиях — [Host API](/a4/host-api/).
# Жизненный цикл документа
> Семь шагов от передачи документа до повторного открытия и основные объекты интерфейса: сеанс, команда, конверт результата, событие, профиль возможностей, артефакт.
*Интеграция строится вокруг сеанса, команд, ревизий и событий, а не вокруг прямого доступа к внутренней разметке редактора.*
1. Ваше приложение получает документ. Ваша система проверяет пользователя и передаёт байты разрешённого DOCX либо безопасный путь к файлу.
2. A4 открывает сеанс. Ядро разбирает файл, строит основную модель документа, профиль доступных возможностей и первую ревизию.
3. Поверхность показывает документ. Пользователь видит страницы и доступные команды, а ваше приложение получает событие готовности.
4. Приходит намерение. Команда несёт тип операции, устойчивую ссылку на место в документе и ожидаемую ревизию; оболочка сама разметку не правит.
5. Ядро применяет или отклоняет. При успехе появляется новая ревизия и признак несохранённых изменений. Отказ не оставляет документ изменённым наполовину.
6. Ваше приложение сохраняет результат. Ядро записывает изменения в файл и отдаёт DOCX, а вы заводите версию и запись в журнале.
7. Повторное открытие подтверждает. Сохранённый файл открывается заново в новом процессе — это доказывает, что изменение дошло до диска, а не осталось на экране.
## Основные объекты API
[Заголовок раздела «Основные объекты API»](#основные-объекты-api)
| Объект | Назначение |
| ---------------------- | ---------------------------------------------------------------------------------------------------------------- |
| Сеанс | Изолированный жизненный цикл одного открытого документа. |
| Команда, или намерение | Типизированное действие: что сделать, с чем и при какой ожидаемой ревизии. |
| Конверт результата | Успех, предупреждения, явный отказ с причиной, диагностика и идентификатор, по которому ответ связан с запросом. |
| Событие | Готовность, несохранённые изменения, сохранение, ошибка и прочие смены состояния. |
| Профиль возможностей | Перечень доступных команд и ограничений — без умолчаний. |
| Артефакт | Байты DOCX, контрольная точка или диагностика — всё, чем распоряжается ваше приложение. |
Как публикуется результат
Сохранение не пишет поверх существующего файла: сначала создаётся временный файл рядом с целевым, содержимое сбрасывается на диск, и только потом временный файл атомарно занимает место цели. Поэтому оборванное сохранение не оставляет наполовину записанный DOCX — на диске либо прежняя версия, либо новая целиком.
Для вашего приложения это значит: появление файла по нужному пути уже само по себе признак завершённой записи — ждать и проверять размер не нужно.
Сохранение контрольной точкой делает ещё один шаг: перед публикацией оно сверяет файл с тем состоянием, которое видело ваше приложение, и отказывает, если его успели изменить снаружи — другой программой, вторым сеансом или синхронизацией. Чужая правка не затирается молча.
## Дальше
[Заголовок раздела «Дальше»](#дальше)
* Как устроены запросы и ответы — [Объекты запросов и ответов](/a4/envelope/).
* Что продукт делает с документом — [карта возможностей](/a4/capabilities/).
* Границы сегодня — [Готовность и границы](/a4/status/).
# A4 — что это
> Встраиваемый DOCX-редактор для документарных систем: что A4 добавляет вашему продукту, что остаётся на вашей стороне и как это выглядит для сотрудника.
**A4** — документная часть платформы FastOffice: DOCX-совместимый редактор и процессор документов, который встраивается в ваше приложение, оставляя документную истину в ядре на Rust.
Одной фразой
Вы сохраняете свой бренд, пользователей, хранилище и процессы, а A4 добавляет внутрь вашего продукта полноценную работу с документом.
*Свой продукт заменять не нужно: в него добавляется документный процессор и готовая пользовательская поверхность.*
## Что уже умеет ваша система
[Заголовок раздела «Что уже умеет ваша система»](#что-уже-умеет-ваша-система)
Карточка документа, пользователи и роли, маршруты согласования, версии, электронная подпись, архив, аудит, интеграции с вашими системами — перевозок, ресурсов предприятия, работы с клиентами — и правила хранения остаются на вашей стороне.
## Что добавляет A4
[Заголовок раздела «Что добавляет A4»](#что-добавляет-a4)
* **Открыть** — получить DOCX от вашей системы и открыть управляемый сеанс документа.
* **Показать** — отрисовать страницы и дать пользователю привычную поверхность редактирования.
* **Изменить** — принять действия пользователя или агента как ограниченные команды, а не произвольную правку ZIP/XML.
* **Сохранить** — собрать корректный DOCX и вернуть файл или подтверждённый результат обратно в вашу систему.
* **Сообщить** — передать события ready, dirty, saved, error и другие согласованные состояния.
Главная граница
A4 не становится владельцем карточки документа или бизнес-процесса. Он отвечает за то, что находится внутри открытого документа и как это содержимое меняется.
## Как это выглядит для сотрудника
[Заголовок раздела «Как это выглядит для сотрудника»](#как-это-выглядит-для-сотрудника)
Сотрудник открывает документ из привычной карточки. Редактор появляется внутри того же окна и под тем же брендом. Пользователь редактирует согласованные элементы, а при сохранении новая версия возвращается в существующий контур хранения и аудита — без ручной выгрузки в отдельный Word и обратной загрузки.
Увидеть вживую
Редактор работает прямо на странице документации: [живой пример](/a4/demo/).
## С какими файлами работает
[Заголовок раздела «С какими файлами работает»](#с-какими-файлами-работает)
Основной формат — DOCX: его открывает и правит сеанс, им же документ сохраняется обратно. Остальные форматы обслуживаются отдельными вызовами процессора, а не сеансом: они переводят файл в документ и обратно. PDF — единственный, который идёт только на выход: печатать в него можно, открывать из него нельзя.
| Формат | В документ | Из документа | Чем делается |
| ------------------ | ---------- | ------------ | --------------------------------------------------------------------------------------------------- |
| DOCX | Да | Да | Основной формат: открыть, править и сохранить обратно тем же сеансом — `open`, `mutate`, `save` |
| PDF | — | Да | `export-pdf`, `export-pdf-embedded` |
| ODT | Да | Да | `import-odt`, `export-odt`; правимая копия — `create-editable-odt-copy`, `mutate-editable-odt-copy` |
| RTF | Да | Да | `import-rtf`, `export-rtf` |
| HTML | Да | Да | `import-html`, `export-html` |
| Обычный текст | Да | Да | `import-txt`, `export-txt` |
| EPUB | Да | Да | `import-epub`, `export-epub` |
| FB2 | Да | Да | `import-fb2`, `export-fb2` |
| XODT — плоский ODT | Да | Да | `import-xodt`, `export-xodt` |
Плоский ODT — тот же OpenDocument, но одним файлом XML вместо zip-пакета: удобно, когда документ хранится в системе контроля версий или его правит внешний инструмент. У таблиц то же самое называется FODS.
Отдельно есть конвертация файла целиком — `convert-doc` — и каталога разом: `convert-doc-batch` с указанием расширения и, при нужде, обходом вложенных папок.
Про PDF/A-1b — формат архивного хранения
Выгрузка обычного PDF работает, в том числе со встроенными шрифтами (`export-pdf-embedded`). Полного PDF/A-1b продукт пока не обещает: команда `preflight-pdfa-1b` проверяет документ на готовность и честно отвечает, что выгрузка в PDF/A ещё закрыта, а `validate-pdfa-1b` проводит её через внешнюю программу проверки, которую вы указываете сами. Если архивный формат обязателен по вашим правилам хранения, это условие пилота, а не готовая возможность.
Полный перечень вызовов — в [справочнике](/reference/a4/overview/). Для таблиц то же самое собрано матрицей: [что можно с каждым форматом](/reference/f1/formats/).
## Что ещё делает процессор, кроме правки
[Заголовок раздела «Что ещё делает процессор, кроме правки»](#что-ещё-делает-процессор-кроме-правки)
Документ в работе редко просто правят. Ниже — вызовы, которые закрывают остальную часть договорного оборота; все они одноразовые, как и работа с форматами, и сеанса для них не нужно.
| Задача | Что происходит | Вызов |
| ------------------------------- | --------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------- |
| Сличить две версии | Из исходного и изменённого файла собирается третий, где расхождения показаны исправлениями, с указанием автора и даты | `review-compare-combine-docx --mode compare` |
| Свести правки рецензентов | Правки из нескольких файлов сводятся в один документ | `review-compare-combine-docx --mode combine` |
| Разослать по списку | Шаблон и список получателей в CSV дают пачку готовых документов; можно взять только часть списка | `mail-merge-business-docx --source … --output-dir …` |
| Посмотреть макросы, не запуская | Из файла с макросами выдаётся их состав — без выполнения | `inspect-vba` |
| Проверить защиту файла | Показывает, чем документ защищён | `inspect-protection` |
| Обновить оглавление | Перестраивает оглавление или список иллюстраций, либо убирает оглавление | `update-toc-docx`, `update-table-of-figures-docx`, `remove-toc-docx` |
Это лишь часть: колонтитулы, разделы, водяные знаки, поля форм, библиография, печать и рассылка собраны по задачам на отдельной странице — [что A4 умеет с документом](/a4/capabilities/).
Термины, которые встречаются во всех разделах — сеанс, ревизия, проекция, явный отказ, — собраны в [глоссарии](/start/glossary/).
## Word-класс внутри вашего продукта
[Заголовок раздела «Word-класс внутри вашего продукта»](#word-класс-внутри-вашего-продукта)
| Слой | Ответственность | Что это даёт |
| ------------------------ | ------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- |
| Ваше приложение | Окно, бренд, вход, хранилище, процессы и их жизненный цикл. | Продукт остаётся вашим продуктом. |
| Адаптер на вашей стороне | Запускает процесс редактора, читает параметры подключения, передаёт события окна, корректно завершает работу. | Встраивание остаётся под вашим управлением. |
| Поверхность редактора | Полотно документа и элементы управления, ввод пользователя, вызовы к вашему приложению и события наружу. | Сотрудник работает с документом внутри вашего окна. |
| Служба поверхности | Отдаёт файлы редактора и параметры подключения, держит защищённый канал и один сеанс документа. | Работает на самой машине и слушает только локальный адрес — наружу ничего не открывается. |
| Сеанс документа | Команды, выделение, история правок, модель документа, вёрстка и сохранение. | Один документ — один сеанс, с общей историей и общими событиями. |
| Ядро и формат документа | Разбор файла, смысловая модель, сохранение частей, которые редактор не показывает, вёрстка и запись обратно. | Содержимое файла определяет формат Word, а не разметка на экране: сохранить и открыть заново можно без потерь. |
### Что получает пользователь в Word-коридоре
[Заголовок раздела «Что получает пользователь в Word-коридоре»](#что-получает-пользователь-в-word-коридоре)
* Открытие согласованных DOCX-документов внутри вашего приложения без отдельного окна A4.
* Правка, разбивка на страницы, сохранение, повторное открытие и проверка того, что документ остался собой.
* В базовом наборе — события `ready`, `dirty`, `saved`, `error` и `unmounted`; для поставки под вашей маркой к ним добавляются события выделения, содержимого и состояния команд.
* Оформление под ваш бренд: тема, язык, режим только для чтения, переменные оформления и собственная оболочка вместо нашей.
* Работа без интерфейса — для пакетной обработки, служб, конвейеров и действий агентов.
### Граница честного обещания
[Заголовок раздела «Граница честного обещания»](#граница-честного-обещания)
* Openability не равна fidelity: capability profile должен отдельно фиксировать чтение, отображение, редактирование, сохранение и preservation по семействам OOXML.
* Полная совместимость с Microsoft Word, Office.js и выполнение VBA-макросов не входят без отдельного соглашения.
* Production-поставка требует acceptance по платформам, save/reopen, lifecycle, security и packaging.
# Быстрый старт: первый JSONL-сеанс
> За 10–15 минут: запустить fastdoc-cli, открыть DOCX, изменить первый абзац командой mutate, сохранить результат и проверить его без GUI.
Это самый короткий путь от установленного A4 до доказанного цикла «команда → изменение модели → запись DOCX → повторное чтение».
Что получится через 10–15 минут
Rust-процесс ответит на `ping` и сообщит протокол `fastdoc.session-serve.v1`; DOCX будет открыт, его первый абзац изменится через API и сохранится отдельным файлом; результат откроется в A4.
Что нужно до начала
Команды ниже выполняются в подготовленной папке `~/Desktop/A4-API-first-run`. Её вместе со скриптами `a4-api-demo.mjs` и `start-web-demo.sh` даёт **Evaluation Kit** — отдельный артефакт поставки (см. [Состав поставки](/platform/delivery/)). В установщик A4 он не входит и с [dl.plantype.ru](https://dl.plantype.ru/a4/release/latest/) не скачивается: его запрашивают у поставщика.
**Если Evaluation Kit ещё не получен**, установленного A4 достаточно, чтобы пройти тот же путь вручную: `fastdoc-cli` лежит внутри приложения (см. [Установка и проверка](/a4/install/)), формат запросов описан в [Объектах запросов и ответов](/a4/envelope/), полный перечень команд — в [справочнике](/reference/a4/session-protocol/). Пример полностью выписанного скрипта есть у F1: [проверенный скрипт](/f1/reference-script/).
## Первый настоящий вызов: JSONL-сеанс
[Заголовок раздела «Первый настоящий вызов: JSONL-сеанс»](#первый-настоящий-вызов-jsonl-сеанс)
JSONL — самый простой контракт для первой интеграции: один JSON-запрос в строке stdin, один JSON-ответ в строке stdout. Rust хранит состояние открытого документа между командами.
### Подготовка
[Заголовок раздела «Подготовка»](#подготовка)
1. Откройте терминал.
2. Проверьте Node.js командой `node --version` — нужна версия 22 LTS или новее.
3. Перейдите в подготовленную папку на рабочем столе.
4. Запустите демонстрационный клиент, передав явные пути входного и выходного файла.
* macOS
```bash
cd ~/Desktop/A4-API-first-run
node --version
node a4-api-demo.mjs \
"/Applications/A4.app/Contents/Resources/app/fixtures/docx/simple_text.docx" \
"$PWD/api-demo-result.docx"
```
* Windows
```powershell
cd $HOME\Desktop\A4-API-first-run
node --version
node a4-api-demo.mjs `
"C:\Program Files\A4\resources\app\fixtures\docx\simple_text.docx" `
"$PWD\api-demo-result.docx"
```
* Linux
```bash
cd ~/Desktop/A4-API-first-run
node --version
node a4-api-demo.mjs \
"/opt/A4/resources/app/fixtures/docx/simple_text.docx" \
"$PWD/api-demo-result.docx"
```
### Что делает скрипт
[Заголовок раздела «Что делает скрипт»](#что-делает-скрипт)
1. Запускает `fastdoc-cli serve` из пакета приложения.
2. Читает приветствие протокола и список из 28 команд.
3. Отправляет `ping`, затем `open` и `model`.
4. Командой `mutate` вставляет текст «API DEMO:» в начало первого абзаца.
5. Читает `status` и сохраняет копию командой `save`.
6. Завершает процесс командой `shutdown`.
### Фактический вывод проверенного прогона
[Заголовок раздела «Фактический вывод проверенного прогона»](#фактический-вывод-проверенного-прогона)
```plaintext
Connected: fastdoc.session-serve.v1
Commands available: 28
Ping: true
Session: rust-session-20320-1
Before: This is a sample word document. It has two pages, but no headers or footers.
After: API DEMO: This is a sample word document. It has two pages, but no headers or footers.
Revision: 1; dirty: true
Saved: /Users/tester/Desktop/A4-API-first-run/api-demo-result.docx
```
Критерий успеха
Ping: true, revision=1, dirty=true и новый api-demo-result.docx на диске.
## Открыть результат и увидеть изменение
[Заголовок раздела «Открыть результат и увидеть изменение»](#открыть-результат-и-увидеть-изменение)
* macOS
```bash
open -a A4 ~/Desktop/A4-API-first-run/api-demo-result.docx
```
* Windows
```powershell
& "C:\Program Files\A4\A4.exe" "$HOME\Desktop\A4-API-first-run\api-demo-result.docx"
```
* Linux
```bash
/opt/A4/a4 ~/Desktop/A4-API-first-run/api-demo-result.docx
```
В начале первого абзаца должен появиться префикс API DEMO:. Это подтверждает полный цикл: команда пришла в Rust, изменила модель документа, записалась в DOCX и была прочитана графическим редактором.
Рисунок 1. Реальный результат API-прогона в установленной A4: первый абзац начинается с «API DEMO:». Скриншот сделан 26 августа 2026.
### Проверить сохранённый файл без GUI
[Заголовок раздела «Проверить сохранённый файл без GUI»](#проверить-сохранённый-файл-без-gui)
* macOS
```bash
CLI="/Applications/A4.app/Contents/Resources/runtime/native/fastdoc-cli"
"$CLI" model-docx \
"$HOME/Desktop/A4-API-first-run/api-demo-result.docx" \
--format json
```
* Windows
```powershell
$CLI = "C:\Program Files\A4\resources\runtime\native\fastdoc-cli.exe"
& $CLI model-docx `
"$HOME\Desktop\A4-API-first-run\api-demo-result.docx" `
--format json
```
* Linux
```bash
CLI="/opt/A4/resources/runtime/native/fastdoc-cli"
"$CLI" model-docx \
"$HOME/Desktop/A4-API-first-run/api-demo-result.docx" \
--format json
```
Что искать в JSON
В body.blocks найдите первый объект kind=paragraph. Его text должен начинаться с API DEMO:. Это прямой доступ к структуре документа, а не чтение изображения или PDF.
## Что дальше
[Заголовок раздела «Что дальше»](#что-дальше)
* Разобрать формат запроса и ответа — [Объекты запросов и ответов](/a4/envelope/).
* Те же команды, но по WebSocket — [Тот же контракт через WebSocket](/a4/websocket/).
* Показать документ пользователю — [Запуск Web Surface](/a4/surface/), а затем [Встраивание Surface](/a4/embedding/).
* Полный перечень команд сеанса — [справочник](/reference/a4/session-protocol/).
* Что-то не запускается — [Диагностика и контрольный список](/a4/diagnostics/).
# Рецепты интеграции
> Реакция на щелчок и двойной щелчок, контекстное меню для ячейки таблицы, выполнение внешней команды и полный сценарий транспортной системы от события до DOCX.
Это предлагаемый API, а не текущий
Примеры ниже показывают, как предлагается устроить реакцию на действия пользователя: события элементов, поставщики контекстного меню и отправку внешних команд. Такого API в текущем контракте A4 **нет** — писать по этим примерам рабочий код сегодня нельзя.
Что доступно сейчас: [Rust Session Protocol](/reference/a4/session-protocol/) — 28 команд документного сеанса, и [Интерфейс поверхности в браузере](/reference/a4/surface-api/) — встраивание поверхности и события хоста. Форма будущего профиля описана в [контракте взаимодействия](/a4/interaction/); она фиксируется договором до начала интеграции.
## Примеры: щелчок, двойной щелчок и меню
[Заголовок раздела «Примеры: щелчок, двойной щелчок и меню»](#примеры-щелчок-двойной-щелчок-и-меню)
### Как отвечать на щелчок и двойной щелчок
[Заголовок раздела «Как отвечать на щелчок и двойной щелчок»](#как-отвечать-на-щелчок-и-двойной-щелчок)
```js
editor.interactions.on("elementActivated", async (event) => {
const { gesture, target } = event;
if (gesture === "click" && target.kind === "comment") {
hostSidebar.openComment(target.nodeId);
}
if (gesture === "doubleClick" &&
target.hostData?.type === "shipment") {
await hostApp.openShipment(target.hostData.id);
}
});
```
### Контекстное меню для ячейки таблицы
[Заголовок раздела «Контекстное меню для ячейки таблицы»](#контекстное-меню-для-ячейки-таблицы)
```js
editor.contextMenu.registerProvider("transport-suite", async (ctx) => {
if (ctx.target.kind !== "tableCell") return [];
return [
{ id: "transport.openCargo", label: "Открыть карточку груза",
enabled: Boolean(ctx.target.hostData?.id) },
{ id: "transport.bindRoute", label: "Привязать к маршруту",
enabled: !ctx.readOnly },
{ type: "separator" },
{ id: "a4.table.insertRowBelow", label: "Вставить строку ниже",
command: { type: "insert_table_row", position: "below" } }
];
});
```
### Выполнение внешней команды
[Заголовок раздела «Выполнение внешней команды»](#выполнение-внешней-команды)
```js
editor.commands.register("transport.bindRoute", async (ctx) => {
const route = await hostUi.chooseRoute();
if (!route) return;
return ctx.session.execute({
operationId: crypto.randomUUID(),
expectedRevision: ctx.revision,
target: ctx.target,
command: { type: "replace_target_text", text: route.displayName }
});
});
```
Почему меню декларативное
Вы возвращаете идентификатор пункта, подпись, состояние и типизированную команду — а рисует меню и проверяет права сам редактор. Так у всех пунктов остаётся одинаковое поведение, перевод, доступность с клавиатуры и единая проверка безопасности. Своей разметки и своих скриптов внутри полотна для этого не нужно.
## Сценарий транспортной системы от события до DOCX
[Заголовок раздела «Сценарий транспортной системы от события до DOCX»](#сценарий-транспортной-системы-от-события-до-docx)
### Разбор по шагам: щелчок правой кнопкой по строке груза
[Заголовок раздела «Разбор по шагам: щелчок правой кнопкой по строке груза»](#разбор-по-шагам-щелчок-правой-кнопкой-по-строке-груза)
* **Пользователь щёлкает правой кнопкой.** Поверхность переводит координаты щелчка в координаты страницы документа и спрашивает ядро, во что попали.
* **Ядро отвечает, что под указателем.** Например ячейка таблицы: её устойчивый идентификатор, номера строки и столбца, объемлющие элементы и номер ревизии, в которой всё это верно.
* **Редактор спрашивает ваши поставщики пунктов меню**, передавая им этот контекст — и только его, а не ссылку на элемент разметки.
* **Ваш поставщик добавляет свои действия.** «Открыть карточку груза» и «Привязать к маршруту» встают рядом с разрешёнными встроенными пунктами.
* **Пользователь выбирает маршрут.** Ваш интерфейс возвращает его идентификатор; команда на изменение документа собирается отдельно — выбор в вашей системе и правка в документе остаются разными действиями.
* **Ядро принимает команду или отклоняет её.** Ожидаемая ревизия и перечень допустимых действий не дают применить правку к устаревшему документу или к объекту, для которого она не предусмотрена.
* **Поверхность обновляется.** События `contentChanged` и `commandStateChanged` приносят новый номер ревизии, и перерисовываются только затронутые страницы, а не весь документ.
* **Сохранение и повторное открытие подтверждают результат.** Документ сохраняется отдельным файлом либо публикуется разом, а затем читается заново — так проверяется, что на диск легло именно то, что видел пользователь.
### Как выглядит переданный контекст
[Заголовок раздела «Как выглядит переданный контекст»](#как-выглядит-переданный-контекст)
```json
{
"schema": "a4.interaction-context.v1",
"gesture": "contextMenu",
"revision": 42,
"readOnly": false,
"target": {
"kind": "tableCell",
"nodeId": "body.block[18].row[2].cell[1]",
"ancestors": [{"kind":"table","nodeId":"body.block[18]"}],
"capabilities": ["read","editText","insertRow"],
"hostData": {"type":"shipment","id":"SHP-009173"}
},
"hit": {"pageIndex":3,"xMilliPx":412000,"yMilliPx":286000}
}
```
### Что приходит при устаревшей ревизии
[Заголовок раздела «Что приходит при устаревшей ревизии»](#что-приходит-при-устаревшей-ревизии)
```json
{
"ok": false,
"error": {
"code": "revision_conflict",
"expectedRevision": 42,
"actualRevision": 43,
"retry": "refresh_target_and_command_state"
}
}
```
Как отказ выглядит в текущем контракте
Формат ответа выше — часть предлагаемого API. Сегодня сеанс отвечает иначе: поле `error` содержит строку, а не объект с кодом.
```json
{"id":"tx-1","ok":false,"cmd":"apply-text-transaction","elapsed_ms":1,
"error":"stale revision: expected 43, received 42"}
```
Читать эту строку нужно со стороны ядра: `expected` — ревизия, на которой документ сейчас находится, `received` — та, что пришла в запросе. То есть здесь документ ушёл на 43, а команда была собрана для 42.
Разбор ответа описан в [Объектах запросов и ответов](/a4/envelope/).
# Ответственность и безопасность
> Что относится к вам, что к A4 и что согласуется совместно; где можно настраивать под свой бренд, какие короткие пути запрещены и как устроена минимальная модель безопасности.
*Поставка под своим брендом даёт свободу в оформлении и процессах, но не позволяет завести вторую версию документа — в JavaScript или в коде расширений.*
| За что отвечаете вы | За что отвечает A4 | Согласуется совместно |
| ---------------------------------------- | ----------------------------------------------------- | ------------------------------------------------------------------------- |
| Бренд, навигация, карточка документа | Ядро на Rust: модель, вёрстка, сцена, запись в файл | Профиль возможностей и набор команд |
| Пользователи, вход, роли | Жизненный цикл поверхности и события документа | Требования безопасности и журналирование |
| Хранилище, версии, сроки хранения, аудит | Поддержанная часть формата DOCX | Форматы, размеры файлов, набор документов для приёмки |
| Смежные системы и процессы | Явные отказы и защита от правки по устаревшей ревизии | Упаковка, обновления, срок поддержки версии, обязательства по доступности |
| Распространение продукта | Происхождение сборки и версия контракта | Порядок пилота и модель поддержки |
## Что можно менять под свой бренд
[Заголовок раздела «Что можно менять под свой бренд»](#что-можно-менять-под-свой-бренд)
Название, логотип, внешняя навигация, тема, язык интерфейса, режим только для чтения, разрешённые переменные оформления, видимость согласованных команд, справка и отраслевые панели вашего приложения.
## Запрещённые короткие пути
[Заголовок раздела «Запрещённые короткие пути»](#запрещённые-короткие-пути)
Прямая запись в файл документа из поверхности; попытка изменить документ правкой того, что нарисовано на экране; выдача внутренних каналов настольного приложения за публичный интерфейс; обращение к сети или чтение файлов без объявленной возможности; запись в журнал пропусков, содержимого документа и секретов; тихий обходной путь, меняющий смысл результата.
## Минимальная модель безопасности
[Заголовок раздела «Минимальная модель безопасности»](#минимальная-модель-безопасности)
Служба поверхности слушает только локальный адрес — снаружи к ней не подключиться. Каждый её запуск получает новый случайный пропуск длиной 256 бит, и действует он ровно столько, сколько работает сама поверхность. Документы не покидают ваш контур, пока вы сами не подключите внешний сервис. Ошибка приходит явным отказом с причиной, и изменение не оставляет наполовину записанный файл. Промышленная поставка фиксирует контрольные суммы, версию, платформу и архитектуру, порядок работы с уязвимостями и правила обновления.
Честное обещание
Фразы «поддерживает DOCX» мало: она ничего не обещает. Договариваться нужно по матрице возможностей, где для каждой пары «формат и функция» сказано, что читается, что записывается, что сохраняется без потерь, на каких платформах и с какими ограничениями.
## Дальше
[Заголовок раздела «Дальше»](#дальше)
* Как это проверяется до договора — [Пилот и приёмка](/platform/pilot/).
* Что настраивается под ваш бренд — [Настройка под свой бренд](/platform/white-label/).
# Готовность и границы
> Граница между реализованным и пилотным, список договорённостей до старта и готовая формулировка ответа внешней команде.
Ниже — граница между тем, что работает сегодня, и тем, что фиксируется контрактом пилота. Что продукт делает с документом по задачам — колонтитулы, оглавления, сравнение версий, печать — собрано в [карте возможностей](/a4/capabilities/).
Как читать «в описанных границах»
Возможность работает в названных пределах, а за ними отказывает предсказуемо — не тихо и не наполовину. Что именно входит в границы, указано в той же строке таблицы. Термин разобран в [глоссарии](/start/glossary/).
## Что доступно сейчас, а что входит в пилот
[Заголовок раздела «Что доступно сейчас, а что входит в пилот»](#что-доступно-сейчас-а-что-входит-в-пилот)
| Возможность | Состояние | Как использовать или при каком условии |
| --------------------------------------------------- | ----------------------------------- | -------------------------------------------------------------------------------------------------- |
| Смысловая структура | Доступно | команда `model` в сеансе или `model-docx` отдельным вызовом |
| Вёрстка, страницы, сцена | Доступно | `compose`, `paginate`, `print`, `print-window`, `print-delta` |
| Рецензирование и стили | Доступно | проекции `review` и `style` |
| Правка текста | Доступно | `mutate` или `apply-text-transaction` |
| Выделение, история, сохранение | Доступно | `set-selection`, отмена и повтор, `save` и контрольные точки |
| Отдельный поиск попадания | В описанных границах | Строка основного текста, её край и позиция курсора; остальные типы объектов добавляются постепенно |
| Поиск попадания в текст и таблицы внутри приложения | Работает, но контрактом не является | Не обращаться напрямую: маршрут внутренний и меняется без предупреждения |
| Контекстные меню текста и таблиц | Работает, но контрактом не является | Внутри продукта работает; способ подключить своё меню ещё не зафиксирован |
| События Surface Host | Доступно в описанном наборе | `ready`, `dirty`, `saved`, `error`, `unmounted` |
| События выделения, содержимого и команд | Фиксируется в пилоте | Нужно договориться о составе, порядке доставки, повторах и жизненном цикле |
| Своё контекстное меню | Фиксируется в пилоте | Описательный список пунктов и типизированная отправка команд |
| Прямая правка разметки страницы или файла | Запрещено | Только через команды сеанса и запись файла ядром |
### Что подтверждает реализуемость
[Заголовок раздела «Что подтверждает реализуемость»](#что-подтверждает-реализуемость)
* **Попадание в текст.** По координате щелчка возвращается страница, строка, позиция в строке и сам текст.
* **Попадание в ячейку таблицы.** Возвращается страница, блок, строка и колонка, включая вложенные таблицы.
* **Контекстные меню.** Отдельные маршруты обслуживают текст и таблицы.
* **Двойной щелчок.** Обслуживаются текст, таблицы, границы и автоподбор ширины.
Что из этого войдёт в контракт
Перечисленное показывает, что механизм работает, но публичным интерфейсом ещё не является. В контракт выносятся устойчивые схемы адресации, событий и команд, версионирование и матрица возможностей.
## Что зафиксировать до начала интеграции
[Заголовок раздела «Что зафиксировать до начала интеграции»](#что-зафиксировать-до-начала-интеграции)
### Профиль возможностей
[Заголовок раздела «Профиль возможностей»](#профиль-возможностей)
| Решение | Что зафиксировать |
| ------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------- |
| Какие объекты адресуются | Абзац, участок форматирования, таблица, ячейка, изображение, примечание, поле и ваши собственные объекты |
| События | Щелчок, двойной щелчок, вызов контекстного меню, активация с клавиатуры; порядок доставки и возможность отменить |
| Контракт меню | Сколько ждать ответа вашей стороны, в каком порядке объединяются пункты, какие идентификаторы зарезервированы, перевод, значки и разделители |
| Контракт команд | Ожидаемая ревизия, ключ защиты от повторного применения, неделимый пакет команд, состав подтверждения и перечень ошибок |
| Сохранение | Куда сохранять, контрольные точки, что делать при конфликте, приёмка по кругу «сохранил — открыл заново» |
| Безопасность | Токен локального доступа, выдача возможностей, что пишется в журнал и что из него вырезается, отсутствие обходных путей мимо контракта |
| Версионирование | Версии схем, срок совместимости, порядок снятия возможностей с поддержки |
| Платформы | Какой профиль принимается в первом пилоте: настольный, внутри окна WebView, серверный или мобильный |
### Минимальный набор приёмочных проверок
[Заголовок раздела «Минимальный набор приёмочных проверок»](#минимальный-набор-приёмочных-проверок)
* Открыть реальный документ и получить смысловой снимок, не потеряв те части файла, которые редактор не разбирает: разметка Word (OOXML) переносится в новый файл как есть.
* Щелчок по тексту и по ячейке даёт правильный объект документа и выделение.
* Двойной щелчок выполняет согласованное действие один раз и не меняет документ сам по себе.
* Контекстное меню меняется в зависимости от объекта и доступных возможностей и открывается с клавиатуры.
* Команда с актуальной ревизией применяется; команда с устаревшей ревизией отклоняется с сообщением вида «stale revision: expected 5, received 3».
* Отмена и повтор восстанавливают результат; сохранение и повторное открытие подтверждают, что изменения дошли до файла.
* Закрытие освобождает слушателей, соединение WebSocket и процесс ядра — зависших процессов не остаётся.
### Что понадобится с вашей стороны
[Заголовок раздела «Что понадобится с вашей стороны»](#что-понадобится-с-вашей-стороны)
* 5–10 реальных документов и перечень элементов, по которым нужны действия.
* Таблицу «тип элемента → щелчок → двойной щелчок → пункты меню → команда документа».
* Требования к настольной, серверной и встраиваемой работе, к работе без сети, к хранилищу, учётным записям и аудиту.
* Список ваших объектов и способ связать их с узлами документа — без второй модели файла на вашей стороне.
# Запуск Web Surface
> Запустить готовую поверхность редактора локально, открыть DOCX в браузере, изменить абзац и сохранить результат.
Что нужно до начала
Команды ниже выполняются в подготовленной папке `~/Desktop/A4-API-first-run`. Её вместе со скриптами `a4-api-demo.mjs` и `start-web-demo.sh` даёт **Evaluation Kit** — отдельный артефакт поставки (см. [Состав поставки](/platform/delivery/)). В установщик A4 он не входит и с [dl.plantype.ru](https://dl.plantype.ru/a4/release/latest/) не скачивается: его запрашивают у поставщика.
**Если Evaluation Kit ещё не получен**, установленного A4 достаточно, чтобы пройти тот же путь вручную: `fastdoc-cli` лежит внутри приложения (см. [Установка и проверка](/a4/install/)), формат запросов описан в [Объектах запросов и ответов](/a4/envelope/), полный перечень команд — в [справочнике](/reference/a4/session-protocol/). Пример полностью выписанного скрипта есть у F1: [проверенный скрипт](/f1/reference-script/).
Surface — готовая локальная страница редактора поверх того же Rust-процессора. Это самый быстрый способ увидеть встраивание: запускаете локальный host и помещаете его URL в WebView или iframe.
Что поверхность умеет сегодня
Она объявляет ровно четыре возможности: открыть документ, изменить **первый абзац**, сохранить и отдавать события вашему приложению. Это проверяемая демонстрация пути «открыл → изменил → сохранил», а не полноценный редактор в браузере: произвольная правка, выделение и контекстное меню наружу пока не выведены.
Полный редактор в браузере — [другой путь](/a4/webhost/): там работает то же настольное приложение, а документ считает ядро на сервере.
Поверхность поднимается одной командой процессора. Путь к нему зависит от системы, остальные аргументы одинаковы: `--doc` — какой документ открыть, `--save` — куда сохранять (по умолчанию в тот же файл).
* macOS
```bash
CLI="/Applications/A4.app/Contents/Resources/runtime/native/fastdoc-cli"
"$CLI" surface --doc ~/Desktop/A4-API-first-run/contract.docx \
--save ~/Desktop/A4-API-first-run/api-demo-web-result.docx
```
* Windows
```powershell
$CLI = "C:\Program Files\A4\resources\runtime\native\fastdoc-cli.exe"
& $CLI surface --doc "$HOME\Desktop\A4-API-first-run\contract.docx" `
--save "$HOME\Desktop\A4-API-first-run\api-demo-web-result.docx"
```
* Linux
```bash
CLI="/opt/A4/resources/runtime/native/fastdoc-cli"
"$CLI" surface --doc ~/Desktop/A4-API-first-run/contract.docx \
--save ~/Desktop/A4-API-first-run/api-demo-web-result.docx
```
В ответ процессор печатает строку JSON с готовым адресом — откройте его в браузере. Адрес содержит одноразовый пропуск, поэтому подставлять ничего не нужно.
В Evaluation Kit для того же есть готовый скрипт `start-web-demo.sh`; он запускает ту же команду с заранее заданными портом и пропуском.
1. Дождитесь загрузки документа.
2. Поставьте курсор в первый абзац и добавьте текст.
3. Нажмите кнопку **Save DOCX** — она подписана по-английски, как и в самой поверхности.
4. Проверьте файл, указанный в `--save`.
5. Остановите поверхность сочетанием Control+C в том же окне терминала.
Порт и пропуск свои при каждом запуске
Порт по умолчанию выбирает система, а пропуск создаётся заново при каждом запуске — поэтому адрес из чужого примера не подойдёт. Оба значения `fastdoc-cli` печатает при старте одной строкой JSON:
```json
{"schema":"fastdoc.surface-host-bootstrap.v1","host":"127.0.0.1",
"port":54321,"token":"…","url":"http://127.0.0.1:54321/?token=…",
"document_path":"/absolute/input.docx",
"save_path":"/absolute/output.docx","ready":true}
```
Берите готовый `url` из этой строки — он уже содержит нужный пропуск. Рядом приходят и оба пути: `document_path` — открытый документ, `save_path` — куда уйдёт сохранение. Ваше приложение может показать их пользователю, не запоминая то, что само же передало в командной строке.
## Что дальше
[Заголовок раздела «Что дальше»](#что-дальше)
Поверхность запущена и работает. Дальше:
* встроить её в свой продукт — [Встраивание Surface](/a4/embedding/);
* точные имена событий, схем и параметров — [Интерфейс поверхности в браузере](/reference/a4/surface-api/).
# Редактор в браузере: встраивание через iframe
> Как встроить редактор A4 в веб-приложение: два адреса вашего API, три шага до рабочего редактора и что происходит внутри.
Если ваш продукт живёт в браузере, редактор можно встроить в него без установки чего-либо пользователю: страница редактора открывается внутри вашего приложения, а документ считает ядро на сервере.
Что для этого нужно получить
Веб-хост — отдельный артефакт поставки: менеджер кабинок, его проверки и образцы конфигураций. В установщик настольного приложения он не входит — ни в пакете для Linux, ни внутри приложения для macOS его нет, — поэтому запрашивается вместе с поставкой, см. [Состав поставки](/platform/delivery/). Ядро при этом берётся из установленного приложения: путь к нему передаётся веб-хосту переменной окружения.
Посмотреть до чтения
Как это выглядит для пользователя, можно увидеть прямо сейчас: [живой пример](/a4/demo/) — редактор работает на странице документации.
Что это не WebAssembly
Ядро не собирается для браузера. Оно работает на сервере, и каждому пользователю поднимается отдельный процесс со своим документом. В браузер уходит обычный код страницы и отрисовка на полотне. Поэтому встраивание — не подключение модуля, а обычный `iframe` на адрес редактора.
## Что нужно от вашего приложения
[Заголовок раздела «Что нужно от вашего приложения»](#что-нужно-от-вашего-приложения)
Два обязательных адреса вашего API и третий, необязательный. Если у вас есть личный кабинет и файлы, оба обязательных уже существуют.
| Адрес | Зачем | Что должен отвечать |
| --------------------------------- | ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| «кто я» | Узнать пользователя | Принимает куки или заголовок браузера, отвечает успехом и идентификатором пользователя, иначе — отказом |
| «файл по идентификатору» | Отдать документ | По тому же удостоверению отдаёт DOCX только тому, у кого есть право. Можно отвечать и подписанной ссылкой — редактор по ней перейдёт |
| «создать файл» *(необязательный)* | Открыть второму его копию | Принимает байты DOCX, в ответе — идентификатор нового файла |
Своей базы, учётных записей и паролей у редактора нет: пользователей и права знаете вы.
## Три шага
[Заголовок раздела «Три шага»](#три-шага)
### Запустить
[Заголовок раздела «Запустить»](#запустить)
Управляющий процесс поднимается одной командой и слушает только локальный интерфейс. Первой строкой он печатает готовность.
```bash
FASTDOC_CLI_BIN="$CLI" \
WEBHOST_WS_DIR=/opt/fastdoc/webhost \
node webhost/cabin-manager.mjs \
--template /opt/fastdoc/blank.docx \
--docs-dir /opt/fastdoc/cabins \
--port 8090 \
--auth-url 'https://ваш-хост/api/me' \
--attach-url 'https://ваш-хост/api/files/{id}/content'
```
Две переменные обязательны, и без них процесс поднимется, но работать не будет. `FASTDOC_CLI_BIN` — путь к процессору: это тот же `$CLI`, что и на странице [Установка и проверка](/a4/install/). `WEBHOST_WS_DIR` — полный путь к тому самому каталогу `webhost`, из которого запускается менеджер: по нему он находит свои файлы.
Перед запуском полезно проверить готовность площадки: `doctor.mjs` называет человеческим языком, чего не хватает — ядра, шаблона, занятого порта или доступности ваших адресов.
### Пробросить через свой сервер
[Заголовок раздела «Пробросить через свой сервер»](#пробросить-через-свой-сервер)
Обычный обратный прокси с одним обязательным условием: **переключение на WebSocket должно проходить**. Без этого редактор не свяжется с ядром.
Готовый образец для nginx лежит в поставке, в каталоге `examples`. Важны три вещи: передача заголовков переключения протокола, передача кук вашей авторизации и достаточный тайм-аут чтения.
Обязательные условия
Редактор должен открываться **по https и на том же домене**, что и ваше приложение: иначе браузер не отдаст ему защищённые куки вашей авторизации, и пользователь увидит «нужен вход».
### Дать ссылку
[Заголовок раздела «Дать ссылку»](#дать-ссылку)
В вашем интерфейсе у файла появляется пункт «Редактировать»:
```html
```
Пользователь, вошедший в ваше приложение, попадает в редактор со своим документом. Правки возвращаются в ваш файл вашим же API.
Как приходит сохранённый файл
Правка приходит как `multipart/form-data` — одной частью с именем `file`. Ваш обработчик обязан **разобрать** составное тело, а не записать его в файл целиком. Если записать целиком, документы будут портиться после каждого сохранения — молча, без ошибок в журнале. Признак беды: при попытке открыть такой DOCX распаковщик сообщает о лишних байтах в начале файла.
Случай не выдуманный: на нём споткнулся образец хоста в самой поставке.
## Что происходит внутри
[Заголовок раздела «Что происходит внутри»](#что-происходит-внутри)
* **У каждого пользователя своя кабинка** — отдельный процесс и свой документ. Чужой документ не виден и подсунуть его нельзя.
* **Права остаются за вами.** Не отдали файл — редактор не откроется. И не только на входе: раз в несколько минут редактор переспрашивает, вправе ли человек всё ещё открывать документ, который у него открыт. Отобрали права — кабинка гаснет, и человек видит ваш же ответ.
* **Правки уходят вашим API** с удостоверением того же пользователя.
* **Отказ в записи виден человеку.** Если вы отвечаете отказом навсегда — сессия истекла или прав больше нет, — вверху страницы появляется полоса: правки сейчас не сохраняются, работа не потеряна. Когда запись снова проходит, полоса уходит.
* **Кабинка гаснет после простоя** и поднимается заново при следующем входе. Часть кабинок можно держать прогретыми заранее: тогда вход в новый документ занимает доли секунды вместо ожидания запуска процесса.
Состояние площадки видно по адресу `/health`: версия сборки, сколько кабинок живо, сколько отказов в записи было за последний час.
## Что видит пользователь без входа
[Заголовок раздела «Что видит пользователь без входа»](#что-видит-пользователь-без-входа)
Редактор не имеет своих учётных записей и паролей: личность он берёт из вашего приложения. Поэтому запрос без вашей сессии получает отказ, а человек видит короткое объяснение — «нужен вход, войдите в систему и откройте документ снова». Это не ошибка встраивания, а нормальное поведение: если такое видит вошедший пользователь, значит до редактора не доехали куки — проверьте, что он открывается по https и на том же домене, а прокси передаёт куки.
## Двое в одном документе
[Заголовок раздела «Двое в одном документе»](#двое-в-одном-документе)
Совместного редактирования сейчас нет
Если пустить двоих в один документ, обе правки уедут к вам и вторая молча затрёт первую. Поэтому второму человеку открывается **копия**: отдельный файл у вас, свой адрес в браузере и полоса «это ваша копия, исходный файл сейчас редактирует другой человек».
Для этого и нужен третий, необязательный адрес — «создать файл». Не дадите его — ничего не сломается: второй человек получит честный отказ вместо копии, и проверка готовности предупредит об этом заранее.
Копия получает осмысленное имя, если ваш адрес отдачи файла присылает обычный заголовок с именем: тогда она называется «Договор №42 (копия).docx», а не датой создания.
Сколько раз за час двоим понадобился один документ, видно в том же `/health`. По этой цифре и стоит решать, нужно ли вам совместное редактирование вообще.
## Верхняя полоса окна
[Заголовок раздела «Верхняя полоса окна»](#верхняя-полоса-окна)
В настольном приложении сверху идёт полоса окна с именем документа и кнопками. В браузере имя документа уже написано на вкладке, и получаются два заголовка друг над другом. Поэтому вид полосы настраивается:
| Значение | Что делает | Кому подходит |
| -------------------------------- | ----------------------------------- | --------------------------------------- |
| `none` — по умолчанию в браузере | Полосы нет, сверху сразу лента | Всем, кто открывает редактор в браузере |
| `slim` | Полоса есть, но без имени документа | Кому нужны её кнопки под рукой |
| `full` | Как в настольном приложении | Кому нужен прежний вид один в один |
Меняется только видимость: геометрию документа считает ядро, страница не сдвигается. Сохранение при выключенной полосе остаётся во вкладке «Файл».
## Проверка
[Заголовок раздела «Проверка»](#проверка)
В поставке есть три проверки, и все запускаются одной командой.
| Что проверить | Чем |
| -------------------------------- | --------------------------------------------------- |
| Готовность площадки до запуска | `doctor.mjs` |
| Правильность встраивания целиком | `acceptance.mjs` — печатает таблицу «ждали / вышло» |
| Поведение под нагрузкой | `load-test.mjs` — на ваших же живых сессиях |
Ноль в строке «не сошлось» у приёмки означает, что встраивание сделано правильно.
## Дальше
[Заголовок раздела «Дальше»](#дальше)
* Что вообще даёт встраивание и где проходят границы — [Ответственность и безопасность](/a4/responsibility/).
* Другие способы разместить A4 — [Платформы и размещение](/a4/deployment/).
* Формат запросов к ядру — [Объекты запросов и ответов](/a4/envelope/).
# Тот же контракт через WebSocket
> Запуск fastdoc-cli с WebSocket на локальном адресе, порядок подключения с пропуском, минимальный клиентский цикл и отличия от обмена строками JSON.
Что нужно до начала
Команды ниже выполняются в подготовленной папке `~/Desktop/A4-API-first-run`. Её вместе со скриптами `a4-api-demo.mjs` и `start-web-demo.sh` даёт **Evaluation Kit** — отдельный артефакт поставки (см. [Состав поставки](/platform/delivery/)). В установщик A4 он не входит и с [dl.plantype.ru](https://dl.plantype.ru/a4/release/latest/) не скачивается: его запрашивают у поставщика.
**Если Evaluation Kit ещё не получен**, установленного A4 достаточно, чтобы пройти тот же путь вручную: `fastdoc-cli` лежит внутри приложения (см. [Установка и проверка](/a4/install/)), формат запросов описан в [Объектах запросов и ответов](/a4/envelope/), полный перечень команд — в [справочнике](/reference/a4/session-protocol/). Пример полностью выписанного скрипта есть у F1: [проверенный скрипт](/f1/reference-script/).
WebSocket удобен, если оболочка вашего продукта уже асинхронная и построена на веб-технологиях. Ядро само выбирает свободный локальный порт и печатает одну строку с адресом подключения и разовым пропуском.
* macOS
```bash
cd ~/Desktop/A4-API-first-run
node a4-websocket-demo.mjs
```
* Windows
```powershell
cd $HOME\Desktop\A4-API-first-run
node a4-websocket-demo.mjs
```
* Linux
```bash
cd ~/Desktop/A4-API-first-run
node a4-websocket-demo.mjs
```
## Порядок подключения
[Заголовок раздела «Порядок подключения»](#порядок-подключения)
1. Запустите `fastdoc-cli serve --ws 0` как дочерний процесс. Ноль означает «любой свободный порт».
2. Прочитайте первую строку stdout — в ней `host`, `port`, `token` и `schema`.
3. Подключитесь к `ws://127.0.0.1:/?token=`, подставив значения из этой строки.
4. Дождитесь приветствия сеанса со схемой `fastdoc.session-serve.v1`.
5. Отправляйте те же объекты JSON, что и через JSONL.
6. При закрытии приложения сначала отправьте `shutdown`, затем закройте сокет и дочерний процесс.
## Фактический результат
[Заголовок раздела «Фактический результат»](#фактический-результат)
```plaintext
1. WebSocket: ws://127.0.0.1:52601
2. Session protocol: fastdoc.session-serve.v1
3. Ping: true
4. Before: This is a sample word document. It has two pages, but no headers or footers.
5. After: WS API DEMO: This is a sample word document. It has two pages, but no headers or footers.
6. Saved: /Users/tester/Desktop/A4-API-first-run/api-demo-websocket-result.docx
7. Reopened from disk: WS API DEMO: This is a sample word document. It has two pages, but no headers or footers.
RESULT: PASS
```
Безопасность транспорта
Слушайте только 127.0.0.1, используйте выданный token, не публикуйте порт наружу и не передавайте путь к произвольному файлу без собственной проверки доступа.
## Минимальный клиентский цикл
[Заголовок раздела «Минимальный клиентский цикл»](#минимальный-клиентский-цикл)
```ts
const child = spawn(cli, ["serve", "--ws", "0"]);
const bootstrap = JSON.parse(await firstLine(child.stdout));
const ws = new WebSocket(
`ws://${bootstrap.host}:${bootstrap.port}/?token=${bootstrap.token}`
);
await new Promise(resolve =>
ws.addEventListener("open", resolve, { once: true }));
ws.send(JSON.stringify({ id: "p1", cmd: "ping" }));
```
Кадры только текстовые
Обмен идёт текстовыми кадрами WebSocket: каждый запрос — одна строка JSON, каждый ответ приходит тем же способом.
Двоичный кадр сеанс отвергает, но соединение не рвёт: в ответ приходит
```json
{"id":null,"ok":false,
"error":"session WebSocket requests must be UTF-8 text frames"}
```
и можно продолжать работу в том же соединении. Обратите внимание на `id: null` — при таком отказе сеанс ещё не знает, из какого запроса кадр.
# Архитектура: четыре слоя и объекты
> Одна система из четырёх слоёв ответственности, точное место ядра, перечень объектов и главные связи между ними.
Система разделена на четыре слоя, и у каждого свой владелец. Таблица читается по последнему столбцу: он говорит, чего слой делать не должен, — именно там проходят границы, которые нельзя нарушать при встраивании.
| Слой | Владелец | Ответственность | Не должен делать |
| ------------------ | -------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------- |
| Ваше приложение | Вы | Пользователи, права, карточка документа, версии, аудит, процессы согласования. Документооборот здесь лишь один из случаев: с тем же успехом это учётная система или отраслевая платформа. | Знать внутреннее устройство файла XLSX не обязано. |
| Оболочка редактора | Мы, под вашим брендом | Показывает редактор, собирает намерение пользователя, отображает ответ. | Формулы не считает и файл сама не меняет. |
| Контракт операций | Общая граница, за которую отвечаем оба | Описывает команды, результаты, отказы, ревизии и возможности. | От того, где именно работает ядро, не зависит. |
| Ядро на Rust | Мы | Единственный владелец смысла книги: расчёт, изменения, история, сохранение. | Карточкой документа и вашими пользователями не управляет. |
## Где именно работает ядро
[Заголовок раздела «Где именно работает ядро»](#где-именно-работает-ядро)
Ядро — не сервер и не экран. Это переносимый вычислительный модуль. В браузере тот же код собирается в WebAssembly. В настольном приложении или на сервере он запускается как обычный исполняемый файл. Меняется способ запуска; смысл рабочей книги и контракт операций остаются одними и теми же.
* **Оболочка** — то, что видит пользователь, и порядок вызовов.
* **Контракт операций** — команды в JSON и ответы известного вида.
* **Ядро** — единая модель книги и операции над ней.
Архитектурное правило
В оболочке не должно появиться второе, маленькое ядро. Если кнопка меняет смысл документа, операция обязана пройти через контракт операций и настоящее ядро. Иначе браузерная и серверная версии начнут вести себя по-разному.
## Какие объекты существуют и как они связаны
[Заголовок раздела «Какие объекты существуют и как они связаны»](#какие-объекты-существуют-и-как-они-связаны)
| Объект | Содержит / знает | Жизненный цикл |
| -------------------------- | ------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------- |
| Сеанс интеграции | Идентификатор документа, права пользователя, настройки продукта. | Создаётся вашей системой, когда открывают редактор. |
| Сеанс книги | Открытую книгу, номер текущей ревизии, историю правок, способ исполнения и состояние сохранения. | Живёт от открытия до закрытия; от других книг отделён. |
| Книга | Листы, имена, стили, настройки, связи и служебные части файла. | Загружается из файла и меняется только ядром. |
| Лист | Ячейки, строки и столбцы, таблицы, объекты и параметры печати. | Опознаётся внутри книги по ключу, который не меняется. |
| Окно просмотра | Только видимую часть листа и сведения о ней. | Меняется при прокрутке и изменении масштаба; всей книгой не является. |
| Операция, она же намерение | Действие, его параметры, ожидаемый номер ревизии и ограничения. | Один запрос через контракт операций. |
| Результат и событие | Новое состояние, что именно изменилось, предупреждение или отказ и данные для показа. | Приходят оболочке и вашему приложению после каждой операции. |
| Сохранённый файл | Содержимое файла, его формат и сведения о сохранении. | Уходит в вашу систему документооборота новой версией. |
### Главные связи
[Заголовок раздела «Главные связи»](#главные-связи)
* **Сеанс интеграции** создаёт и ограничивает → **сеанс книги**
* **Сеанс книги** владеет состоянием → **книга, номер ревизии, история правок**
* **Книга** отдаёт наружу только нужное → **окно просмотра**
* **Операция** сверяет ревизию и меняет → **сеанс книги**
* **Сохранение** создаёт → **готовый файл → новую версию в вашей системе**
Почему это важно
Вы встраиваете не «экран Excel», а понятный набор объектов. Экран можно заменить или переделать под себя; сеанс книги, операция, ревизия и сохранённый файл остаются той опорой, на которую можно рассчитывать.
## Дальше
[Заголовок раздела «Дальше»](#дальше)
* Что происходит от открытия книги до сохранения — [Жизненный цикл](/f1/lifecycle/).
* Что умеет ядро рядом с Excel — [карта возможностей](/f1/atlas/).
* Первый проект — [Быстрый старт](/f1/quickstart/).
# Карта возможностей рядом с Excel
> Что у F1 описано контрактом и проверяемо, чем он отличается от Excel и чего мы не утверждаем.
F1 сравнивают с табличным редактором, и на этом уровне все участники выглядят одинаково: сетка, формулы, форматы. Разница начинается там, где таблица перестаёт быть отдельным приложением и становится частью чужого продукта.
Как читать эту страницу
Здесь нет утверждения «мы лучше». Excel остаётся эталоном полноты табличной семантики: у него родные Power Query, язык M, VBA и объектная модель. Ниже показано другое — какие слои у F1 описаны контрактом и потому проверяемы на ваших файлах.
Знаки в таблице относятся к контракту. Контракт и опубликованный выпуск — не одно и то же: часть описанных операций в сборку ещё не вошла, и где это касается строк ниже, сказано прямо в них. Полная разница — в [справочнике](/reference/f1/overview/).
Правило этой страницы
**«Нет» про Excel мы пишем только там, где есть источник и дата проверки.** Где твёрдого источника нет — стоит «требует уточнения»: проверьте сами, ссылки внизу страницы.
Наша колонка тоже под присягой. **«Есть»** стоит там, где возможность закрыта операциями опубликованного выпуска и проверяется при каждой сборке документации. **«Контракт»** — там, где операции описаны, но в сборку ещё не вошли. Мы не красим контракт в зелёный.
## Обозначения
[Заголовок раздела «Обозначения»](#обозначения)
| Знак | Что означает |
| ---- | ------------------------------------------------------------------- |
| ● | Есть: работает как родная возможность и проверяется |
| ◐ | Частично: работает в объявленном подмножестве или другим механизмом |
| ◇ | Контракт: операции описаны, в опубликованный выпуск ещё не вошли |
| — | Нет: в официальной документации родного эквивалента не нашлось |
| ? | Требует уточнения: твёрдого источника у нас пока нет |
Прочерк означает ровно то, что написано: в изученной официальной документации Microsoft родной возможности не найдено. Это не доказательство, что её нельзя добавить надстройкой. Если вы знаете источник, который опровергает любую клетку, — напишите, поправим и обновим дату.
## Сравнение по возможностям
[Заголовок раздела «Сравнение по возможностям»](#сравнение-по-возможностям)
| Возможность | F1 | Excel | Что за этим стоит |
| ------------------------------------- | -- | ----- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Таблицы и формулы | ● | ● | Базовый уровень; сам по себе не отличает один продукт от другого. |
| Форматы Excel | ● | ● | У F1 — объявленная матрица возможностей и правило сохранения того, что редактор не понимает. |
| Сводные таблицы | ● | ● | Одиннадцать операций контракта: вычисляемые поля и элементы, группировка и детализация, макет, фильтры и ранжирование, настройки полей значений, страницы фильтра отчёта. Модель данных OLAP доступна только для чтения. |
| Условное форматирование | ● | ● | Правила применяются, изменяются и переносятся между диапазонами тремя операциями контракта. Две из них — применение и перенос — есть в последнем выпуске, третья, изменение правил, пока только в контракте. |
| Защита книги, листа и диапазонов | ● | ● | Права на отдельные диапазоны, защита листа и жизненный цикл защиты книги — три операции; четвёртой показывается, чем именно защищён файл. |
| Power Query | ◐ | ● | Подключение, преобразование, объединение, загрузка и обновление — в объявленном подмножестве. Полного совпадения с Excel контракт не обещает: это записано в нём отдельной строкой. |
| Язык M | ◐ | ● | Разбор и вычисление в ядре на Rust со своей библиотекой функций. Это подмножество языка, а не полная среда выполнения M. |
| Зависимости запросов и обновление | ● | ● | Граф зависимостей, отмена и вытеснение устаревшего обновления. |
| Приватность и учётные данные запросов | ● | ● | Уровни приватности источников; секреты не попадают в проекции книги. |
| Запуск макросов VBA | ◐ | ● | Ядро не истолковывает VBA: оно осматривает проект, планирует запуск, показывает предпросмотр правок и переносит результат в книгу, а исполняет макрос отдельный провайдер. |
| Объектная модель Excel | ● | ● | В F1 среда выполнения отделена от слоя объектной модели. |
| События книги и листа | ● | ● | Механизмы различаются; семантику событий Excel приходится воспроизводить отдельно. |
| Формы и модель взаимодействия | ● | ● | В F1 модель смысловая, а не попиксельное повторение форм Excel. |
| COM, OLE, ActiveX, Win32 | ◐ | ● | В F1 это слой совместимости с явными границами, а не общая среда Windows. |
| Редактор в браузере | ◇ | ● | У F1 в браузере работает то же ядро на Rust через WebAssembly. |
| Обработка без интерфейса | ◇ | ◐ | Серверную обвязку вокруг ядра вы строите сами. |
| Встраивание и OEM | ◇ | ◐ | У F1 — контракт операций и ваша собственная оболочка. |
| Агенты как клиенты вычислителя | ◇ | ◐ | В F1 порядок описан явно: осмотреть, предложить, получить разрешение, выполнить, проверить. |
## Чем F1 отличается на самом деле
[Заголовок раздела «Чем F1 отличается на самом деле»](#чем-f1-отличается-на-самом-деле)
### Подготовка данных встроена в саму таблицу
[Заголовок раздела «Подготовка данных встроена в саму таблицу»](#подготовка-данных-встроена-в-саму-таблицу)
Power Query в F1 — не надстройка, а часть продукта со своим жизненным циклом: источники, редактор запросов с шагами преобразования, вычислитель языка M, объединение, загрузка результата, граф зависимостей, обновление, уровни приватности и учётные данные. Это не «умеем открывать файлы с запросами», а работа с запросами как с объектами книги.
### Автоматизация описана как слои совместимости
[Заголовок раздела «Автоматизация описана как слои совместимости»](#автоматизация-описана-как-слои-совместимости)
F1 разделяет разбор языка VBA, среду выполнения, слой объектной модели Excel, события, модель взаимодействия и границы обращения к внешним объектам. Благодаря этому по каждому сценарию можно сказать, что работает как родное, что — через совместимость, а что не поддерживается вовсе. Ответ «поддерживаем VBA» без такого разделения ничего не значит.
### Одно смысловое ядро для разных сред
[Заголовок раздела «Одно смысловое ядро для разных сред»](#одно-смысловое-ядро-для-разных-сред)
Настольное приложение, браузер через WebAssembly, серверная обвязка и агенты обращаются к одному и тому же контракту операций. Табличная семантика не переносится в каждую среду заново — а именно так возникают расхождения, когда одна и та же книга считается по-разному в разных местах.
### Агент — часть контракта, а не надстройка над интерфейсом
[Заголовок раздела «Агент — часть контракта, а не надстройка над интерфейсом»](#агент--часть-контракта-а-не-надстройка-над-интерфейсом)
Порядок работы агента: осмотреть ограниченное состояние, предложить изменение, получить разрешение, выполнить через контракт, проверить результат. Агент не становится вторым владельцем смысла документа и не щёлкает по интерфейсу вместо человека.
## Чего мы не утверждаем
[Заголовок раздела «Чего мы не утверждаем»](#чего-мы-не-утверждаем)
Эти четыре утверждения не следует делать ни в переписке, ни в сравнениях — они не выдержат проверки:
* **«F1 полнее Excel».** Excel остаётся эталоном собственной семантики, и Power Query, язык M, VBA и объектная модель у него родные.
* **«Поддерживаем COM, ActiveX и Win32».** Этот слой в F1 помечен как совместимость, а не как полноценная среда Windows.
* **«Серверная и мобильная версии готовы».** Там, где статус архитектурный, готового продукта нет.
* **«В Excel этого нет».** Если вывод основан только на отсутствии возможности в публичной документации, это не доказательство.
## Где посмотреть подробности
[Заголовок раздела «Где посмотреть подробности»](#где-посмотреть-подробности)
* Как устроено встраивание и что пересекает границу — [Интеграция в приложение](/f1/integration/).
* Работа в браузере — [Вариант Web и WebAssembly](/f1/wasm/).
* Серверный сценарий и его границы — [F1 как процессор на сервере](/f1/server/).
* Сильные стороны и честные компромиссы — [Сильные стороны и компромиссы](/f1/tradeoffs/).
* Полный перечень операций — [справочник F1](/reference/f1/overview/).
О датах и источниках
Сравнение опирается на официальную документацию Microsoft, проверено **30 августа 2026 года**. Excel меняется, поэтому перед тем как принимать решение по этой таблице, стоит перепроверить актуальные данные и провести собственные замеры на своих книгах.
## Чем эта таблица отличается от рекламной
[Заголовок раздела «Чем эта таблица отличается от рекламной»](#чем-эта-таблица-отличается-от-рекламной)
**Где Excel сильнее — мы так и пишем.** Таблица, где у другой стороны всё плохо, не убеждает никого и проверяется за пять минут. Excel — зрелый продукт с сорокалетней историей, и там, где он делает больше, в нашей колонке стоит «частично» или «контракт».
**Наша колонка проверяется машиной, а не редактором.** За каждой строкой с пометкой «есть» стоят операции опубликованного выпуска: их список снят с самого ядра и лежит в репозитории, а сборка документации сверяет с ним текст и не проходит, если они разошлись. Поэтому «есть» здесь — не мнение.
**Разница между «есть» и «контракт» — не формальность.** В контракте 203 операции, в опубликованном выпуске — 185. Всё, что описано, но ещё не вошло в сборку, помечено «контрактом», а не зелёным знаком.
## Дальше по табличной части
[Заголовок раздела «Дальше по табличной части»](#дальше-по-табличной-части)
* Как лист отвечает на щелчки, двойные щелчки и вызов меню — [Реакция на щелчки и контекстное меню](/f1/interaction/).
* Что поверхность сообщает наружу — [События поверхности](/f1/surface-events/).
* Куда движется продукт — [От концепции к пилоту](/f1/roadmap/).
* Что менялось от версии к версии — [История версий F1](/f1/changelog/).
## Источники
[Заголовок раздела «Источники»](#источники)
* [Power Query](https://learn.microsoft.com/en-us/power-query/)
* [Язык Power Query M](https://learn.microsoft.com/en-us/powerquery-m/)
* [Excel VBA](https://learn.microsoft.com/en-us/office/vba/api/overview/excel)
* [Надстройки Excel](https://learn.microsoft.com/en-us/office/dev/add-ins/excel/)
# История версий F1
> Что появлялось, менялось и исправлялось в F1 от версии к версии.
Каждая строка ниже — изменение, которое видно в самом продукте. Версии идут от новых к старым.
Верхняя запись может опережать то, что выложено: список изменений пополняется по мере готовности выпуска, а выкладка идёт отдельным шагом. Какая версия доступна сейчас, видно в каталоге выпусков — на него ведёт страница установки.
Номер версии складывается из года, квартала и номера исправления внутри квартала: в `2026.3.4` это четвёртое исправление к выпуску третьего квартала 2026 года. Счёт исправлений начинается заново с каждым кварталом, поэтому за `2026.3.19` следует `2026.4.0`, а не `2026.3.20`.
В именах скачиваемых файлов к номеру добавлена ещё одна часть — сквозной номер сборки, например `2026.3.4.25`. На сравнение версий он не влияет и нужен, чтобы отличать файлы одной версии, собранные в разное время. Кандидаты и тестовые сборки помечены суффиксом: `2026.4.0-rc1`, `2026.4.0-beta1`.
Установленную версию приложение сверяет с той, что предлагает канал обновлений, и предлагает обновиться, когда выходит старшая. Проверить вручную можно пунктом меню «Проверить обновления…».
Всего выпусков: 61, счёт идёт с квартала `2023.2`.
## Выпуски 2026.3
[Заголовок раздела «Выпуски 2026.3»](#выпуски-20263)
### 2026.3.18
[Заголовок раздела «2026.3.18»](#2026318)
**Появилось**
* Мост к внешним приложениям: передача и возврат массивов, в том числе многомерных и байтовых; обращение к уже запущенному приложению, к файлу и к объекту по имени; полная таблица кодов ошибок; пустая ссылка как аргумент.
* Данные реального времени: цикл обновления и менеджер источников, который держит пул серверов по их именам и раздаёт подписки на темы.
* Долгая операция объявляет себя человеку в строке состояния.
* Макросы: тип листа отвечается по факту, именованные диапазоны читаются и по номеру, и по имени.
**Изменилось**
* Резкость сетки перестала зависеть от увеличения.
* Формат «Общий» ограничен пятнадцатью значащими цифрами, как в Excel.
**Исправлено**
* Плашка защищённого просмотра отжимает ленту и не перекрывает содержимое.
* Книги-шаблоны несут объявленную таблицу стилей.
* Старые книги читаются терпимее к повреждённым участкам.
### 2026.3.17
[Заголовок раздела «2026.3.17»](#2026317)
**Появилось**
* Вложенные в книгу объекты: разбор, извлечение содержимого, исходное имя упакованного файла, возврат правок вложения обратно в книгу.
**Исправлено**
* Дубликат объекта больше не теряется при сохранении.
* «Формат по образцу» и окно сортировки отпускают Escape.
* Меню ленты передаёт управление окну «Формат ячеек».
### 2026.3.16
[Заголовок раздела «2026.3.16»](#2026316)
**Появилось**
* Разбор пользовательских форм макросов: подписи и значения элементов управления, группы переключателей, иерархия рамок и многостраничников.
* Перенос форматов из буфера обмена через ядро.
* Динамические массивы: границы области разлива закрыты.
**Исправлено**
* Жесты по полотну считаются от полотна, а не от рамки окна.
* Первый читаемый кадр больше не выдаёт себя за готовую сетку.
* Формат даты «д-ммм» вернулся в список форматов.
### 2026.3.15
[Заголовок раздела «2026.3.15»](#2026315)
**Появилось**
* Книга открывается в два шага: сначала видимая часть, потом остальное.
**Изменилось**
* Система дат книги соблюдается при вычислении, при открытии и при сохранении; функции листа считают по системе дат самой книги.
* Макросы: функция форматирования переписана по составу — скобки, дробные части, масштабирующая запятая.
**Исправлено**
* Правила проверки данных в таблицах сохраняются в книге.
* Окно восстановления после сбоя принадлежит своему документу.
### 2026.3.14
[Заголовок раздела «2026.3.14»](#2026314)
**Появилось**
* Макросы: отбор подмножества массива, создание массива из значений, обход массива в цикле по элементам.
* Вычислительное ядро поставляется в составе того же выпуска двумя сборками: обычной и для браузера.
**Изменилось**
* Режим сравнения строк, объявленный в модуле, влияет на сравнение.
* Макросы, которые Excel не компилирует, больше не исполняются.
**Исправлено**
* В функциях поиска режим, заданный ссылкой на ячейку, больше не подменяется.
* Поиск по возрастанию сравнивает текст без учёта регистра, как Excel.
* Пакет для Linux больше не конфликтует с A4 на одной машине.
### 2026.3.13
[Заголовок раздела «2026.3.13»](#2026313)
**Появилось**
* Макросы: перечисления Excel связаны целиком — больше двух тысяч именованных констант.
* Защита листа паролем, режим «только для интерфейса» и перечень разрешённых действий.
* Переход по адресу с позиционными аргументами.
**Изменилось**
* Сортировка большого текста считается быстрее.
* Чтение логического значения перестало заводить строку на каждое обращение.
**Исправлено**
* Сравнение строки с логическим значением из ячейки больше не даёт ошибку типа.
### 2026.3.12
[Заголовок раздела «2026.3.12»](#2026312)
**Появилось**
* Макросы: модули классов — объявление типа, публичные поля как члены, создание объекта; номера строк; объявления внешних процедур и события; оператор сравнения по образцу; функции даты и времени.
* Функции листа доступны макросам по общему списку — 366 имён.
**Исправлено**
* Фильтр таблицы вернул поведение Excel.
* Построитель формул снова открывается.
* Изображение полотна при изменении масштаба стало устойчивым.
### 2026.3.11
[Заголовок раздела «2026.3.11»](#2026311)
**Появилось**
* Макросы: разбиение и склейка строк, вырезка слева, справа и по середине, обрезка пробелов, поиск подстроки, замена, преобразование числа в текст.
* Окно сообщения показывается по-настоящему.
* Запуск макроса из продукта открывает сеанс исполнения; идущий макрос можно остановить, откатить и опросить о состоянии.
* Запись в диапазон из нескольких областей доходит до каждой области.
**Изменилось**
* Прокрутка больших книг переиспользует уже построенные проекции.
**Исправлено**
* Порог перехода к экспоненциальной записи чисел совпадает с Excel.
* Корень из отрицательного числа даёт ту же ошибку, что и Excel.
* Используемый диапазон учитывает любую отформатированную пустую ячейку.
### 2026.3.10
[Заголовок раздела «2026.3.10»](#2026310)
**Появилось**
* Пакет анализа данных: сглаживание, ранг и процентиль, преобразование Фурье, генерация случайных чисел, выборка.
* Сценарии: подгрузка и объединение данных, лежащих вне листа.
* Макросы: предпросмотр показывает, что макрос изменит в книге, до его запуска; результат макроса можно зафиксировать в файл; макрос не выходит за границы книги без объявленного разрешения.
**Исправлено**
* Сохранение книги в байты больше не запирает её от последующего сохранения.
* Повторная сортировка сохраняет порядок отмены и повтора.
### 2026.3.9
[Заголовок раздела «2026.3.9»](#202639)
**Появилось**
* Номер сборки рядом с номером версии.
### 2026.3.8
[Заголовок раздела «2026.3.8»](#202638)
**Появилось**
* Портативный архив для Windows вернулся под понятным именем.
**Изменилось**
* Заметки к выпуску — только человеческая сводка на русском.
**Исправлено**
* Кнопки «Работа с данными» и «Текст по столбцам» доступны так же, как в Excel.
* Неверно записанная формула отклоняется, а не принимается молча.
* Сбой при сборке описания выпуска больше не останавливает сам выпуск.
### 2026.3.7
[Заголовок раздела «2026.3.7»](#202637)
**Появилось**
* Расширенный фильтр.
* Удаление дубликатов и проверка данных перешли в ядро; итоговая строка таблицы и вычисляемые столбцы приведены к поведению Excel.
* Создание графических объектов в открытой книге.
* Подпись пакета проверяется в целевой системе перед установкой; пакет для централизованной установки на macOS заверяется наравне с образом диска.
**Исправлено**
* Расширенный фильтр больше не включает стрелки автофильтра.
* Всплывающее окно фильтра обращается к своему столбцу.
* Диапазон фильтра покрывает всю область данных, а не один столбец.
* «Применить повторно» остаётся доступной, пока фильтр в силе.
* Каталог тем попадает в пакет и загружается в установленном приложении.
* Служебные ключи перевода больше не показываются человеку.
### 2026.3.6
[Заголовок раздела «2026.3.6»](#202636)
**Появилось**
* Макросы: функции листа и поиск совпадения отвечают через общий источник; чтение свойств листа через типизированную переменную.
**Изменилось**
* Версия внутри приложения показывается без пометки кандидата.
**Исправлено**
* «Очистить фильтр» активна, пока в списке есть снятые галочки.
* Один жест в списке значений выполняет одно действие.
* Взаимодействие со сводными таблицами вернулось к поведению Excel.
* Установщик обновления запускается средствами системы.
* Вкладка «Формулы»: исправлены дефекты вида и переходов фокуса.
### 2026.3.5
[Заголовок раздела «2026.3.5»](#202635)
**Появилось**
* Пункт меню «Проверить обновления…» с ответом человеку.
* Макросы: вставка ячеек, строк и столбцов со сдвигом.
**Исправлено**
* Список значений фильтра не закрывается после каждой галочки и после переключения автоприменения.
* В меню фильтра один общий шеврон вместо трёх разных.
* Окно «О программе» называет систему по факту.
* Стрелка фильтра в заголовке стала тонким шевроном.
### 2026.3.4
[Заголовок раздела «2026.3.4»](#202634)
**Появилось**
* Автообновление на Windows и Linux.
* Адрес хранилища обновлений задаётся настройкой; при сбое загрузка повторяется, есть защита от зацикливания и порог минимальной версии.
**Исправлено**
* Применение фильтра больше не снимает стрелки с других столбцов.
* Автоприменение переехало к кнопкам и не выглядит значением фильтра.
* Кнопка расширенного фильтра не выходит за границу своей группы.
### 2026.3.3
[Заголовок раздела «2026.3.3»](#202633)
**Появилось**
* Окно сортировки как в Excel: заголовки, кнопки, варианты «от старых к новым» для дат, сортировка слева направо.
* У окон продукта появилась собственная рамка с настоящими кнопками, включая разворачивание на весь экран.
**Изменилось**
* Вкладка «Данные» приведена к макету.
**Исправлено**
* Сортировка слева направо действительно переставляет столбцы.
* Окно сортировки показывает тот столбец, который в нём выбран, и предлагает для дат правильные варианты порядка.
* Предупреждение о сортировке не появляется на столбце с формулами.
* Буквы алфавита на значках сортировки переведены.
* Перетащенное окно остаётся на экране.
* Кнопка «Фильтр» рисует стрелки в заголовках.
* Макросы: недопустимый размер шрифта отвергается как значение; объединение диапазонов принимается; выделение из нескольких областей действует на все области.
### 2026.3.2
[Заголовок раздела «2026.3.2»](#202632)
**Появилось**
* Откат обновления, если новая версия не запустилась.
**Исправлено**
* Приложение больше не пишет внутрь собственного пакета — из-за этого ломалась подпись.
### 2026.3.1
[Заголовок раздела «2026.3.1»](#202631)
**Изменилось**
* Первое сервисное обновление линии.
### 2026.3.0
[Заголовок раздела «2026.3.0»](#202630)
Первый выпуск линии. Ниже — то, с чем продукт в неё вошёл.
**Появилось**
* Открытие и сохранение книг XLSX.
* Вычислительное ядро: пересчёт по зависимостям и частичный пересчёт только того, что изменилось.
* Язык формул: арифметика, ссылки и диапазоны; суммы, средние, счёт значений, минимум и максимум; дата и время; текстовые функции — длина, вырезка, поиск, замена, склейка, регистр; проверка типа значения; степень, корень, округление, логарифмы, тригонометрия; статистика; поиск значений в таблице.
* Обработка и свёртка данных запросом.
* Работа с данными: сортировка, фильтр, проверка данных, удаление дубликатов, текст по столбцам, сводные таблицы.
* Библиотека функций, построитель формулы и проверка формул.
* Макросы: собственный движок исполняет процедуры, модули классов, пользовательские типы, перечисления, условную компиляцию и переходы; объектная модель книги, листа и диапазона; защита листа и книги; буфер обмена; именованные диапазоны; выделение целых строк и столбцов; удаление строк со сдвигом; область печати.
* Автообновление приложения на macOS.
## Выпуски 2026.2
[Заголовок раздела «Выпуски 2026.2»](#выпуски-20262)
### 2026.2.13
[Заголовок раздела «2026.2.13»](#2026213)
**Изменилось**
* Книга держится в памяти разрежённо: пустые области листа места не занимают, и файл на сотни тысяч строк открывается без разворачивания всей сетки.
**Исправлено**
* Прокрутка к дальнему краю листа не пересчитывает лист целиком.
### 2026.2.12
[Заголовок раздела «2026.2.12»](#2026212)
**Появилось**
* Оценка совместимости книги до открытия: какие возможности файла поддержаны полностью, какие частично.
### 2026.2.11
[Заголовок раздела «2026.2.11»](#2026211)
**Изменилось**
* Пересчёт затрагивает только зависимые ячейки, а не весь лист.
### 2026.2.10
[Заголовок раздела «2026.2.10»](#2026210)
**Появилось**
* Защита листа и книги: заблокированные ячейки отклоняют правку с понятным отказом.
## Выпуски 2026.1
[Заголовок раздела «Выпуски 2026.1»](#выпуски-20261)
### 2026.1.9
[Заголовок раздела «2026.1.9»](#202619)
**Появилось**
* Автоматизация как транзакция: последовательность операций применяется целиком либо не применяется вовсе.
### 2026.1.8
[Заголовок раздела «2026.1.8»](#202618)
**Появилось**
* Разбор макросов VBA и отчёт о том, какие конструкции поддержаны.
### 2026.1.7
[Заголовок раздела «2026.1.7»](#202617)
**Изменилось**
* Внешние подключения к данным не выполняются без явного разрешения хоста.
### 2026.1.6
[Заголовок раздела «2026.1.6»](#202616)
**Исправлено**
* Отмена и повтор для операций над диапазоном записываются одной записью истории, а не по ячейке.
## Выпуски 2025.4
[Заголовок раздела «Выпуски 2025.4»](#выпуски-20254)
### 2025.4.8
[Заголовок раздела «2025.4.8»](#202548)
**Появилось**
* Сборка для WebAssembly: тот же счёт формул в браузере без отдельного движка на JavaScript.
### 2025.4.7
[Заголовок раздела «2025.4.7»](#202547)
**Появилось**
* Сервисный режим: обработка книг на сервере, без интерфейса.
### 2025.4.6
[Заголовок раздела «2025.4.6»](#202546)
**Изменилось**
* Граница между хостом и ядром описана одним типизированным контрактом операций.
### 2025.4.5
[Заголовок раздела «2025.4.5»](#202545)
**Появилось**
* Окно данных: хосту возвращается только видимая часть листа, а не книга целиком.
## Выпуски 2025.3
[Заголовок раздела «Выпуски 2025.3»](#выпуски-20253)
### 2025.3.7
[Заголовок раздела «2025.3.7»](#202537)
**Появилось**
* Постоянный сеанс работы с книгой: состояние живёт между вызовами.
### 2025.3.6
[Заголовок раздела «2025.3.6»](#202536)
**Появилось**
* История с отменой и повтором на стороне ядра, общая для всех способов правки.
### 2025.3.5
[Заголовок раздела «2025.3.5»](#202535)
**Изменилось**
* Лист адресуется устойчивым ключом, а не именем: переименование листа больше не рвёт ссылки.
### 2025.3.4
[Заголовок раздела «2025.3.4»](#202534)
**Исправлено**
* Сохранение книги не теряет вычисленные значения формул для программ, которые их читают без пересчёта.
## Выпуски 2025.2
[Заголовок раздела «Выпуски 2025.2»](#выпуски-20252)
### 2025.2.6
[Заголовок раздела «2025.2.6»](#202526)
**Появилось**
* Сводные таблицы: построение, обновление и сохранение.
### 2025.2.5
[Заголовок раздела «2025.2.5»](#202525)
**Появилось**
* Диаграммы: чтение из книги, отображение и запись обратно.
### 2025.2.4
[Заголовок раздела «2025.2.4»](#202524)
**Исправлено**
* Диапазон диаграммы переживает вставку и удаление строк.
### 2025.2.3
[Заголовок раздела «2025.2.3»](#202523)
**Появилось**
* Условное форматирование по значению и по формуле.
## Выпуски 2025.1
[Заголовок раздела «Выпуски 2025.1»](#выпуски-20251)
### 2025.1.5
[Заголовок раздела «2025.1.5»](#202515)
**Появилось**
* Сортировка и фильтры по столбцам, включая фильтр по цвету.
### 2025.1.4
[Заголовок раздела «2025.1.4»](#202514)
**Появилось**
* Именованные диапазоны и их использование в формулах.
### 2025.1.3
[Заголовок раздела «2025.1.3»](#202513)
**Изменилось**
* Ошибки в формулах возвращаются кодом ошибки, а не пустым значением.
### 2025.1.2
[Заголовок раздела «2025.1.2»](#202512)
**Исправлено**
* Ссылка на другой лист не сбивается при копировании формулы.
## Выпуски 2024.4
[Заголовок раздела «Выпуски 2024.4»](#выпуски-20244)
### 2024.4.4
[Заголовок раздела «2024.4.4»](#202444)
**Появилось**
* Форматы чисел: даты, проценты, денежные единицы, разделители разрядов.
### 2024.4.3
[Заголовок раздела «2024.4.3»](#202443)
**Появилось**
* Объединение ячеек, перенос текста и выравнивание в ячейке.
### 2024.4.2
[Заголовок раздела «2024.4.2»](#202442)
**Исправлено**
* Ширина столбца и высота строки сохраняются точно, а не округлённо.
### 2024.4.1
[Заголовок раздела «2024.4.1»](#202441)
**Исправлено**
* Повторяющиеся пробелы внутри ячейки не схлопываются.
## Выпуски 2024.3
[Заголовок раздела «Выпуски 2024.3»](#выпуски-20243)
### 2024.3.3
[Заголовок раздела «2024.3.3»](#202433)
**Появилось**
* Запись XLSX: собранная книга открывается сторонними программами.
### 2024.3.2
[Заголовок раздела «2024.3.2»](#202432)
**Изменилось**
* Части книги, которые редактор не понимает, сохраняются без изменений и возвращаются в файл.
### 2024.3.1
[Заголовок раздела «2024.3.1»](#202431)
**Изменилось**
* Книга записывается во временный файл и заменяет исходный одним действием.
## Выпуски 2024.2
[Заголовок раздела «Выпуски 2024.2»](#выпуски-20242)
### 2024.2.3
[Заголовок раздела «2024.2.3»](#202423)
**Появилось**
* Правка ячеек: ввод значений, текста и формул.
### 2024.2.2
[Заголовок раздела «2024.2.2»](#202422)
**Появилось**
* Выделение диапазона мышью и клавиатурой.
### 2024.2.1
[Заголовок раздела «2024.2.1»](#202421)
**Исправлено**
* Ввод числа с запятой распознаётся по языку интерфейса.
## Выпуски 2024.1
[Заголовок раздела «Выпуски 2024.1»](#выпуски-20241)
### 2024.1.2
[Заголовок раздела «2024.1.2»](#202412)
**Появилось**
* Счёт формул со ссылками на ячейки и диапазоны, обнаружение циклических ссылок.
### 2024.1.1
[Заголовок раздела «2024.1.1»](#202411)
**Появилось**
* Первый набор встроенных функций: арифметика, суммы, условия.
## Выпуски 2023.4
[Заголовок раздела «Выпуски 2023.4»](#выпуски-20234)
### 2023.4.2
[Заголовок раздела «2023.4.2»](#202342)
**Появилось**
* Показ сетки с заголовками строк и столбцов, прокрутка по большому листу.
### 2023.4.1
[Заголовок раздела «2023.4.1»](#202341)
**Изменилось**
* Значения ячеек хранятся типизированно: число, текст, дата, логическое, ошибка.
## Выпуски 2023.3
[Заголовок раздела «Выпуски 2023.3»](#выпуски-20233)
### 2023.3.1
[Заголовок раздела «2023.3.1»](#202331)
**Появилось**
* Чтение нескольких листов одной книги и переключение между ними.
### 2023.3.0
[Заголовок раздела «2023.3.0»](#202330)
**Появилось**
* Показ содержимого книги с сохранением структуры листов.
## Выпуски 2023.2
[Заголовок раздела «Выпуски 2023.2»](#выпуски-20232)
### 2023.2.1
[Заголовок раздела «2023.2.1»](#202321)
**Изменилось**
* Разбор книги перенесён в ядро на Rust.
### 2023.2.0
[Заголовок раздела «2023.2.0»](#202320)
**Появилось**
* Чтение XLSX: части пакета, общие строки, значения ячеек.
## До перехода на нумерацию по кварталам
[Заголовок раздела «До перехода на нумерацию по кварталам»](#до-перехода-на-нумерацию-по-кварталам)
Самые ранние сборки выходили под номерами другого вида и раздавались как альфа — для показа, а не для работы. Отдельной истории изменений по ним нет: тогда продукт менялся быстрее, чем выходили версии.
# Контрольный список готовности
> Что должно быть выполнено, прежде чем считать интеграцию готовой к пилоту.
* [ ] Архив скачан с [dl.plantype.ru](https://dl.plantype.ru/f1/release/latest/), а не получен по почте или со стороннего зеркала.
* [ ] Контрольная сумма скачанного файла совпала с указанной в `latest*.yml` рядом со сборкой.
* [ ] Подпись приложения проверена — способом своей системы, как показано в разделе [Проверка подписи](/f1/install/#%D0%BF%D1%80%D0%BE%D0%B2%D0%B5%D1%80%D0%BA%D0%B0-%D0%BF%D0%BE%D0%B4%D0%BF%D0%B8%D1%81%D0%B8-%D0%B8-%D0%BD%D0%BE%D1%82%D0%B0%D1%80%D0%B8%D0%B0%D0%BB%D1%8C%D0%BD%D0%BE%D0%B3%D0%BE-%D0%B7%D0%B0%D0%B2%D0%B5%D1%80%D0%B5%D0%BD%D0%B8%D1%8F).
* [ ] Процессор `compute_contract_cli` найден внутри установленного приложения и запускается.
* [ ] Книга открывается вызовом `open_workbook_subset`.
* [ ] `subsetId` и ключ листа берутся из ответа, а не собираются на своей стороне.
* [ ] Три ячейки изменены одной операцией `apply_cell_edits`, а не тремя подряд: иначе отказ на середине оставит книгу изменённой наполовину.
* [ ] `read_cell_details` вернул ожидаемое значение — в примере это 42 в ячейке C1.
* [ ] Отмена и повтор выполнены историей ядра, а не собственным стеком на своей стороне.
* [ ] Сохранённый файл создан и заново открыт другим процессом — так проверяется, что на диск легла книга, а не полуфабрикат.
* [ ] Отказы разбираются по полям `ok` и `code`, и интерфейс не показывает успех, когда операция отклонена.
* [ ] Для браузера и сервера предусмотрены отдельный процесс-посредник и список разрешённых адресов файлов.
## Если пункт не отмечается
[Заголовок раздела «Если пункт не отмечается»](#если-пункт-не-отмечается)
* Не проходит открытие или сохранение — [диагностика](/f1/diagnostics/).
* Непонятен состав ответа или код отказа — [Как устроен сеанс API](/f1/session/).
* Нужна операция, которой нет в примере, — [справочник Compute Contract](/reference/f1/overview/).
* Речь про Web или сервер — [F1 как процессор на сервере](/f1/server/) и [Ядро в браузере через WebAssembly](/f1/wasm/).
# Живой пример: таблица со сметой
> Работающая таблица F1 на странице документации: формулы, доли и оба итога пересчитываются по-настоящему.
Ниже — та же табличная часть, что и в поставке, с обычной сметой: позиции, количества, цены, формулы и доли. Когда демонстрационная площадка поднята, ею можно пользоваться прямо здесь: измените количество или цену — суммы, «Итого» и «К оплате» пересчитаются сами. Книга открывается в вашей собственной кабинке: правки видите только вы, они никуда не сохраняются, а следующий посетитель получит смету исходной.
Демонстрационная площадка сейчас не поднята — сама таблица при этом работает. Загляните позже или напишите нам: покажем на живой встрече, на ваших книгах.
[F1 с открытой сметой](/demo-f1/)
На весь экран[открыть отдельно ↗](/demo-f1/)Настоящая книга, а не снимок: формулы в столбцах «Сумма» и «Доля» считает ядро, а не разметка страницы
## Что именно здесь работает
[Заголовок раздела «Что именно здесь работает»](#что-именно-здесь-работает)
Рамка выше — не сборка для браузера. Устроено так же, как показано в [Средах выполнения](/f1/environments/): в браузере работает оболочка, а книгу держит отдельный процесс ядра на нашей стороне; между ними — постоянное соединение. У каждого посетителя свой процесс и своя копия книги, поэтому чужие правки сюда не попадают, а ваши не уходят к другим.
Отсюда и заметное на глаз свойство: пересчёт идёт не мгновенно, как в таблице внутри страницы, а с задержкой сети. Это цена схемы, а не особенность ядра — в настольном приложении и в сборке для браузера тот же пересчёт происходит на месте.
## Что стоит попробовать
[Заголовок раздела «Что стоит попробовать»](#что-стоит-попробовать)
* Изменить количество в столбце «Кол-во» — пересчитаются сумма строки, доля и оба итога.
* Изменить скидку в `B13` — изменится только строка «К оплате».
* Встать на ячейку `D11` и посмотреть в строку формул: там `SUM`, а не записанное число.
## Чем этот пример отличается от рабочего встраивания
[Заголовок раздела «Чем этот пример отличается от рабочего встраивания»](#чем-этот-пример-отличается-от-рабочего-встраивания)
| Что | В этом примере | В вашем приложении |
| ---------------- | -------------------------------------------- | --------------------------------------------------- |
| Кто пользователь | Гость: личность одна на всех посетителей | Ваш вошедший пользователь |
| Откуда книга | Один и тот же демонстрационный файл | Ваш файл по идентификатору |
| Куда идут правки | Никуда: кабинка живёт до простоя и стирается | В ваше хранилище, вашим же API |
| Права | Не проверяются | Проверяете вы, в том числе повторно во время работы |
## Дальше
[Заголовок раздела «Дальше»](#дальше)
* Отдельные умения по одному, каждое своим маленьким проектом — [Примеры F1](/f1/examples/).
* Где считает ядро в каждой из сред — [Среды выполнения](/f1/environments/).
* Что нужно вашей стороне для встраивания — [Интеграция в приложение](/f1/integration/).
# Диагностика
> Типовые проблемы первого запуска и как их локализовать.
Если первый запуск не идёт, найдите свой случай в таблице: рядом названа причина и то, что проверить.
| Симптом | Причина | Действие |
| ---------------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------ |
| Программа молчит при запуске | Неверный путь или файл не запускается | Убедиться, что файл лежит по указанному пути и запускается вручную |
| Открытие вернуло `ok=false` | Ошибка в адресе файла, формате или правах доступа | Передавать адрес файла целиком, от корня — в Node.js его даёт `pathToFileURL` из модуля `node:url`; прочитать `code` и `message` из ответа |
| Устаревшая ревизия | Другой запрос уже изменил книгу | Запросить свежую проекцию с новым номером ревизии и осознанно повторить операцию |
| Операция уходит не на тот лист | Передано видимое имя вместо ключа листа | Использовать `activeSheet.path` из ответа на `open` |
| Сохранённый файл не появился | Неверный адрес назначения или нет прав на запись | Проверить абсолютный URI и каталог |
| `subsetId` не найден | Процесс ядра был перезапущен и потерял открытые подмножества | Снова вызвать open\_workbook\_subset |
| Формула показывает старое значение | Книга только что открыта в новом процессе | Это значение, сохранённое в файле; чтобы получить свежее, вызовите пересчёт |
## Что приложить к обращению
[Заголовок раздела «Что приложить к обращению»](#что-приложить-к-обращению)
* версию F1 и платформу;
* контрольную сумму sha512 установочного файла — ту, что сверяли при установке;
* имя операции (`operation`) и код отказа (`code`);
* минимальную обезличенную книгу XLSX, на которой это видно;
* одну JSON-строку запроса и одну JSON-строку ответа;
* указание, воспроизводится ли после нового процесса.
Собранное отправляйте на — этого хватает, чтобы начать разбор, не спрашивая недостающее вторым письмом.
Состав ответа при отказе — на странице [Как устроен сеанс API](/f1/session/).
# Набор для встраивания: структура, события, меню
> Модель доступа без DOM, структурные проекции книги, события щелчка и выбора, вклад в контекстное меню и команды вместо прямой записи.
Публичного набора для встраивания пока нет
Структурные проекции книги и обработка щелчков, двойных щелчков, выбора объектов и контекстных меню внутри F1 уже работают, но остаются внутренними интерфейсами продукта. Ниже описано, каким должен стать публичный набор для встраивания (Embedding API), а не то, что можно вызвать сегодня.
Сам контракт при этом уже зафиксирован отдельной версией: перечень событий, коды отказов, набор настроек редактора и объект управления им объявлены и не меняются произвольно. Нет второй половины — того, что этот контракт выполняет и поставляется вам пакетом. Поэтому писать код против имён и типов можно заранее, а запустить его — пока нет.
Что доступно сейчас: [Compute Contract](/reference/f1/overview/) — в контракте 203 операции чтения и изменения книги, из них 185 принимает ядро последнего выпуска. Обращаться к DOM-селекторам, внутренним `CustomEvent` и глобальным точкам входа `__f1…` нельзя: это не контракт, и они меняются без предупреждения.
| Статус | Объект | Что это означает |
| ------------------ | -------------------------------- | ------------------------------------------------------------------------------------- |
| Есть сейчас | Контракт операций в ядре | Чтение структуры, ячеек и объектов, типизированные операции изменения. |
| Есть сейчас | Обработка действий в поверхности | Определение попадания, щелчки и вызов меню, выделение и внутренние смысловые события. |
| Требует оформления | Публичный набор для встраивания | Устойчивые методы и события, добавление своих пунктов меню, права и версионирование. |
## Какой API нужен для встраивания
[Заголовок раздела «Какой API нужен для встраивания»](#какой-api-нужен-для-встраивания)
* **Чтение** — узнать состав книги, выделение, ячейку, объект.
* **События** — реагировать на действия пользователя.
* **Команды** — безопасно изменять книгу.
* **Расширение интерфейса** — добавлять свои команды и пункты меню.
## Не DOM документа, а структурные проекции и команды
[Заголовок раздела «Не DOM документа, а структурные проекции и команды»](#не-dom-документа-а-структурные-проекции-и-команды)
Таблица в F1 не выложена в разметку страницы как дерево элементов. Она рисуется на полотне, а в памяти держится только видимая часть книги — поэтому нужной ячейки или объекта в разметке может не быть вовсе, даже когда пользователь их видит. Обращаться нужно к логической структуре через набор вызовов.
| Объект | Что даёт | Откуда берутся данные |
| --------------- | ---------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------- |
| WorkbookSession | Идентификаторы книги и сеанса, ревизия, признак несохранённых изменений, активный лист, доступные форматы. | Сеанс ядра. |
| WorkbookInfo | Листы с устойчивыми идентификаторами, их имена, видимость и какой лист активен. | Проекция из ядра. |
| Selection | Лист, выделенный диапазон, активная ячейка и идентификаторы выбранных объектов. | Определение попадания в поверхности плюс идентификаторы из ядра. |
| CellDetails | Хранимое значение, отображаемое значение, формула, тип, формат, проверка ввода, примечание и ссылка. | Операция `read_cell_details`. |
| RangeSnapshot | Ограниченный участок: значения, формулы, стили и сопутствующие данные. | Запрос к ядру с явными границами участка. |
| ObjectInventory | Фигуры, диаграммы, изображения, элементы управления, их привязки, порядок наложения и доступные действия. | Опись объектов из файла. |
| ContextTarget | Что находится под указателем: ячейка, таблица, диаграмма, фигура, заголовок. | Смысловое определение попадания. |
### Главное правило
[Заголовок раздела «Главное правило»](#главное-правило)
Не хранить живые ссылки
Вам приходит проекция — снимок состояния с устойчивым идентификатором и номером ревизии. Хранить у себя узел разметки или изменяемый объект книги и писать в него напрямую нельзя: к следующему действию и разметка, и состояние успевают измениться. Перед каждым действием заново проверяются идентификатор, ревизия, права и доступность операции.
Так предлагается назвать публичные вызовы — в текущем контракте таких имён нет.
```ts
const session = await F1.open({
mount, documentId, bytes, mode: "edit"
});
const workbook = await session.getWorkbookInfo();
const selection = await session.getSelection();
const cell = await session.getCellDetails(selection.activeCell);
```
## Как получить листы, диапазон, таблицу и графический объект
[Заголовок раздела «Как получить листы, диапазон, таблицу и графический объект»](#как-получить-листы-диапазон-таблицу-и-графический-объект)
| Задача | Предлагаемый вызов | Что вернётся |
| ------------------------ | ------------------------------- | --------------------------------------------------------------------------------------------------- |
| Получить листы | `session.getWorkbookInfo()` | Список листов с идентификаторами, именами, видимостью и порядком. |
| Прочитать ячейку | `session.getCellDetails(...)` | Хранимое и отображаемое значение, формула, тип, стиль и сопутствующие данные. |
| Прочитать диапазон | `session.getRangeSnapshot(...)` | Значения, формулы и стили запрошенного участка — без всей книги. |
| Узнать таблицу | `session.getTableAt(...)` | Идентификатор таблицы, её диапазон, столбцы, фильтры, итоги и доступные действия. |
| Получить объекты | `session.getObjects(...)` | Опись объектов с идентификаторами, типами, привязками, порядком наложения и признаком изменяемости. |
| Узнать объект под точкой | `session.hitTest(...)` | Что находится под указателем: вид объекта и его устойчивый идентификатор. |
Пример: найти диаграмму и переименовать через команду
```ts
const objects = await session.getObjects({ sheetId });
const chart = objects.find(x => x.type === "chart");
if (chart?.capabilities.rename) {
await session.execute({
operation: "renameObject",
objectId: chart.stableId,
name: "План перевозок",
expectedRevision: session.revision
});
}
```
### Что проекция ядра отдаёт уже сейчас
[Заголовок раздела «Что проекция ядра отдаёт уже сейчас»](#что-проекция-ядра-отдаёт-уже-сейчас)
Открытие книги уже возвращает активный лист, список листов, видимую область, предпросмотр ячеек, сведения о таблицах и опись объектов. Для каждого объекта доступны устойчивый идентификатор, лист, тип, имя, привязка, связи с другими частями, порядок наложения, видимость, блокировка и перечень допустимых изменений. Для отдельной ячейки есть операция `read_cell_details`.
Ограничение
Запроса «вернуть всё дерево книги целиком» быть не должно. Он разворачивает в память всю книгу — то самое, чего разрежённая модель позволяет избежать, — и заодно выносит наружу части, которые читателю этого запроса видеть не полагалось. Чтение должно быть адресным и ограниченным.
## Щелчки, выделение и действия с объектами
[Заголовок раздела «Щелчки, выделение и действия с объектами»](#щелчки-выделение-и-действия-с-объектами)
| Событие | Когда приходит | Основные поля |
| --------------------- | ---------------------------------------------------------------------------- | ------------------------------------------------------------------------------- |
| `selectionChanged` | Выделение изменилось и это подтверждено. | новое и прежнее выделение, источник изменения, ревизия. |
| `cellActivated` | Одиночный щелчок или касание сделали ячейку активной. | ссылка на ячейку, диапазон, нажатые клавиши-модификаторы, источник. |
| `cellDoubleClicked` | Двойной щелчок по ячейке — после того, как определено, во что именно попали. | ссылка на ячейку, намерение правки, клавиши-модификаторы. |
| `objectSelected` | Выбрана фигура, диаграмма, картинка или элемент управления. | устойчивый идентификатор, тип объекта, привязка, доступные действия. |
| `objectDoubleClicked` | Двойной щелчок по графическому объекту. | сам объект, действие по умолчанию, клавиши-модификаторы. |
| `contextMenuOpening` | Щелчок правой кнопкой или долгое нажатие на телефоне. | объект под указателем, выделение, встроенные пункты меню, доступные действия. |
| `workbookChanged` | Ядро применило изменение. | новая ревизия, какая операция, что изменилось, признак несохранённых изменений. |
| `operationRefused` | Ядро отказало — до того, как что-либо изменилось. | код и текст отказа, объект, текущая ревизия. |
Подписка на смысловые события
```ts
const unsubscribe = session.on("cellDoubleClicked", async event => {
const details = await session.getCellDetails(event.cellRef);
hostPanel.open({ documentId, cell: details });
});
session.on("objectSelected", event => {
propertiesPanel.show(event.target);
});
// При закрытии интеграции:
unsubscribe();
```
### Порядок обработки жеста
[Заголовок раздела «Порядок обработки жеста»](#порядок-обработки-жеста)
указатель или касание → определение попадания → подтверждение выделения → смысловое событие → действие по умолчанию
Почему не события браузера
Полотно может быть пересоздано между щелчками, и два щелчка попадут в разные узлы разметки — по браузерным событиям это выглядит как два отдельных щелчка, а не как двойной. Поэтому двойной щелчок по одному и тому же объекту определяет сама поверхность, а не вы по событиям браузера.
## Как менять меню в зависимости от выбранного элемента
[Заголовок раздела «Как менять меню в зависимости от выбранного элемента»](#как-менять-меню-в-зависимости-от-выбранного-элемента)
Искать внутренний элемент меню в разметке и вставлять туда кнопку не нужно. Вместо этого вы регистрируете поставщика пунктов меню: перед показом F1 определяет, что находится под указателем, и запрашивает у вас дополнительные пункты.
| ContextTarget.kind | Примеры данных контекста |
| ------------------------ | -------------------------------------------------------------------------------- |
| cell / range | sheetId, address, valueType, formula, validation, comment, hyperlink, protected. |
| table | tableId, name, range, columnId, totals/filter state, mutation capabilities. |
| chart | objectId, chartType, source range, anchor, editable capabilities. |
| shape / image | objectId, anchor, zOrder, groupId, metadata, supported mutations. |
| rowHeader / columnHeader | track index, hidden state, size, protection. |
| `sheetTab` | sheetId, name, visibility, active/grouped state. |
| canvas | sheetId, pointer coordinates, no semantic object. |
Пример menu contribution для строки таблицы
```ts
session.contextMenu.registerProvider(async context => {
if (context.target.kind !== "table") return [];
return [{
id: "partner.open-shipment-card",
label: "Открыть карточку перевозки",
icon: "external-link",
enabled: Boolean(context.target.rowKey),
group: "partner",
order: 20,
run: () => host.openCard(context.target.rowKey)
}];
});
```
### Правила расширения меню
[Заголовок раздела «Правила расширения меню»](#правила-расширения-меню)
* У пункта есть собственное имя с вашей приставкой, переводимая подпись, необязательный значок, группа, порядок в ней и правило, по которому он включён или виден.
* Поставщик пунктов получает безопасный снимок обстановки, а не внутреннюю разметку и не всю книгу.
* Встроенные команды остаются за F1; вы добавляете свои пункты и в оговорённых местах заменяете разрешённые.
* Если ваш поставщик пунктов думает слишком долго, ожидание обрывается: меню не должно зависнуть из-за вашей серверной части.
## Команды вместо прямой записи в структуру
[Заголовок раздела «Команды вместо прямой записи в структуру»](#команды-вместо-прямой-записи-в-структуру)
Любая операция, меняющая документ, проходит через контракт операций в ядре. Так поведение остаётся одинаковым в браузере, в настольном приложении и на сервере, а история изменений — целостной.
| Шаг | Что происходит |
| -------------------- | -------------------------------------------------------------------------------------------------------- |
| 1. Собрать намерение | Имя операции, устойчивая ссылка на объект или диапазон, параметры и ожидаемая ревизия. |
| 2. Проверить доступ | Режим только для чтения, защита листа, объявленные возможности, формат исходного файла и правила сеанса. |
| 3. Передать ядру | Тот же контракт — через сборку для браузера, настольный процессор или серверную службу. |
| 4. Применить целиком | При успехе меняются ревизия и история; при отказе книга остаётся нетронутой — половины правки не бывает. |
| 5. Отдать результат | Новая проекция и событие об изменении книги — либо событие об отказе. |
Пример правки с проверкой ревизии
```ts
const result = await session.execute({
operation: "setCellValue",
target: { sheetId, row: 12, col: 4 },
value: { kind: "string", value: "Доставлено" },
expectedRevision: session.revision
});
if (!result.ok && result.code.endsWith("-stale-revision")) {
await session.refreshProjection();
}
```
### Запрещённая модель
[Заголовок раздела «Запрещённая модель»](#запрещённая-модель)
```js
// НЕЛЬЗЯ: это обходит Rust, историю и сохранение.
document.querySelector("[data-cell='E13']").textContent = "Доставлено";
session.workbook.sheets[0].cells[12][4].value = "Доставлено";
```
Отмена и повтор
Правка книги создаёт ровно одну запись в общей истории ядра. Своего стека отмены на вашей стороне заводить не нужно: две истории разойдутся, и отмена начнёт возвращать не то состояние, которое видел пользователь.
# Среды выполнения и сквозной сценарий
> Что меняется между настольным приложением, браузером и сервером, а что остаётся одинаковым; полный путь «открыть байты → изменить → вернуть XLSX».
## Что меняется между средами, а что остаётся одинаковым
[Заголовок раздела «Что меняется между средами, а что остаётся одинаковым»](#что-меняется-между-средами-а-что-остаётся-одинаковым)
| Среда | Доступ к структуре | События пользователя | Изменение документа |
| --------------------- | --------------------------------------------------------- | ----------------------------------------------------------------------- | ------------------------------------------------------------------- |
| Браузер, WebAssembly | Через единый интерфейс сеанса поверх вызовов WebAssembly. | Щелчок, двойной щелчок, контекстное меню, клавиатура, выделение. | Команда уходит в ядро внутри WebAssembly. |
| Настольное приложение | Тот же интерфейс; вызовы уходят в локальный сеанс. | Те же события, что и в браузере. | Команда уходит в отдельный процесс ядра. |
| WebView на телефоне | Тот же интерфейс, но видимая область меньше. | Касание, двойное касание и долгое нажатие превращаются в те же события. | Команда уходит в WebAssembly; нужен свой жизненный цикл приложения. |
| Сервер и агенты | Сеанс без интерфейса: запросы и команды. | Щелчков нет; есть события команды, сеанса и изменения. | Команда уходит в процесс ядра на сервере. |
### Смысловой контракт одинаков
[Заголовок раздела «Смысловой контракт одинаков»](#смысловой-контракт-одинаков)
Выделение, цель контекстного меню, операция, ревизия, результат и сохранённый файл описаны одинаково независимо от того, как устроен транспорт. На телефоне долгое нажатие превращается в то же событие открытия контекстного меню. На сервере щелчка мышью нет, но агент запрашивает те же сведения о ячейке и выполняет ту же операцию — с теми же правами и теми же отказами.
### Особые ограничения
[Заголовок раздела «Особые ограничения»](#особые-ограничения)
* В браузере нельзя отдавать структуру большой книги целиком: каждый запрос ограничен участком.
* На телефоне сеанс должен переживать выгрузку приложения из памяти — через контрольную точку и повторное открытие, а не через ссылку на память WebView, которой к тому моменту уже нет.
* На сервере нужны изоляция клиентов друг от друга, квоты, отмена задач и журнал действий. Готовой серверной платформы поставка не включает.
* Агенту разрешён только заранее согласованный список запросов и команд. Учётные данные и исходный текст запросов Power Query и макросов в события не попадают.
## Открыть байты → прочитать → изменить → сохранить
[Заголовок раздела «Открыть байты → прочитать → изменить → сохранить»](#открыть-байты--прочитать--изменить--сохранить)
Пример ниже использует тот же протокол, которым проверяется сборка. Индексы строк и столбцов считаются с нуля. Поле `viewport` ограничивает область, которая уйдёт в ответ, а `sourceBytesBase64` позволяет передать книгу байтами — тогда ядру не нужен доступ к вашей файловой системе.
Часть 1: открыть документ и получить структуру
```ts
import { spawn } from "node:child_process";
import { readFile } from "node:fs/promises";
import readline from "node:readline";
// Путь к процессору внутри установленного приложения — в переменной
// окружения F1_CLI; где он лежит в каждой системе, показано на
// странице «Установка F1».
const cli = spawn(process.env.F1_CLI, ["--session"],
{ stdio: ["pipe", "pipe", "inherit"] });
const lines = readline.createInterface({ input: cli.stdout });
const iterator = lines[Symbol.asyncIterator]();
const next = async () => JSON.parse((await iterator.next()).value);
const ask = async request => {
cli.stdin.write(JSON.stringify(request) + "\n");
return next();
};
const hello = await next();
if (hello.kind !== "compute_contract_cli_session_ready")
throw new Error("F1 core handshake failed");
const bytes = await readFile("./demo.xlsx");
const viewport = { top: 0, left: 0, rows: 40, cols: 12 };
const opened = await ask({
operation: "open_workbook_subset_from_bytes",
sourceName: "demo.xlsx",
sourceBytesBase64: bytes.toString("base64"),
viewport, requestedBackend: "native-desktop"
});
if (!opened.ok) throw new Error(opened.code);
const { subsetId, activeSheet, objectInventory = [] } = opened.payload;
console.log(activeSheet, objectInventory);
```
## Адресная операция, проверка ревизии и возврат байтов XLSX
[Заголовок раздела «Адресная операция, проверка ревизии и возврат байтов XLSX»](#адресная-операция-проверка-ревизии-и-возврат-байтов-xlsx)
После открытия книги поле `subsetId` называет её живой сеанс в ядре, и все следующие запросы несут этот идентификатор. Запрос на изменение передаёт `expectedRevision`: если ревизия устарела, ядро отказывает до внесения правки, а не применяет её наполовину.
Часть 2: адресное чтение, атомарная правка и сохранение
```ts
const cell = await ask({
operation: "read_cell_details", subsetId,
row: 0, col: 0, viewport,
requestedBackend: "native-desktop"
});
const edited = await ask({
operation: "apply_cell_edits", subsetId,
edits: [{ inputKind: "text", sheetKey: activeSheet.path,
row: 1, col: 1, input: "Доставлено" }],
expectedRevision: 0, viewport,
requestedBackend: "native-desktop"
});
if (!edited.ok) throw new Error(`${edited.code}: ${edited.message ?? ""}`);
const saved = await ask({
operation: "save_workbook_subset_to_bytes", subsetId,
preserveCachedValues: true,
requestedBackend: "native-desktop"
});
const output = Buffer.from(saved.payload.bytesBase64, "base64");
// Закрыть ядро: пока порождённый процесс жив, программа не завершится,
// а процессор останется висеть после её конца.
cli.stdin.end();
cli.kill();
```
## Тот же контракт в браузере: адаптер событий поверх полотна
[Заголовок раздела «Тот же контракт в браузере: адаптер событий поверх полотна»](#тот-же-контракт-в-браузере-адаптер-событий-поверх-полотна)
### Как выглядит вызов ядра из браузера
[Заголовок раздела «Как выглядит вызов ядра из браузера»](#как-выглядит-вызов-ядра-из-браузера)
Сеанс в браузере: доставку берёт на себя код страницы, смысл документа остаётся за ядром.
Модуль `fastsheet_bridge_wasm.js` рядом со сборкой ядра под WebAssembly — отдельный артефакт поставки: он идёт пакетами движка (см. [Состав поставки](/platform/delivery/)) и в установщик настольного приложения не входит. Пути к процессору со страницы установки для этого случая не годятся: там нативный исполняемый файл, а здесь модуль для браузера.
```ts
import init, { WasmComputeSession } from "./fastsheet_bridge_wasm.js";
await init();
const core = new WasmComputeSession(Date.now(), 0x362cafe);
const dispatch = request =>
JSON.parse(core.dispatch(JSON.stringify(request)));
const opened = dispatch({
operation: "open_workbook_subset_from_bytes",
sourceName: file.name,
sourceBytesBase64: await fileToBase64(file),
viewport: { top: 0, left: 0, rows: 40, cols: 12 },
requestedBackend: "wasm"
});
```
### Что делать с нынешней веб-оболочкой
[Заголовок раздела «Что делать с нынешней веб-оболочкой»](#что-делать-с-нынешней-веб-оболочкой)
Сегодня оболочка уже шлёт внутреннее событие `fastsheet:surface-object-selected` и сама обрабатывает щелчок, двойной щелчок и вызов контекстного меню. Для пилота их стоит спрятать за одной точкой входа. Подписываться напрямую, как показано ниже, допустимо только во внутренней сборке — но не рассчитывать на это как на долгосрочный интерфейс.
**Внутренний мост событий — его ещё предстоит превратить в устойчивый набор вызовов:**
```js
// INTERNAL / UNSTABLE: адаптер внутри поставки F1.
surface.addEventListener("fastsheet:surface-object-selected", event => {
hostEvents.emit("objectSelected", {
target: normalizeObjectTarget(event.detail),
revision: currentRevision()
});
});
surface.addEventListener("dblclick", event => {
const target = semanticHitTest(event.clientX, event.clientY);
hostEvents.emit("doubleClicked", { target });
});
surface.addEventListener("contextmenu", event => {
event.preventDefault();
const target = semanticHitTest(event.clientX, event.clientY);
openComposedMenu(target, hostMenuProviders);
});
```
Граница ответственности
Со стороны хоста подписка идёт на objectSelected, doubleClicked и contextMenuOpening, а приходит ContextTarget. Внутреннее имя события, устройство Canvas и реализацию hit-test знает только код F1.
## Дальше
[Заголовок раздела «Дальше»](#дальше)
* Где именно работает ядро в каждой среде — [Ядро в браузере](/f1/wasm/), [отдельный процесс](/f1/native/), [на сервере](/f1/server/).
* Что фиксируется до интеграции — [Контрольный список](/f1/checklist/).
# Примеры F1
> Двадцать три маленьких проекта: от минимального листа до миллиона строк, совместной работы и связей между книгами.
Каждый пример — работающий маленький проект, а не выдержка из кода. Но считает в них не ядро: на его месте стоит дублёр, а сами проекты собраны из пакетов `@f1/*` — они идут пакетами движка ([Состав поставки](/platform/delivery/)) и в открытый реестр npm не публикуются. Поэтому смотреть здесь нужно на объём кода — сколько его нужно написать вокруг листа для каждой задачи, — а не на числа в ячейках.
Настоящее ядро с настоящим пересчётом — в [живом примере](/f1/demo/): там книга открыта в отдельном процессе, и правка ячейки меняет итоги по-честному.
Смотреть их стоит по порядку: первые отвечают на вопрос «сколько нужно написать, чтобы получить лист», дальше идут отдельные умения, а в конце — предельные случаи вроде миллиона строк и работы без экрана.
* [Минимальный листсколько строк нужно, чтобы получить работающий лист](/f1/examples/minimal/)
* [Лист с лентойкак добавить нашу ленту, если она нужна](/f1/examples/ribbon/)
* [Встраивание в чужую страницучто будет с чужой страницей вокруг листа: её шрифт, фон и прокрутка](/f1/examples/embedded/)
* [Миллион строкцена трёх жестов на предельной книге — прокрутки, протяжки и масштаба: кадр p50 и p95 и обращения к ядру числами, а не обещанием](/f1/examples/stress/)
* [Перекрестьеподсветка строки и столбца активной ячейки: выключена по умолчанию и включается свойством](/f1/examples/crosshair/)
* [Только чтениекнига видна, но не правится: команды серые с причиной, а не спрятаны, и правка не начинается вовсе](/f1/examples/read-only/)
* [Свои заголовкиподписи строк и столбцов задаёт встраивающий, а ширина полосы считается по его тексту](/f1/examples/custom-headers/)
* [Лист без полос заголовковполосы прячутся одним свойством, и освободившееся место занимает сам лист](/f1/examples/no-headings/)
* [Свой пункт менючужой пункт в нашем меню: за чертой, под именем владельца и со своей командой — притвориться нашим он не может](/f1/examples/custom-menu/)
* [Своё сочетание клавишчужое сочетание срабатывает, а занятые листом (Ctrl+D, Ctrl+R, F2, Shift+Пробел) остаются за листом — отказ виден словами](/f1/examples/custom-shortcut/)
* [Свой языкязыка нет в наших трёх — его приносит хозяин страницы: подписи переводятся, непереведённое идёт запасной цепочкой, а не пропадает](/f1/examples/custom-locale/)
* [Перетащите книгу сюдачужой документ открывается байтами: оболочка доносит файл, разбор остаётся за швом контракта, а отказ приходит словами того, кто открывал — на витрине это заглушка, она берёт csv, tsv и txt, а на xlsx честно отказывает](/f1/examples/open-bytes/)
* [Двое в одном документедве рамки рядом: двигаете выделение в одной — во второй виден чужой курсор с именем; и прямо на экране написано, что видны только окна этого браузера](/f1/examples/presence/)
* [Помощники ведут листдо десяти внешних участников, каждый в своём процессе: ходит по своей колонке, выделяет участок и вписывает итог — намерениями через тот же канал; отказ в лишнем виден и ему, и человеку; не запустили — страница так и говорит](/f1/examples/driver/)
* [Своё событиедве точки расширения наружу: onCommand отдаёт любое намерение поверхности (наше и чужое), onGesture — цену закончившегося жеста числами](/f1/examples/custom-event/)
* [Гиперссылкассылка — свойство ячейки: одна операция ядра ставит её вместе с текстом, снимает, оставляя текст, и сама называет ссылки листа](/f1/examples/hyperlink/)
* [Найти и заменитьпоиск — один запрос, замена — два, и первый книгу не меняет: ядро вычисляет план правок, а пишет их та же дорога, что ввод](/f1/examples/find-replace/)
* [Примечанияшесть действий примечаний одной операцией контракта — перечислить, создать, удалить, ответить, отменить, повторить; текст, номер и нормализованный адрес называет ядро](/f1/examples/annotations/)
* [Структура листавосемь действий — одна операция: оболочка называет диапазон, ось и действие, а уровни и что спрятать считает ядро](/f1/examples/outline/)
* [Разметка страницгде ляжет разрыв — единственный метод SDK, который спрашивает и ничего не меняет; саму печать делает хост, и это граница, а не пробел](/f1/examples/page-breaks/)
* [Разрешить изменение диапазоновшесть намерений из десяти, что есть у операции: пароль уходит за шов и не возвращается — обратно приходит только способ защиты](/f1/examples/protected-ranges/)
* [Связи книгиформула считает по чужой книге: список связей, их состояние и адрес источника приходят от ядра, а открывает книгу хозяин окна](/f1/examples/workbook-links/)
* [Голова без экранакнига открывается, правится и читается из Node — без единого обращения к DOM](/f1/examples/headless/)
## Как это связано с ядром
[Заголовок раздела «Как это связано с ядром»](#как-это-связано-с-ядром)
Примеры показывают оболочку — то, что видит и трогает пользователь. Считает не она: за вычисления, формулы, историю и сохранение отвечает ядро, и разговаривают они через одно-единственное место — транспорт.
В примерах на месте ядра стоит дублёр: `createMockTransport` возвращает придуманные значения, чтобы лист было на чём показать. Поэтому цифры в них ничего не значат — важно, сколько кода нужно написать вокруг.
В вашем продукте меняется ровно эта строка: вместо дублёра ставится настоящий транспорт — сборка ядра в браузере или мост к процессу рядом. Остальной код примера остаётся как есть.
Что именно умеет ядро по ту сторону транспорта, описано в [справочнике Compute Contract](/reference/f1/overview/): это те самые операции, которые оболочка и вызывает.
## Пять правил, на которые ссылается код
[Заголовок раздела «Пять правил, на которые ссылается код»](#пять-правил-на-которые-ссылается-код)
Комментарии в примерах часто объясняют решение ссылкой на правило: «второй владелец книги не заводится (инвариант №2)». Правил всего пять, они общие для всех примеров и звучат так.
| Правило | О чём оно |
| ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
| №1 | Считает ядро, оболочка только показывает. Ни формул, ни истории, ни записи файла на стороне интерфейса нет. |
| №2 | Второй владелец книги не заводится: любая правка идёт той же дорогой, что и ввод с клавиатуры, — своей у неё нет. |
| №3 | Не показывать несделанное сделанным. Нарисовать в примере то, чего продукт не умеет, нельзя даже картинкой; в комментариях это названо `fake-green`. |
| №4 | Реестр команд принадлежит листу. Команды встраивающего в него не попадают — они живут своей стороной и не смешиваются с нашими. |
| №5 | Событие ничего не применяет и ничего не требует: лист поднимает намерение, а решает хозяин страницы. |
## Как ими пользоваться
[Заголовок раздела «Как ими пользоваться»](#как-ими-пользоваться)
Файлы каждого примера показаны на его странице целиком. Проект лежит в `examples/f1/<имя примера>` — можно взять его как заготовку и заменить книгу на свою.
## Дальше
[Заголовок раздела «Дальше»](#дальше)
* Как устроен лист внутри — [Архитектура](/f1/architecture/).
* Что можно вызвать сегодня — [справочник Compute Contract](/reference/f1/overview/).
# Примечания
> Шесть действий примечаний одной операцией контракта — перечислить, создать, удалить, ответить, отменить, повторить; текст, номер и нормализованный адрес называет ядро.
[шесть действий примечаний одной операцией контракта — перечислить, создать, удалить, ответить, отменить, повторить; текст, номер и нормализованный адрес называет ядро](/examples/f1/app/projects/annotations/index.html)
На весь экран[открыть в новой вкладке ↗](/examples/f1/app/projects/annotations/index.html)Работающий пример. Ниже — его код целиком, файл за файлом.
В рамке выше работает тот самый проект, чей код лежит ниже, — собранный, но не переписанный: те же файлы, только пропущенные через сборку. Считает в нём не ядро, а дублёр транспорта, поэтому числа в ячейках ничего не значат: смотреть нужно на код.
Страница, которую открывает браузер.
index.html
```html
Примечания — @f1