Среды выполнения и сквозной сценарий
Что меняется между настольным приложением, браузером и сервером, а что остаётся одинаковым; полный путь «открыть байты → изменить → вернуть XLSX».
Что меняется между средами, а что остаётся одинаковым
Заголовок раздела «Что меняется между средами, а что остаётся одинаковым»| Среда | Доступ к структуре | События пользователя | Изменение документа |
|---|---|---|---|
| Браузер, WebAssembly | Через единый интерфейс сеанса поверх вызовов WebAssembly. | Щелчок, двойной щелчок, контекстное меню, клавиатура, выделение. | Команда уходит в ядро внутри WebAssembly. |
| Настольное приложение | Тот же интерфейс; вызовы уходят в локальный сеанс. | Те же события, что и в браузере. | Команда уходит в отдельный процесс ядра. |
| WebView на телефоне | Тот же интерфейс, но видимая область меньше. | Касание, двойное касание и долгое нажатие превращаются в те же события. | Команда уходит в WebAssembly; нужен свой жизненный цикл приложения. |
| Сервер и агенты | Сеанс без интерфейса: запросы и команды. | Щелчков нет; есть события команды, сеанса и изменения. | Команда уходит в процесс ядра на сервере. |
Смысловой контракт одинаков
Заголовок раздела «Смысловой контракт одинаков»Выделение, цель контекстного меню, операция, ревизия, результат и сохранённый файл описаны одинаково независимо от того, как устроен транспорт. На телефоне долгое нажатие превращается в то же событие открытия контекстного меню. На сервере щелчка мышью нет, но агент запрашивает те же сведения о ячейке и выполняет ту же операцию — с теми же правами и теми же отказами.
Особые ограничения
Заголовок раздела «Особые ограничения»-
В браузере нельзя отдавать структуру большой книги целиком: каждый запрос ограничен участком.
-
На телефоне сеанс должен переживать выгрузку приложения из памяти — через контрольную точку и повторное открытие, а не через ссылку на память WebView, которой к тому моменту уже нет.
-
На сервере нужны изоляция клиентов друг от друга, квоты, отмена задач и журнал действий. Готовой серверной платформы поставка не включает.
-
Агенту разрешён только заранее согласованный список запросов и команд. Учётные данные и исходный текст запросов Power Query и макросов в события не попадают.
Открыть байты → прочитать → изменить → сохранить
Заголовок раздела «Открыть байты → прочитать → изменить → сохранить»Пример ниже использует тот же протокол, которым проверяется сборка. Индексы строк и столбцов считаются с нуля. Поле viewport ограничивает область, которая уйдёт в ответ, а sourceBytesBase64 позволяет передать книгу байтами — тогда ядру не нужен доступ к вашей файловой системе.
Часть 1: открыть документ и получить структуру
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»После открытия книги поле subsetId называет её живой сеанс в ядре, и все следующие запросы несут этот идентификатор. Запрос на изменение передаёт expectedRevision: если ревизия устарела, ядро отказывает до внесения правки, а не применяет её наполовину.
Часть 2: адресное чтение, атомарная правка и сохранение
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 — отдельный артефакт поставки: он идёт пакетами движка (см. Состав поставки) и в установщик настольного приложения не входит. Пути к процессору со страницы установки для этого случая не годятся: там нативный исполняемый файл, а здесь модуль для браузера.
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 и сама обрабатывает щелчок, двойной щелчок и вызов контекстного меню. Для пилота их стоит спрятать за одной точкой входа. Подписываться напрямую, как показано ниже, допустимо только во внутренней сборке — но не рассчитывать на это как на долгосрочный интерфейс.
Внутренний мост событий — его ещё предстоит превратить в устойчивый набор вызовов:
// 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);});- Где именно работает ядро в каждой среде — Ядро в браузере, отдельный процесс, на сервере.
- Что фиксируется до интеграции — Контрольный список.