feat(sync): cross-device custom font sync (#4077)

* refactor(sync): kind-agnostic replica primitives

Extract dict-only sync into shared primitives (registry, pull/apply
orchestrator, persist env, schema allowlist) so other kinds can plug
in. Companion changes: per-replica Storage Manager grouping,
useReplicaPull boot-race recovery, manifest=null reconciliation on
every boot pull, copyFile takes explicit srcBase + dstBase, settled-
event helpers, lenient webDownload Content-Length (R2/S3 signed URLs
commonly omit it), and generic "File" transfer toast copy any replica
kind can share.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(sync): cross-device custom font sync

Plug the font replica adapter into the kind-agnostic primitives:
font store gains replica wiring, custom font import publishes the
replica row + queues a binary upload, and bootstrap registers the
font adapter and download-complete handler.

Includes legacy flat-path migration so pre-existing fonts sync
without re-import, full @font-face activation on auto-download
(load + mount the rule, mirroring manual import), and a fix to
createCustomFont so contentId / bundleDir / byteSize survive the
trip through addFont — otherwise import-time publish silently
no-oped on missing contentId.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Huang Xin
2026-05-07 14:28:44 +08:00
committed by GitHub
parent cbdc3b8f52
commit 981579c255
62 changed files with 2066 additions and 526 deletions
@@ -236,10 +236,16 @@ export const nodeFileSystem: FileSystem = {
return new File([buffer], fileName);
},
async copyFile(srcPath: string, dstPath: string, base: BaseDir): Promise<void> {
const fullDst = await toAbsolute(this.resolvePath(dstPath, base));
async copyFile(
srcPath: string,
srcBase: BaseDir,
dstPath: string,
dstBase: BaseDir,
): Promise<void> {
const fullSrc = await toAbsolute(this.resolvePath(srcPath, srcBase));
const fullDst = await toAbsolute(this.resolvePath(dstPath, dstBase));
await fsp.mkdir(nodePath.dirname(fullDst), { recursive: true });
await fsp.copyFile(srcPath, fullDst);
await fsp.copyFile(fullSrc, fullDst);
},
async readFile(