.agents/skills/sync-plate-ui/SKILL.md
Handle $ARGUMENTS.
Goal: sync Plate UI registry components from this repo into a downstream app without flattening that app's product forks. The workflow reads Plate registry source, generated changelog JSON, and downstream local files; classifies upstream-owned versus target-owned hunks; writes reviewable artifacts; then applies only accepted sync rows.
This is not sync-shadcn. sync-shadcn compares upstream shadcn docs with
Plate docs. sync-plate-ui compares Plate UI registry source with a target app
that copied and customized Plate UI, such as ../potion.
This skill is consumer-side only. It reads generated Plate UI changelog JSON;
it does not create upstream Plate changelog entries. Upstream Plate devs author
those through the changeset skill and
tooling/data/plate-ui-changelog.mdx.
Downstream Plate UI files are usually mixed ownership. One hunk can be a local product fork, the next hunk can be a Plate bugfix the target should receive. Never treat an entire file as forked or safe to overwrite unless the source evidence proves it.
The right model is a three-way sync:
base: the last Plate UI source known to be applied to the targetupstream: current Plate UI source in this repolocal: current target repo fileIf the base is unknown, run bootstrap planning and stop. Do not pretend an LLM can safely reconstruct fork ownership from vibes.
This skill depends on
$autogoal. Load
autogoal before writing target sync state, run artifacts, dashboards, or
implementation changes.
Default goal template:
node .agents/skills/autogoal/scripts/create-goal-scratchpad.mjs \
--template sync-plate-ui \
--title "sync plate ui <target> <scope>"
autogoal owns lifecycle, completion, blocked semantics, and final
check-complete.mjs. sync-plate-ui owns downstream sync policy, target repo
mapping, fork classification, hunk decisions, and apply semantics.
Planning mode is the default. It may read the target repo and write artifacts
under the target's .plate-ui-sync/**, but it must not mutate target source.
Apply mode starts only when a later user message accepts a named plan, dashboard payload, component, or row. Then apply only the accepted rows.
Collaborative planning is for policy decisions, such as whether a downstream app should keep a local UX fork or follow Plate.
No direct micro-merge exception by default. For downstream product repos, "small" is not enough. Exact-match upstream-only files can be applied in apply mode without user-by-hunk review, but planning still records the row first.
Supported commands:
status: read-only summary of target sync state and likely next stepplan: write a component or range sync planreview: re-audit an existing plan against current Plate and target sourcedashboard: write a review board from open plan rowsapply: apply accepted rows from a copied dashboard payload or named planIf the first token is a command, dispatch to that command. Otherwise treat the argument as planning scope. Examples:
sync-plate-ui ../potion code-block-node
sync-plate-ui plan ../potion code-block-node
sync-plate-ui status ../potion
sync-plate-ui apply ../potion
If the target repo is omitted and cannot be inferred from the current working
directory, ask one focused question. Do not silently choose ../potion unless
the user named Potion or the active plan already names it.
The target repo owns its sync state:
<target>/.plate-ui-sync/status.json
<target>/.plate-ui-sync/forks/<component>.json
<target>/.plate-ui-sync/runs/<date>-<scope>/
<target>/.plate-ui-sync/dashboard.json
<target>/.plate-ui-sync/dashboard.md
status.json tracks what has actually landed:
{
"sourceRepo": "/Users/zbeyens/git/plate",
"targetRepo": "/Users/zbeyens/git/potion",
"lastReviewedAt": "YYYY-MM-DD",
"components": {
"code-block-node": {
"lastAppliedPr": 4989,
"lastAppliedSourceRef": "<plate-git-sha>",
"lastAppliedSourceHash": "<sha256-of-transformed-source>",
"targetFiles": ["src/registry/ui/code-block-node.tsx"],
"forks": ["toolbar-style"]
}
}
}
If status.json is missing, bootstrap from direct source evidence:
components.jsonpackage.jsonBootstrap mode may write a plan and fork ledger. It must not apply changes until the user accepts the baseline.
Prefer generated changelog JSON:
/registry/changelog/index.json
/registry/changelog/components.json
/registry/changelog/<event-id>.json
When working from the local Plate repo, read the same files at
apps/www/src/registry/changelog. components.json maps registry item ids to
event JSON. Each event should expose id, change source, release, affected
registry items, source files, migration notes, and diagnostics.
If generated JSON is missing or incomplete, treat that as weak evidence. Prove the sync from Plate registry source and PR-level or merge-base diffs before recommending apply. Prose alone is not enough.
Plate registry sources to inspect first:
apps/www/src/registry/registry-ui.tsapps/www/src/registry/registry-kits.tsapps/www/src/registry/registry-examples.tsapps/www/src/registry/ui/**apps/www/src/registry/components/**apps/www/src/registry/hooks/**apps/www/src/registry/lib/**apps/www/src/registry/app/**Do not run build:registry. Do not edit generated registry output.
Read target config before path assumptions:
components.jsonpackage.jsontsconfig.jsonsrc/registry/**For Potion-like targets, expect:
src/registry/**src/components/**@platejs/* and platejs versionsExisting target scripts are evidence, not authority. Audit them before relying on them.
For every component/file row, compute:
base -> upstream
base -> local
upstream -> local
Apply target transforms before comparing when the target uses different import aliases. Record every transform in the plan.
Classify at hunk or symbol level:
upstream-only: Plate changed, local still matches baselocal-only: target fork changed, Plate did notsame-change: both changed to equivalent resultconflict: both changed the same area differentlylocal-delete: target removed the Plate file or symbolrename-map: target renamed the file or exportunknown-base: no trustworthy baseNever collapse these into a single file-level decision unless every hunk has the same classification.
Use these decision labels:
pull-upstream: apply the Plate change to the targetkeep-fork: preserve target-owned behaviorsmart-merge: apply part of Plate while preserving named target behaviorreject-upstream: do not apply this Plate change to this targetdelete-target-residue: remove obsolete target fork codepackage-only: update package versions without copying UI sourceneeds-question: user decision requiredno-op: no target-owned change neededEvery smart-merge row must state:
Planning mode may:
.plate-ui-sync/** artifacts.plate-ui-sync/runs/**Planning mode must not:
Planning artifacts:
<run>/plan.md
<run>/inventory.json
<run>/inventory.md
<run>/component-diffs.md
<run>/target-files.txt
<run>/decision-counts.json
Do not persist .patch files. Inspect focused diffs and summarize the relevant
hunks.
Every plan must include:
# Sync Plate UI <target> <scope>
## Range
- Plate source ref
- target repo
- component scope
- changelog entries or fallback evidence
## Target Config
- package manager
- registry paths
- aliases
- existing sync scripts and whether they are trusted
## Inventory
| Component | Plate file | Target file | Base | Class | Decision | Evidence |
## Fork Ledger
| Component | File | Hunk/symbol | Keep/Pull/Ask | Reason |
## Apply Rows
| Row | Action | Files | Why | Verification |
## Questions
## Verification Plan
## Status Update Rule
Dashboard mode renders unresolved rows for user review. It writes:
<target>/.plate-ui-sync/dashboard.json
<target>/.plate-ui-sync/dashboard.md
If a target later adds an HTML dashboard script, use it. Do not invent a local browser dependency for the first version.
Dashboard rows support these actions:
Pull: apply Plate rowKeep Fork: preserve target rowSmart Merge: apply with named fork preservationReject: mark upstream row rejected for this targetAsk: copy a question-only payloadCopied apply payload:
$sync-plate-ui apply ../potion
Rows:
- code-block-node/4989/language-label: Pull note: safe upstream-only render hunk
- code-block-node/4989/toolbar-style: Keep Fork note: Potion toolbar owns layout
Questions:
- ai-menu/streaming: Should Potion keep its custom stream controls?
Rows mutate state only under Rows. Question rows are answered in chat and do
not mutate structured state.
Apply mode may mutate target source only for accepted rows.
Before applying:
Apply rules:
pull-upstream: copy or patch the upstream hunk after target transforms.keep-fork: update fork ledger only; do not change target source unless the
accepted row asks to harden the fork.smart-merge: patch only the named Plate-owned hunks and preserve named
target-owned hunks.reject-upstream: record the rejection with reason.delete-target-residue: delete only named obsolete target code.package-only: update target package versions with the target package
manager and lockfile.Never overwrite a whole target file unless all of these are true:
pull-upstreamAfter applying:
.plate-ui-sync/status.jsonStatus mode is read-only. It summarizes:
Output shape:
Status: <fresh | needs-plan | stale-source | stale-target | bootstrap-needed | blocked>
Target: <path>
Components tracked: <count>
Open decisions: <count>
Next: <command>
Review mode checks whether an existing plan is still true.
It may:
<run>/review.mdIt must not apply changes.
Verdicts:
fresh: plan rows still match current Plate and target sourcestale-plate: Plate source changed since the planstale-target: target files changed since the planstale-state: .plate-ui-sync/status.json contradicts the planblocked: a ref/file/base cannot be provenPotion is a good stress target because it contains copied registry files under
src/registry/**, app wrappers under src/components/**, and pinned Plate
packages. Its existing sync:plate and sync:potion-ui scripts must be read
before use; do not assume they sync Plate UI into Potion.
For ../potion code-block-node, inspect at minimum:
../potion/components.json../potion/package.json../potion/src/registry/ui/code-block-node.tsx../potion/src/registry/ui/code-block-node-static.tsxapps/www/src/registry/ui/code-block-node.tsxapps/www/src/registry/ui/code-block-node-static.tsxcode-block-node and related componentsExpected result is a plan that says which hunks to pull, which Potion hunks to keep, and what verification Potion owns.
The workflow must be agent-actionable:
If the component changelog is prose-only, record that as a blocker for perfect agent-native sync and fall back to source diffs plus explicit user acceptance.
Resolve these before broad planning:
autogoal loaded and active goal checked or created.components.json and package manager read..plate-ui-sync/status.json read or bootstrap mode recorded.Planning completion requires:
.plate-ui-sync/runs/**check-complete.mjs passes for the active goal planApply completion requires:
check-complete.mjs passes for the active goal planPlanning output:
Target: <path>
Scope: <component/range>
Plan: <target>/.plate-ui-sync/runs/.../plan.md
Dashboard: <path or N/A>
| Decision | Count |
| --- | ---: |
| pull-upstream | ... |
| keep-fork | ... |
| smart-merge | ... |
| needs-question | ... |
Next: review the plan, then invoke `sync-plate-ui apply <target>` with accepted rows.
Apply output:
Applied: <rows>
Kept forks: <rows>
Target verification: <commands/results>
Open decisions: <count or none>
Status: <target>/.plate-ui-sync/status.json updated