Qiongli R5D Zotero Companion Delivery Plan
Status: D0–D3 complete locally — D4 in progress
Date: July 27, 2026
Target branch: feat/r4b-ui-localization-polish
Baseline: desktop migration and integration repair commit 4e04ba55
Roadmap: docs/superpowers/roadmaps/2026-07-13-qiongli-2-accelerated-rust-migration-roadmap.md
Goal
Make the existing Qiongli Zotero Companion a truthful, product-controlled local integration without embedding Zotero, mutating a Zotero profile silently, or turning Qiongli into a second reference-library authority.
Qiongli owns:
- deterministic Companion XPI assembly and release identity;
- bounded Zotero Desktop, Connector, Companion, version, and endpoint observations;
- a user-confirmed handoff to Zotero's own Plugins interface;
- post-install and post-restart verification; and
- import-file fallback whenever live local integration is unavailable.
Zotero owns plugin trust confirmation, plugin activation, plugin updates after installation, and the user's reference library.
Product sequence
inspect Zotero and Companion without mutation
-> verify the bundled XPI identity
-> explain plugin authority and requested local access
-> open Zotero and reveal the exact XPI
-> user confirms installation in Zotero Plugins
-> verify Connector and /qiongli/ping after restart
-> enable explicit dry-run search/write planning
-> retain import-file fallback at every unavailable stateNo state may report ready from an XPI file, profile directory, install receipt, or successful handoff alone. ready requires a live, bounded, loopback-only Companion response with the supported endpoint version.
D0 — Status and compatibility contract
Status: complete locally; awaiting integration commit
- add one strict top-level Zotero integration projection to the native Desktop snapshot and App API;
- distinguish
not-observed, Zotero unavailable, Companion missing, Companion incompatible, update available, restart required, ready, disabled, and not observable; - expose Connector and Companion observations separately;
- expose installed Companion version, available bundled version, observed endpoint version, and the supported endpoint version without returning an endpoint URL or response body;
- keep CSL-JSON, RIS, BibTeX, and import-report fallback availability visible;
- reserve explicit verify and prepare-install capabilities without performing either operation during an ordinary snapshot; and
- reject unknown fields, invalid versions, unbounded formats, and contradictory states at both Rust and TypeScript boundaries.
Acceptance:
- ordinary snapshot construction performs no loopback request;
- the new contract is schema-versioned and fixture-backed;
not-observedis neutral rather than an installation failure;- fallback remains available before Zotero is installed or observed; and
- no Zotero profile path crosses the App API.
Implementation evidence:
qiongli-runtimeowns the supported Companion endpoint version and derives disabled, Zotero-not-running, Companion-missing, incompatible, and ready states from bounded observations;- Native Desktop exposes a neutral, read-only, top-level Zotero projection without probing loopback during ordinary snapshot construction;
- App API schema version 9 carries the strict projection and path-free Desktop handoff actions across the Rust and TypeScript boundary;
- Rust rejects contradictory ready, missing, incompatible, and not-observed evidence, and TypeScript mirrors the same high-value contradictions;
- the canonical Rust fixture, development transport, and frontend contract all use the same fallback formats and endpoint version; and
- targeted Runtime, UI, Native Desktop, App API, Desktop test, typecheck, and production-build verification passed on July 27, 2026.
D1 — Product-controlled Companion artifact
Status: complete locally; awaiting integration commit
- make the XPI build deterministic and include its version, compatibility floor/ceiling, endpoint version, size, and SHA-256 in a strict manifest;
- include the verified XPI and manifest in every advertised desktop package;
- reject missing, changed, oversized, or incompatible Companion artifacts;
- keep XPI version independent from the Qiongli App version while binding both identities into the package receipt; and
- add package inventory and copied-artifact acceptance without Python or Node at runtime.
Implementation evidence:
- Rust and Python independently assemble the same canonical, deterministic XPI and strict artifact manifest from the four bounded Companion sources;
- the source build embeds and verifies the artifact without Python or Node at runtime and materializes it under
Qiongli.app/Contents/Resources/Zotero; - macOS, Windows, and Linux Desktop package manifests require the XPI and artifact manifest, bind both digests and the independent Companion version, and reject missing or changed entries;
- Desktop package receipts, packaged-product acceptance, release evidence, and Linux AppImage validation carry the same Companion identity; and
- a source-built macOS App passed artifact verification, strict code-signature verification, native startup check, and product-version inspection on July 27, 2026.
D2 — Desktop installation handoff
Status: complete locally; awaiting integration commit
- add a Zotero integration card with Refresh, Prepare installation, Reveal XPI, Open Zotero, and Verify actions;
- never copy directly into a Zotero profile or extension database;
- show plugin version, supported Zotero versions, digest, requested local access, and restart consequences before handoff;
- use an opaque, receipt-bound staged artifact rather than exposing arbitrary filesystem selection to the WebView; and
- report
restart-requireduntil a compatible live Companion is observed.
Implementation evidence:
- Client Integrations now presents a dedicated Zotero card with separate local Refresh, approval-gated Prepare installation, receipt-owned Reveal XPI, detected-app Open Zotero, and explicit live Verify actions;
- the Rust service stages the exact embedded and verified XPI under Qiongli-owned
v2/zotero/companionstate with a canonical receipt, atomic persistence, idempotence, drift rejection, and no Zotero profile write; - the WebView sends no path for any Zotero action; native code resolves only the verified staged XPI and an allowlisted detected Zotero application;
- Refresh performs local disk/application observation only, while Verify alone performs the bounded 900 ms loopback Companion probe;
- the card discloses the independent Companion version, Zotero compatibility range, exact artifact digest and size, requested local access, Zotero-owned confirmation/restart boundary, and import-file fallback;
- unsupported Zotero versions are distinct from missing, stopped, and incompatible Companion states and cannot prepare an XPI; and
- Rust staging, UI model, Native Desktop fixture/handoff, TypeScript contract, Svelte typecheck, 116 Desktop tests, and the production frontend build passed on July 27, 2026.
D3 — Full MCP and explicit library operation qualification
Status: complete locally; awaiting integration commit
- connect the Desktop observation to the existing native
qiongli_zotero_statuscontract; - qualify local search, collections, tags, notes, and attachment summaries;
- require dry-run before every reference upsert;
- require explicit write intent for non-dry-run operations;
- preserve Zotero-authored fields under the selected update policy; and
- fall back to import files without claiming a library write occurred.
Implementation evidence:
- Companion
0.3.0and the App, Runtime, artifact builder, and Full MCP now require endpoint contract2; a live0.2.2/endpoint-1 Companion is exposed as a bundled update rather than as a valid connection; - both direct Full MCP Zotero tools and the Zotero source inside literature search probe the endpoint contract before operating;
- bounded local search qualifies DOI, title, citekey, creator, year, tag, and collection filters and returns creators, tags, collection paths, note summaries, and attachment summaries without paths by default;
- every mutation requires an immediately preceding, one-shot, five-minute, plan-bound dry-run receipt plus
write_intent: "apply"; direct, replayed, expired, or changed-plan writes are refused before mutation; - upserts are bounded to 100 items, preserve non-empty Zotero-authored fields under the default policy, append only missing tags, and do not duplicate a matching child note;
- unavailable or incompatible live integration returns truthful CSL-JSON, RIS, BibTeX, and report fallback rather than claiming a Zotero write; and
- 26 Companion tests, 139 passing Full MCP tests with three platform skips, Runtime/UI/Desktop compatibility tests, 21 App API tests, and deterministic artifact tests passed on July 27, 2026.
D4 — Packaged acceptance and release readiness
Status: automated source-App acceptance complete; clean-commit packaged run and Zotero-owned manual gates pending
- use an isolated Zotero profile and disposable library;
- cover missing Zotero, not running, missing Companion, incompatible endpoint, install handoff, restart, ready, update, disable, and removal;
- prove no profile mutation before Zotero's own confirmation;
- prove XPI/package/endpoint identities and import-file fallback after restart;
- run the accepted search and dry-run/write lifecycle against disposable data; and
- keep all evidence non-publishing until the parent Beta gates permit release.
Automated implementation evidence:
- the release artifact builder emits a Mozilla-style
qiongli-zotero-companion-updates.jsononly when given an explicit release tag, binding Companion version, Zotero compatibility, immutable XPI URL, and XPI SHA-256; the release target registry uploads it beside the XPI while prerelease App updates remain App-staged until the latest stable release advances the public update URL; pnpm acceptance:zoterovalidates the exact Companion resources inside a macOS App, runsapp snapshotunder a disposable HOME, proves no Zotero profile directory is created, and reruns the native state matrix, deterministic artifact checks, Companion lifecycle, and Full MCP qualification;- the source App built on July 27, 2026 carried Companion
0.3.0, endpoint contract2, a 65,639-byte XPI, and matching XPI digest77fff3a2841571a7f15b519b753f6b20eaf4c93492fea59c3b01cdfd8ca0c17c; - the resulting ignored receipt is
dist/macos/qiongli-r5d-zotero-acceptance.receipt.json, is explicitly non-publishing, and records that this dirty source App has no Desktop package manifest binding; - the same receipt binds the source App XPI byte-for-byte to the independently built release XPI and records update-manifest digest
95116c93994de318f642cab2a958ab34fa7772e766c855c391e9207a860fc623; desktop:macos:acceptancenow runs the same R5D verifier against its clean-commit accepted App and retains the separate receipt beside the main packaged-product acceptance receipt; and- automated evidence covers missing/stopped/missing-Companion/incompatible/ update/ready/disabled/removal state transitions, XPI staging without profile mutation, endpoint shutdown, bounded search, one-shot approved writes, duplicate preservation, and import-file fallback.
Manual Zotero gate:
- Commit the intended R5D source, then run
pnpm desktop:macos:acceptance:open. Confirm the R5D receipt reportsdesktopPackageManifestBound: true. - Quit the ordinary Zotero session. Launch
/Applications/Zotero.app/Contents/MacOS/zotero -P, create a dedicatedQiongli-R5D-Acceptanceprofile, and give it a new disposable data directory. Do not enable sync or sign in. Zotero notes that a new profile may otherwise reuse an existing data directory, so verify the directory in Settings → Advanced → Files and Folders before continuing: https://www.zotero.org/support/kb/multiple_profiles. - With that profile running and no Companion installed, use Refresh and Verify in Qiongli. The App must distinguish installed/stopped Zotero from Companion missing and must keep import-file fallback visible.
- Select Prepare installation and inspect the preview. It must show Companion
0.3.0, endpoint2, Zotero8.0–9.0.*, the exact XPI digest and size, local access disclosure, and restart consequence. Cancel once and confirm that the disposable Zotero profile is byte-for-byte unchanged. - For the update gate, download the published legacy fixture
qiongli-zotero-companion-0.2.2.xpifrom https://github.com/jxpeng98/qiongli/releases/download/v1.17.0/qiongli-zotero-companion-0.2.2.xpi and verify SHA-2561b6308b4fc92f4992a4202813b2a30df0fc9ab3cf6a36098bc3b5f85fffa7012. Install it only in the disposable profile, restart, and run Verify. Qiongli must show an incompatible legacy endpoint asupdate-required, never Ready. - Confirm Prepare, use Reveal XPI, and install the revealed
0.3.0file through Zotero Tools → Plugins. Zotero's current plugin documentation requires the.xpito be dragged into that window and warns that plugins have full local access: https://www.zotero.org/support/plugins. Qiongli must remainrestart-required; staging alone must never report Ready. - Restart only the disposable Zotero profile and run Verify. Ready requires a live Companion
0.3.0response with endpoint2. - In the disposable library, exercise one title search, one dry-run upsert, and one approved write using the immediately preceding receipt. Confirm a replay or changed plan is rejected, a duplicate keeps curated metadata, a matching child note is not duplicated, and attachment results expose no path by default.
- Disable the Companion in Zotero, restart, and verify Qiongli leaves Ready. Re-enable and restart to prove recovery. Finally remove the Companion, restart, and verify Companion Missing plus import-file fallback.
- Preserve the displayed states, then delete the disposable Zotero profile and data directory. Run the manual recorder with every gate identifier reported by
pnpm acceptance:zotero:manual-record -- --list-gates, including--confirm disposable-profile-removed. The recorder requires the clean packaged automated receipt, exact source commit, package manifest, XPI, and update-manifest identities; it refuses partial confirmations and remains non-publishing. Do not publish either receipt while the parent Beta gates remain open.
Nonclaims
R5D does not:
- bundle or redistribute Zotero Desktop;
- install an extension silently;
- edit Zotero's SQLite database or profile metadata directly;
- treat an extension file as activation evidence;
- call a model provider;
- upload a Zotero library; or
- replace Zotero as the authority for references, collections, notes, or attachments.
Completion gate
R5D is complete when:
- every advertised desktop package carries one verified Companion artifact;
- installation requires Zotero-owned user confirmation;
- App, CLI, and Full MCP agree on the same live compatibility observation;
- incompatible, missing, stopped, and unobservable states remain distinct;
- explicit writes pass dry-run, approval, restart, and duplicate qualification;
- import files remain usable without Zotero or the Companion; and
- packaged acceptance proves install, update, verification, disable, removal, and fallback without mutating a real user library.