mirror of
https://github.com/storytold/wordcraft.git
synced 2026-10-26 19:37:03 -06:00
* Zotero 1: range fields and custom document properties round-trip Citation managers' ADDIN fields (Zotero, Mendeley, EndNote) are now kept as range fields: FieldStart/FieldEnd markers with the result as ordinary, formatted content between them, possibly spanning paragraphs (a Zotero bibliography). Before, the result was flattened to plain text and a multi-paragraph field lost its end. - doc: InlineObject::FieldStart/FieldEnd, Document::field_ranges, balance_field_ranges; CustomProp and custom_props with get/set/remove. - docx: read ADDIN fields as ranges (field codes up to 2 MiB), write them back as begin/instr/separate…end; orphan markers are dropped on save so files always hold whole fields. docProps/custom.xml is parsed and regenerated (simple types as text, vectors/blobs verbatim) instead of passed through, so Zotero's ZOTERO_PREF_n settings are editable. - engine: file.properties reads and sets custom properties. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Zotero 2: wire-protocol client and document bridge New crate wordcraft-zotero (L5) speaks Zotero's published word-processor protocol (port 23116; 32-bit transaction id + big-endian length frames, JSON payloads, ERR: replies): - client: sends an integration command (addEditCitation, addEditBibliography, addNote, refresh, removeCodes, setDocPrefs) and answers Zotero's calls until Document_complete or the connection closes (cancelled dialog). Answers go through a callback so the app can apply them on its UI thread; run_on_session answers directly. - Bridge: implements Zotero's Document_*/Field_* calls on a Session. Fields are stored as Word's plugin stores them (ADDIN ZOTERO_ range fields; prefs in ZOTERO_PREF_n, with Zotero's ReferenceMark field type kept as Word's Field in the file), so documents stay Word-compatible. Rich text (RTF or HTML) keeps citation formatting; bibliographies get a Bibliography style from setBibliographyStyle; note styles create footnotes/endnotes, and deleting a lone note citation removes its note; placeholder links convert to fields. One transaction = one undo step. Field ids stay stable through inserts and deletes. - Tests: a fake Zotero over a real socket, every call, docx round trip, hostile arguments for every method, random frames (proptest). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Zotero 3: Zotero in the app and the CLI, tested with the real Zotero - ui.zotero.<command> (addEditCitation, addEditBibliography, addNote, refresh, removeCodes, setDocPrefs), ui.zotero.status and ui.zotero.answer. The socket runs on a background thread; each of Zotero's calls is answered on the UI thread against the session, and Zotero's questions become a dialog the session waits on. Web builds report that Zotero needs the desktop app. - wordcraft-cli zotero <command> <file> [--save] [--trace] [--port]. - Bridge: a selection Zotero makes to show a citation is put back when the session ends. - ui_shot: {"wait": ms} keeps frames running for background work. - docs/zotero.md. Checked against Zotero 10.0.6: refresh of a citation and bibliography (APA), its "modified citation" question answered in the app's dialog, RTF results with italics, Bibliography style, prefs round trip. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Zotero 4: switching between in-text and note styles moves citations Document_convert and Field_convert now move a citation between the text, footnotes and endnotes, as Zotero asks when the document's style changes between an in-text and a note style: into a new note at the same place, or back into the text where the note mark was (removing a note that held only the citation; keeping one with other text). Field ids survive the move, formatting is kept, and each Zotero session stays one undo step. Checked against Zotero 10.0.6: unlink citations (removeCodes, with its confirmation question) and a refresh after switching the document to Chicago notes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Zotero 5: Zotero tab in the ribbon A Zotero tab (before Help) with Add/Edit Citation, Add Note, Add/Edit Bibliography, Refresh, Document Preferences and Unlink Citations. The buttons are disabled while a Zotero command is running and in the web build. Three new icons drawn in code (document preferences, unlink, add note); the others reuse the References tab's. Labels and status messages translated (zh-hans, zh-hant, ja). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Zotero: Add/Edit Bibliography inserted nothing Zotero reads a newly inserted bibliography field back before filling it, and unlinks a field whose text is empty, taking it for a bibliography the user deleted. Our new fields were empty, so Add/Edit Bibliography ended with Field_removeCode and nothing in the document. New fields now hold "{Citation}" until Zotero writes the result. Found by tracing the session with Zotero 10.0.6; regression test added. wordcraft-cli zotero takes --cmd (run before the Zotero command, e.g. caret.docEnd) to reproduce sessions from a given caret position. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Zotero: log each command's outcome and Zotero's calls to the app log Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Cargo.lock: wordcraft-zotero at the workspace version (0.4.0) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Brandon Thomas <echelon@gmail.com>
70 lines
4.3 KiB
Markdown
70 lines
4.3 KiB
Markdown
# Control protocol
|
|
|
|
Start the app with a control port: `wordcraft --control 7981` (or `WORDCRAFT_CONTROL_PORT=7981`).
|
|
It listens on `127.0.0.1` only. Send one JSON object per line, with the window's key (see
|
|
[Keys](#keys)); each gets one reply line:
|
|
|
|
```json
|
|
{"id": 1, "key": "…", "method": "engine.execute", "params": {"command": "text.insert", "params": {"text": "Hello"}}}
|
|
{"id": 1, "ok": true, "result": {"anchor": {...}, "focus": {...}}}
|
|
```
|
|
|
|
Errors come back as `{"id": …, "ok": false, "error": "message"}`. A failed command leaves the
|
|
document unchanged. A line that isn't valid JSON gets one error reply, then the server closes the
|
|
connection (so an HTTP request sent to the port can't run a command); reconnect to continue.
|
|
|
|
## Keys
|
|
|
|
Any local program (and possibly a web page) can reach a loopback port, so every request needs the
|
|
window's key. Once the port is bound, the app writes a random key (256 bits, as hex) to
|
|
`<settings>/control-key.<instance>` and removes the file when the window closes; a new start
|
|
writes a new key. `<settings>` is the folder that holds the preferences: on Linux
|
|
`$XDG_CONFIG_HOME/wordcraft` (by default `~/.config/wordcraft`), on macOS
|
|
`~/Library/Application Support/WordCraft`, on Windows `%APPDATA%\WordCraft`. `<instance>` is the
|
|
Flatpak instance id inside Flatpak, and `local-<port>` otherwise. On Unix the file has mode 0600.
|
|
|
|
```sh
|
|
cat ~/.config/wordcraft/control-key.local-7981 # Linux, outside Flatpak, port 7981
|
|
```
|
|
|
|
- Send the key as top-level `"key"` with every request, or once per connection with
|
|
`{"method": "auth", "key": "…"}` (reply `{"ok": true, "result": {}}`); later requests on that
|
|
connection may leave `key` out. Only `auth` reads `params.key`; for other methods `params.key`
|
|
is an ordinary parameter (`ui.key {"key": "Enter"}` presses Enter).
|
|
- The key is checked on every request. A missing or wrong key gets
|
|
`{"ok": false, "error": "unauthorized"}`, and the app closes the connection.
|
|
- A line over 1 MiB gets `{"ok": false, "error": "line too long"}`, and the app closes the
|
|
connection.
|
|
- A connection that sends no authorised request within 30 s of being accepted is closed. That is
|
|
one limit: blank lines and bad JSON do not extend it. After an authorised request the
|
|
connection may stay idle.
|
|
|
|
`wordcraft-cli mcp --connect` takes the key from `WORDCRAFT_CONTROL_KEY`, or else from the key
|
|
file for the port it connects to, and reads it again on every new connection (the app may have
|
|
restarted). It sends the key only to loopback addresses (`127.0.0.0/8`, `::1`, `localhost`). For
|
|
any other host it sends no key, says so, and the app refuses its requests: to drive an app on
|
|
another machine, forward its port to `127.0.0.1` (for example `ssh -L 7981:127.0.0.1:7981 host`)
|
|
and set `WORDCRAFT_CONTROL_KEY` to that app's key.
|
|
|
|
## Methods
|
|
|
|
| Method | Params | Result |
|
|
|---|---|---|
|
|
| `engine.execute` | `command`, `params` | the command's result (any id from `engine.commands`, including UI commands `ui.tab`, `ui.dialog`, `ui.backstage`, `ui.zotero.*` (see `docs/zotero.md`), `ui.language` (`{"value": "auto"|"en"|"zh-hans"|"zh-hant"|"ja"|"pt-br"|"es"|"uk"}`)…) |
|
|
| `engine.commands` | — | every command: id, label, location, shortcut, params, enabled |
|
|
| `document.inspect` | `text?` | blocks (text, style, runs, lists, tables), parts, sections, selection, pages |
|
|
| `ui.inspect` | — | UI state, view, dialog, window size, page rects on screen, caret, perf |
|
|
| `ui.click` | `x`, `y`, `button?`, `count?`, `shift?`, `cmd?`, `alt?` | real pointer input at window coordinates |
|
|
| `ui.clickText` | `page` (0-based), `x`, `y` (points from the page's top-left), `count?` | click inside a page |
|
|
| `ui.move` / `ui.drag` | `x`,`y` / `x`,`y`,`toX`,`toY`,`steps?` | pointer move / drag |
|
|
| `ui.key` | `key` (`B`, `Enter`, `Left`…), `shift?`, `alt?`, `cmd?` | key press through egui (shortcuts apply) |
|
|
| `ui.text` | `text` | typed text through egui |
|
|
| `ui.screenshot` | `path?` | PNG of the window |
|
|
| `ui.render` | `path`, `page?` (1-based), `scale?` | PNG of a page, rendered by the engine |
|
|
| `ui.parity` | — | feature-catalog parity |
|
|
| `ui.resize` / `ui.focus` / `app.quit` | | window control |
|
|
|
|
Any other method name is treated as a command id: `{"method": "format.bold"}` works.
|
|
|
|
Positions are `{"story": "body" | {"part": n}, "path": [block, row, cell, block, …], "off": byteOffset}`.
|