Back to Dyad

App naming and folder slugs

rules/app-naming.md

1.10.02.4 KB
Original Source

App naming and folder slugs

Read this when touching app display names, app folder paths, or any flow that creates/moves an app directory (create, copy, import, rename, blueprint approval, template apply).

  • Display names and folder names are separate concepts. All folder derivation and validation lives in src/shared/app_names.ts (renderer + main safe); DB/filesystem collision suffixing lives in src/ipc/utils/app_name_resolution.ts (main only). Do NOT introduce a new slugifier for app folders — template apply once had its own (allocateNewAppPath) and the two competing derivations caused pointless double folder moves (lumen-notes-2lumen-notes-2-1) because the allocator counted the app's own folder as a collision.
  • slugifyAppPath in src/shared/slugify.ts is only for GitHub repo / Vercel project name defaults (must stay ASCII). App folders use slugifyAppFolderName, which preserves CJK and transliterates accents.
  • Windows device names remain reserved when they have an extension (CON.txt). When sanitizing one, insert the safety suffix before the first dot (CON-app.txt); appending it after the extension (CON.txt-app) leaves the reserved base unchanged and fails validation.
  • Collision probing must exclude the app being renamed (DB row AND its own on-disk folder), or re-running the same rename inflates suffixes (Todo App 2Todo App 3).
  • Collision probing is deliberately capped at 1000 candidates to keep a pathological database/filesystem state from blocking a user action indefinitely. Exhausting the cap must surface a DyadErrorKind.Conflict with actionable context; auto-suffixing is not an unbounded guarantee.
  • A case-only folder rename (MyAppmyapp) must use fs.rename, never copy-then-delete: on case-insensitive filesystems (macOS/Windows defaults) source and destination are the same physical directory, so the delete step destroys the app.
  • Long-running operations that write inside an app must acquire the same per-app withLock(appId, ...) used by rename/location changes and re-read apps.path after acquiring it. A path captured before network or other async work can become stale and recreate files in the app's former directory. If a workflow releases the lock across an await (for example, while cancelling streams), re-fetch the row and recompute the path after reacquiring it.