Skip to main content

Built-in Resource Sync Process

The 600+ files under resources/design-studio-builtin/ (Skill / Design System / Craft) are not in git. They are pulled from the upstream nexu-io/open-design repository by commit pin and materialized to the user directory by the materializer on first startup.

Why this approach: upstream content is extensive and updated frequently. Committing everything would create significant noise in elftia's git history and frequent merge conflicts. Pinning a commit ensures reproducible builds while making "upgrading built-in assets" a one-time lock file bump.

:::warning Curation Exception: prompt-templates/ never syncs (2026-06-04)

prompt-templates/ is a manually curated subset maintained in place and not participating in sync: the upstream complete collection contains content risk entries like real human faces (e.g., templates for generating Elon Musk images), and these JSONs would be packaged as anonymously visible "community templates" in all channel releases. Therefore:

  • The lock's subdirs does not include prompt-templates; the sync script has NEVER_SYNC_SUBDIRS safeguard that will hard fail if it's added back (resync will first clearSubdirs delete the curated set then inject the unfiltered upstream complete set).
  • To introduce new templates from upstream: manually copy JSON one by one and review (real human faces / brand IP / NSFW), update curation log — see resources/design-studio-builtin/prompt-templates/README.md.
  • This directory exceptionally enters git (.gitignore is inverted for resources/design-studio-builtin/* with !…/prompt-templates/), a fresh clone can immediately reproduce the curated set without network access.

:::

Involved Files

PathPurposeIn git?
resources/design-studio-builtin.lock.jsonPin file — { repo, commit, subdirs }✅ Only file in git
resources/design-studio-builtin/Synced content on disk (600+ files)❌ gitignored
resources/design-studio-builtin/prompt-templates/Curation exception — manually curated subset, never syncs (see warning above), includes README.md curation log✅ Exception in git (gitignore inverted)
resources/design-studio-builtin/.synced.jsonMarker — records last successful sync commit, hit to skip❌ (inside ignored directory)
resources/design-studio-builtin/NOTICEAuto-generated third-party attribution
scripts/sync-design-studio.mjsSync script (pull by lock)
scripts/update-design-studio.mjsAuto-upgrade script — pull upstream HEAD + bump lock + sync + bump FLAG_FILENAME in one command
packages/desktop/app/main/services/content/workspace/design/builtin/index.tsFirst startup materializer + FLAG_FILENAME

Trigger Times

TriggerCommand / HookMode
npm installpostinstall hook--soft (network failure doesn't block install)
npm run setup / setup:nativeManual initialization--soft
npm run build:electron / build:official / build:steamBetween electron-vite build and electron-builderStrict mode (failure fails build)
npm run sync:designManual executionStrict mode
npm run sync:design -- --forceManual execution (force refetch)Strict mode + skip marker
npm run sync:design:updateOne-click upgrade to upstream HEAD (recommended)See "Upgrade to upstream latest commit" below

When marker hits (all three fields of {repo, commit, subdirs} exactly match lock), skip directly and output [sync:design] already synced @ <repo>@<sha7> — skipping (use --force). This is normal state, don't mistake it for an error.

What the Sync Script Does

scripts/sync-design-studio.mjs main flow:

  1. Read resources/design-studio-builtin.lock.json, validate schema (must have repo / commit / subdirs[])
  2. Compare local .synced.json marker — if all three fields match and no --force, skip and exit
  3. Pull tarball:
    • Public repo → https://codeload.github.com/<repo>/tar.gz/<commit>
    • After setting GITHUB_TOKEN / GH_TOKEN env vars → https://api.github.com/repos/<repo>/tarball/<commit> (private repos use this)
  4. Extract with decompress, strip: 1 remove outer package name directory, filter keep only subdirs listed in lock
  5. Before extracting, fs.rm(subdir, { recursive: true, force: true }) clean each target subdir to avoid stale files after upstream deletes
  6. Write marker .synced.json
  7. Write NOTICE — auto-generate third-party attribution (Apache-2.0)

decompress 4.x cross-platform gotcha (don't break)

strip is applied before filter, but on Windows paths received by filter are already normalized to backslashes. The filter in the script uses file.path.split(/[\\/]/)[0] to cut both separators — cutting only / passes all 600+ files on Linux but 0 on Windows, and the failure mode is silent (only outputs empty directory), debugging is painful. When modifying this section, validate on both platforms.

Upgrade to Upstream Latest Commit

npm run sync:design:update

scripts/update-design-studio.mjs bundles three steps of the "upgrade" process into one action:

  1. GET https://api.github.com/repos/<repo>/commits/HEAD — parse the SHA of upstream default branch HEAD
  2. If same as commit in lock → log already up to date — nothing to do and exit 0
  3. Otherwise regex replace lock file's commit field (preserve original format), spawn sync-design-studio.mjs to pull and extract; if sync fails, automatically rollback lock, won't leave a dirty pointer to an unsynced commit
  4. Use upstream new commit's short-sha (first 7 chars) to regex-rewrite services/content/workspace/design/builtin/index.ts's FLAG_FILENAME constant to .elftia-builtin-materialized-<short-sha>.json — old users' old flag filename in <userData> no longer matches, next startup rematerializes new content
  5. Print git diff / git commit hints

Only these two files change on commit: lock.json and builtin/index.ts.

Flag naming convention: FLAG_FILENAME embeds upstream commit's short-sha (e.g., .elftia-builtin-materialized-83ddf76.json). Used to be integer version numbers v1/v2/..., migrated to short-sha format — switching commits naturally changes the name, no need to maintain manual version numbers.

Dry-run:

npm run sync:design:update -- --dry-run

Only print SHA difference, don't touch any files. Can be used on CI to detect "whether there's a new upstream version available".

Auth: When GITHUB_TOKEN is not set, use GitHub public API limited to 60 req/hr unauthenticated. In CI set GITHUB_TOKEN=${{ secrets.GITHUB_TOKEN }} to 5000 req/hr.

Alternate Path: Manual Three Steps

Only use when auto script is broken, or want to pin to a non-default branch / historical commit:

  1. Change lock: Write the full 40-character SHA in commit field of resources/design-studio-builtin.lock.json (not short hash — script does exact string comparison)

  2. Run sync:

    npm run sync:design

    When commit doesn't match marker, automatically refetch, don't need --force. --force is only for when marker and lock match but local directory is corrupted and you want to force refetch.

  3. Bump FLAG_FILENAME: Edit packages/desktop/app/main/services/content/workspace/design/builtin/index.ts, change constant to new commit's short-sha:

    const FLAG_FILENAME = '.elftia-builtin-materialized-<new short-sha>.json';

    This step is critical — existing elftia users' <userData>/elftia/.elftia-builtin-materialized-<old>.json flag no longer matches, next startup triggers re-materialize. Without bumping, only fresh installs get new content, existing users continue seeing old content.

Verification

# Delete local marker to force rerun verification
rm resources/design-studio-builtin/.synced.json
npm run sync:design

# See new file count / date in NOTICE
cat resources/design-studio-builtin/.synced.json
cat resources/design-studio-builtin/NOTICE | head -10

Common Operations

ScenarioCommand
Upgrade to upstream latest commit (including flag bump)npm run sync:design:update
Check if new upstream version available (don't modify files)npm run sync:design:update -- --dry-run
See if local is synced / which commit synced tocat resources/design-studio-builtin/.synced.json
Force refetch once (directory corrupted / suspect incomplete extract)npm run sync:design -- --force
Query upstream main latest SHAgh api repos/nexu-io/open-design/commits/main --jq .sha
Temporarily skip sync (CI debugging)Delete lock file → script logs "lock file missing, skipping" and exits
Replace upstream with private forkChange lock's repo field, export GITHUB_TOKEN then run sync

Troubleshooting

SymptomCause / Solution
[sync:design] already synced @ ... — skipping (use --force)Normal, marker hit. Add -- --force to refetch
HTTP 404 ... (private repo? set GITHUB_TOKEN)Repo in lock is private or commit doesn't exist, export GITHUB_TOKEN or verify SHA
no files matched subdirs [...]Directory in subdirs doesn't exist on that commit (upstream renamed/refactored), check directory layout on that commit
On Windows sync succeeds but product directory is emptydecompress filter path separator issue. Don't change split(/[\\/]/) in script to split('/')
User upgrades but doesn't see new built-in SkillForgot to bump FLAG_FILENAME, old user's old flag still there → skip materialize. sync:design:update auto bumps, if manually changing lock don't forget step 3
sync:design:update reports HTTP 403 ... rate-limited?GitHub unauthenticated API limited to 60 req/hr. Export GITHUB_TOKEN and rerun
sync:design:update syncs successfully but flag didn't changeCheck if FLAG_FILENAME in services/content/workspace/design/builtin/index.ts was renamed — script uses regex const FLAG_FILENAME = '\.elftia-builtin-materialized-...\.json'; to locate, will fail after renaming
CI pulls tarball too slow / exceeds bandwidthUse actions/cache to cache resources/design-studio-builtin/ by lock file hash as key, skip download on hit