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
subdirsdoes not includeprompt-templates; the sync script hasNEVER_SYNC_SUBDIRSsafeguard that will hard fail if it's added back (resync will firstclearSubdirsdelete 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 (
.gitignoreis inverted forresources/design-studio-builtin/*with!…/prompt-templates/), a fresh clone can immediately reproduce the curated set without network access.
:::
Involved Files
| Path | Purpose | In git? |
|---|---|---|
resources/design-studio-builtin.lock.json | Pin 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.json | Marker — records last successful sync commit, hit to skip | ❌ (inside ignored directory) |
resources/design-studio-builtin/NOTICE | Auto-generated third-party attribution | ❌ |
scripts/sync-design-studio.mjs | Sync script (pull by lock) | ✅ |
scripts/update-design-studio.mjs | Auto-upgrade script — pull upstream HEAD + bump lock + sync + bump FLAG_FILENAME in one command | ✅ |
packages/desktop/app/main/services/content/workspace/design/builtin/index.ts | First startup materializer + FLAG_FILENAME | ✅ |
Trigger Times
| Trigger | Command / Hook | Mode |
|---|---|---|
npm install | postinstall hook | --soft (network failure doesn't block install) |
npm run setup / setup:native | Manual initialization | --soft |
npm run build:electron / build:official / build:steam | Between electron-vite build and electron-builder | Strict mode (failure fails build) |
npm run sync:design | Manual execution | Strict mode |
npm run sync:design -- --force | Manual execution (force refetch) | Strict mode + skip marker |
npm run sync:design:update | One-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:
- Read
resources/design-studio-builtin.lock.json, validate schema (must haverepo/commit/subdirs[]) - Compare local
.synced.jsonmarker — if all three fields match and no--force, skip and exit - Pull tarball:
- Public repo →
https://codeload.github.com/<repo>/tar.gz/<commit> - After setting
GITHUB_TOKEN/GH_TOKENenv vars →https://api.github.com/repos/<repo>/tarball/<commit>(private repos use this)
- Public repo →
- Extract with
decompress,strip: 1remove outer package name directory,filterkeep onlysubdirslisted in lock - Before extracting,
fs.rm(subdir, { recursive: true, force: true })clean each target subdir to avoid stale files after upstream deletes - Write marker
.synced.json - 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
Recommended Path: One-Click Automation
npm run sync:design:update
scripts/update-design-studio.mjs bundles three steps of the "upgrade" process into one action:
GET https://api.github.com/repos/<repo>/commits/HEAD— parse the SHA of upstream default branch HEAD- If same as
commitin lock → logalready up to date — nothing to doand exit 0 - Otherwise regex replace lock file's
commitfield (preserve original format), spawnsync-design-studio.mjsto pull and extract; if sync fails, automatically rollback lock, won't leave a dirty pointer to an unsynced commit - Use upstream new commit's short-sha (first 7 chars) to regex-rewrite
services/content/workspace/design/builtin/index.ts'sFLAG_FILENAMEconstant to.elftia-builtin-materialized-<short-sha>.json— old users' old flag filename in<userData>no longer matches, next startup rematerializes new content - Print
git diff/git commithints
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:
-
Change lock: Write the full 40-character SHA in
commitfield ofresources/design-studio-builtin.lock.json(not short hash — script does exact string comparison) -
Run sync:
npm run sync:designWhen
commitdoesn't match marker, automatically refetch, don't need--force.--forceis only for when marker and lock match but local directory is corrupted and you want to force refetch. -
Bump
FLAG_FILENAME: Editpackages/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>.jsonflag 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
| Scenario | Command |
|---|---|
| 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 to | cat resources/design-studio-builtin/.synced.json |
| Force refetch once (directory corrupted / suspect incomplete extract) | npm run sync:design -- --force |
| Query upstream main latest SHA | gh 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 fork | Change lock's repo field, export GITHUB_TOKEN then run sync |
Troubleshooting
| Symptom | Cause / 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 empty | decompress filter path separator issue. Don't change split(/[\\/]/) in script to split('/') |
| User upgrades but doesn't see new built-in Skill | Forgot 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 change | Check 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 bandwidth | Use actions/cache to cache resources/design-studio-builtin/ by lock file hash as key, skip download on hit |
Related Files / Modules
scripts/sync-design-studio.mjs— pull by lock + extractscripts/update-design-studio.mjs— one-click upgrade to upstream HEADresources/design-studio-builtin.lock.jsonpackages/desktop/app/main/services/content/workspace/design/builtin/index.ts—FLAG_FILENAME,materializeBuiltinResources,isBuiltinMaterialized- Design Studio Architecture Overview