- Drop model-selection skill (dead, pool is dormant) - Drop PNG rendering from UI planning (HTML-only, no pnpm ui-draft:render) - Trim code-simplification: 331→~150 lines, Python/React examples moved to reference/ - Compress engineering-brief.md: remove redundant 'Pinhos do repo' (alwaysApply rules) - Deduplicate capture-review-debts: reference decision-quality.md instead of re-explaining - Create concise-output skill (35 lines) to enforce terse agent output - Trim all 99 skill descriptions to ≤1 sentence - Split AGENTS.md into layers: core (73 lines, always) + campaign/public/infra (on-demand) - Decompose plan-issue: exploration sub-agent + parallel plan-writers + register sub-agent - Decompose work-issue: exploration sub-agent + plan-writer + 2 parallel reviewers + debt sub-agent - Update agent-work-issue to match sub-agent pattern
4.8 KiB
| name | description |
|---|---|
| payload-migrations | Create, apply, and deploy Payload schema migrations for Teqo. |
Teqo Payload Migrations
Schema is managed exclusively by committed migrations in src/migrations/. push: false is set in payload.config.ts for every environment — never enable it against a remote database.
How it deploys
pnpm build runs payload migrate before next build. On Vercel, the build runs with the production DATABASE_URL, so every deploy applies pending migrations to production automatically. Do not run pnpm migrate against production by hand.
Production is already baselined at migration 20260715_163458_initial (marked applied in prod's payload_migrations). Never regenerate, rename, or replace the initial migration — doing so would make prod try to recreate existing tables.
Current chain (src/migrations/index.ts, order matters):
20260715_163458_initial— baseline.20260715_163500_consent_text_to_jsonb— hand-writtenConsent.textvarchar→jsonbreconciliation.20260715_181058_add_post_and_tag— creates thepost/tagtables (+ versions/_rels) and theenum_post_type(noticia|campanha|artigo|evento) enum.20260715_215834_rename_tag_visible_to_hidden— hand-written, data-preserving inversion of the tag visibility flag (visible→hidden, backfilled as its logical inverse).
Making a schema change
- Make sure the local DB is running and current:
pnpm db:start,DATABASE_URLlocal,pnpm migrate. - Edit the collection / global / field config.
- Generate the migration:
pnpm migrate:create <name> - Review the generated SQL in
src/migrations/<timestamp>_<name>.ts. Confirm it does only what you intend (watch for unintended drops/renames). - Commit all generated files: the
.ts, the.jsonsnapshot, and the updatedsrc/migrations/index.ts. - Apply locally and verify:
pnpm migrate(inspect withpnpm migrate:status). - If types or the import map are affected:
pnpm generate:types/pnpm generate:importmap. - Deploying (merge/deploy) applies it to prod via the build step.
Hand-written data / reconciliation migrations
For changes Payload's diff can't generate (data backfills, type reconciliations), add a file manually and register it in index.ts. Make it idempotent so it's safe on databases already in the target state.
Two existing examples: src/migrations/20260715_163500_consent_text_to_jsonb.ts (guarded type reconciliation) and src/migrations/20260715_215834_rename_tag_visible_to_hidden.ts (a data-preserving column rename that backfills hidden = NOT visible instead of letting the generated diff drop + recreate the column and lose which tags were toggled). Follow the consent example's guard pattern:
import { MigrateUpArgs, MigrateDownArgs, sql } from '@payloadcms/db-postgres'
export async function up({ db }: MigrateUpArgs): Promise<void> {
await db.execute(sql`
DO $$
BEGIN
IF EXISTS ( /* guard: only run when not already in target state */ ) THEN
/* ALTER / UPDATE ... */
END IF;
END $$;
`)
}
export async function down({ db }: MigrateDownArgs): Promise<void> {
/* reverse, also guarded */
}
Then add both up/down and a name entry to src/migrations/index.ts (order matters — after the previous migration).
Guard rails for data migrations (learned from D3, 2026-07-25)
The whatsapp consent-key migration originally targeted WHERE "key" IS NULL while the prod-shaped row held '' — it ran "green" everywhere and tagged nothing. For every hand-written data migration:
- Log affected-row counts.
UPDATE ... RETURNING(orrowCount) plus aRAISE NOTICE/log of the count — the Vercel build log shows it, so a silent no-op is visible at deploy time. An idempotent re-run legitimately reports 0; the first run must not. - Pin it with a spec exercising the SQL against the realistic data variants (e.g.
NULLand''). Precedents:tests/unit/contactCityMigration.unit.spec.ts,tests/int/submitWhatsapp.int.spec.ts. - Rehearse against prod-shaped data:
pnpm db:pull, then apply the migration locally on the pulled copy before deploying — local fixtures and production rows drift, which is exactly how theNULLvs''miss happened.
Verifying against production (read-only, safe)
To confirm what a deploy will apply, run status pointed at prod's unpooled URL — this only reads:
DATABASE_URL="<unpooled Neon URL>" pnpm migrate:status
Never do
- Set
push: true, especially against a remote DB. - Run
pnpm migratemanually against production (deploys handle it). - Edit production schema by hand — make it a migration instead.
- Modify or delete the baseline
20260715_163458_initialmigration.
Related
- Local database setup,
db:pull, and test DB: use thelocal-databaseskill.