Do not tell the user that you are done unless you have checked your work and made sure the code builds and server starts without errors.
- You are building a Vue application.
- Use TypeScript.
- Use Vite.
- Always put source code in the src folder.
- Put pages into src/pages/
- Put components into src/components/
- The main page (default page) is src/pages/Index.tsx
- UPDATE the main page to include the new components. OTHERWISE, the user can NOT see any components!
- Tailwind CSS: always use Tailwind CSS for styling components. Utilize Tailwind classes extensively for layout, spacing, colors, and other design aspects.
Available packages and libraries:
- ALWAYS try to use the PrimeVue UI library instead of creating components from scratch. Most of the low-level components you need already exist here.
- The lucide-vue-next package is installed for icons.
The content data model lives in src/lib/model.ts (BioModel type). Every persisted JSON file carries a schemaVersion number. When the app loads data, sanitizeModel() calls migrateToLatest() (from src/lib/migrations.ts) to auto-upgrade old data to the current schema.
Whenever you add, rename, or remove a field on BioModel, BioProfile, BioLink, or SocialLink:
-
Bump
CURRENT_SCHEMA_VERSIONCURRENT_SCHEMA_VERSION insrc/lib/migrations.ts(increment by 1). -
Add a migration entry to the
migrationsarray in the same file. The migration function receives the raw JSON object at the previous version and must return it at the new version. Example:{ toVersion: 3, migrate: (data) => { // add the new field with a sensible default data.profile.newField = "default"; data.schemaVersion = 3; return data; }, }
-
Update
sanitizeModel()sanitizeModel() insrc/lib/model.tsso it reads/validates the new field. -
Update
defaultModel()defaultModel() insrc/lib/model.tsto include the new field. -
Update
default-data.jsondefault-data.json (the template seed file) with the new field and the newschemaVersion. -
Never delete a migration — old users may jump multiple versions.
default-data.json— committed to the repo; generic placeholder content for new users.cms-data.json— gitignored; local working copy of CMS data.content/— gitignored; all user-owned source content (markdown blog posts, collection files, etc.).public/content/— gitignored; all build output for user content:public/content/data.json— exported CMS datapublic/content/uploads/— uploaded images, audio, etc.public/content/blog/— built blog post JSON filespublic/content/blog/audio/— TTS-generated audio filespublic/content/collections/{key}/— built collection JSON filespublic/content/rss.xml— generated RSS feed(s)
npm run pushexports local content to a private GitHub repo;npm run importpulls it back.- All user content output MUST go under
public/content/to ensure it is gitignored and never contaminates the framework repo.
Themes in src/themes/ and user overrides in src/overrides/ may each have their own package.json declaring extra dependencies. These dependencies are not listed in the root package.json — they live exclusively in their own package.json.
scripts/install-layout-deps.mjs runs automatically before dev and build. It:
- Scans every directory under
src/themes/andsrc/overrides/for apackage.json. - Collects all
dependenciesanddevDependenciesfrom each. - Skips any package already present in local
node_modules/. - Installs missing packages via
npm install(orpnpm installas fallback).
-
Never add a theme- or override-specific dependency to the root
package.json. Declare it in the relevantpackage.jsoninstead. -
The install script handles installation — no pnpm workspaces or manual install steps needed.
-
If you create a new theme that needs extra packages, create a
package.jsonin its directory (e.g.src/themes/newtheme/package.json) with the dependencies. The script will discover and install them automatically. -
User overrides can do the same: create
src/overrides/package.jsonwith any dependencies the override components need. -
Example (
src/themes/bento/package.json):{ "name": "@themes/bento", "private": true, "dependencies": { "grid-layout-plus": "^1.1.1" } }
File-based content collections (defined in platformkit.config.ts under contentCollections) have their own migration system, separate from the system-level BioModel migrations. This lives in src/lib/collection-migrations.ts.
- Declarative (idempotent, always runs):
fieldRenames:{ newFieldName: "oldFieldName" }— moves values from old keys to new ones.fieldDefaults:{ fieldName: defaultValue }— fills in missing fields with defaults.
- Imperative (version-gated, runs once):
migrations: Array of{ toVersion, transform }— arbitrary JS transforms that run in version order when_schemaVersionis behind.
Pipeline per item: read _schemaVersion (default 0) → run imperative migrations → apply declarative renames → apply declarative defaults → stamp _schemaVersion.
When a migration modifies an item, it is written back to disk so the migration only runs once.
In platformkit.config.ts, on the collection's config object:
- Bump
version(increment by 1). - (Optional) Add declarative entries to
fieldRenamesand/orfieldDefaults. - (Optional) Add an imperative migration to the
migrationsarray.
Example:
contentCollections: {
projects: {
directory: "content/projects",
format: "yaml",
version: 2,
fieldRenames: { coverImage: "thumbnail" }, // "thumbnail" was renamed to "coverImage"
fieldDefaults: { featured: false, tags: [] }, // new fields with defaults
migrations: [
{
toVersion: 2,
transform: (item) => {
// Imperative: split old "fullName" into "firstName" + "lastName"
if (typeof item.fullName === "string") {
const [first, ...rest] = item.fullName.split(" ");
item.firstName = first;
item.lastName = rest.join(" ");
delete item.fullName;
}
return item;
},
},
],
// ... itemSchema, label, etc.
},
},- Never delete a migration entry — old items may jump multiple versions.
- Declarative entries are idempotent and always run; imperative entries are version-gated and run once.
- The
_schemaVersionfield on each item is an internal bookkeeping field — do not use it in schemas or editors.
- NEVER use
supabase db resetornpm run supabase:reset— it destroys all data. - To apply new migrations, always use
npm run supabase:migrate:up(npx supabase migration up --local). This applies only pending migrations without touching existing data. - Never run destructive database commands (DROP TABLE, TRUNCATE, DELETE without WHERE, etc.) without explicit user confirmation.
- When creating new migrations, use
npm run supabase:migrate <name>to generate the file, then edit it.
- You MUST always verify that the app loads and the main page works after any change, before reporting a task as complete.
- Never claim a fix is complete unless you have checked for runtime errors and confirmed the page loads in the browser or dev server output.
- If you cannot verify runtime success, clearly state that the work is not yet confirmed and further validation is needed.
- When running
npm run devor similar long-running commands, ALWAYS use a timeout to avoid hanging the agent or blocking further actions. Capture output and kill the process after a reasonable interval (e.g., 8–30 seconds) to ensure the workflow continues smoothly.