Skip to content

Architecture

The pieces

muchado words FILE
      ├── host/server.py   loopback HTTP server, background thread
      │                    serves the editor, answers its host calls,
      │                    runs x2t to open, save and export
      ├── host/bridge.js   window.AscDesktopEditor, injected into every page
      │                    before the editor's own scripts run
      └── host/window.py   a pywebview window pointed at 127.0.0.1

The editor is ordinary web content. It is served over loopback to a WebKit window on your own machine, with one script injected ahead of it.

The seam

Upstream's desktop application is a Chromium fork. The editor talks to its host through a single JavaScript object, window.AscDesktopEditor, whose reference implementation is about seven thousand lines of C++ inside CEF.

That object is the entire contract. MuchAdo implements it in JavaScript, backed by a Python server over HTTP — which is why the host is a few thousand lines rather than a browser fork.

The offline editor itself is already in upstream's source: three files, about 1,200 lines, selected at build time by SDK_PLATFORM=desktop. We do not fork it, patch it or reimplement it. We answer it.

What the host actually does

the editor asks the host does
open this document runs x2t to convert it into the editor's working format
here are my changes appends them to a change log
save runs x2t to merge the working format and the change log back into a document
print the same, to PDF, then hands the file to the desktop
open / save-as dialog puts a real native dialog on screen
new document copies a blank from the payload
open an external link hands the URL to your browser

Every one of these is a small HTTP endpoint. The conversion work is upstream's x2t binary, which is also what upstream uses.

x2t and doctrenderer

x2t is the converter. For most formats it is C++ all the way down. For PDF it is not: doctrenderer embeds V8 and runs the editor's own JavaScript layout engine server-side, so the PDF you print is laid out by the same code that drew the screen.

This has a practical consequence. The PDF path needs the real font files and a font index with absolute paths to them — not the obfuscated copies the browser downloads. Get that wrong and it fails inside V8 with a JavaScript type error; get it half-wrong and it segfaults.

The payload

The application and the payload are separate artifacts on separate version numbers.

the Python package a few hundred KB, on PyPI
core-<platform>.tar.gz 58 MB — x2t and the native libraries
editors.tar.gz 71 MB — sdkjs and web-apps, Words only
fonts-core.tar.gz 4 MB — font sources, not web fonts

Two things force that split. Generated files hold absolute paths, so they must be produced on the target machine; and doctrenderer reads real .ttf files, so we ship font sources and generate the browser's copies locally at install time.

Design notes

Branding is build-time configuration, not a fork. The product name, the logos and the application name written into saved documents are all upstream configuration points.

Our changes to upstream are a patch queue, not a branch. See The patch queue.

One observation is not a diagnosis. The regression harness exists because of a string of confident wrong answers about why the editor would not load. Each was consistent with the evidence and each was wrong.