| name | platforma-dev |
| description | Platforma SDK monorepo development — building packages, do-pack, using local SDK in blocks and desktop app, turbo filters, dev vs normal builds, pnpm overrides workflow, changesets, PRs, running local platforma server for tests. |
| user-invocable | true |
Platforma SDK monorepo development
Quick reference
| Task | Command | Run from |
|---|
| Install deps | pnpm install | monorepo root |
| Build all | pnpm build | monorepo root |
| Build all (dev) | pnpm build:local | monorepo root |
| Pack all packages | pnpm do-pack | monorepo root |
| Pack single package | pnpm do-pack --filter="<pkg>" | monorepo root |
| Reset everything | pnpm reset | monorepo root |
| Desktop app dev | pnpm build && pnpm run dev | desktop app root |
Building the monorepo
First-time setup
pnpm install
pnpm build
Node >= 22 required. Package manager: pnpm 9.14+.
Build commands
pnpm build runs turbo run build, which compiles every package in dependency order. Turbo caches outputs — unchanged packages are skipped on subsequent builds.
pnpm do-pack runs turbo run do-pack, which first builds (if needed), then creates package.tgz in every package directory. These .tgz files are what gets consumed by blocks and the desktop app.
Selective builds with turbo filters
Build or pack specific packages instead of the full monorepo. Turbo flags like --filter pass through from pnpm do-pack / pnpm build:
pnpm do-pack --filter="@platforma-sdk/model"
pnpm do-pack --filter="@platforma-sdk/model..."
pnpm do-pack --filter="...@platforma-sdk/model"
pnpm build --filter="@platforma-sdk/ui-vue"
pnpm do-pack --filter="@platforma-sdk/model" --filter="@platforma-sdk/ui-vue"
Filter syntax:
--filter="pkg" — the package + its dependencies
--filter="pkg..." — the package + all its dependencies (what it needs)
--filter="...pkg" — the package + all its dependents (what needs it)
For iteration, pack only what you changed. Example: if you modified sdk/model/, run pnpm do-pack --filter="@platforma-sdk/model" instead of the full pnpm do-pack.
Force rebuild (bypass cache)
pnpm build --force
pnpm do-pack --filter="pkg" --force
Key packages
| Package | Path | Typical consumers |
|---|
@platforma-sdk/model | sdk/model/ | Block model definitions |
@platforma-sdk/ui-vue | sdk/ui-vue/ | Block UI (Vue components) |
@platforma-sdk/workflow-tengo | sdk/workflow-tengo/ | Block workflows (Tengo) |
@platforma-sdk/test | sdk/test/ | Block test utilities |
@milaboratories/uikit | lib/ui/uikit/ | Desktop app, block UI |
@milaboratories/pl-middle-layer | lib/node/pl-middle-layer/ | Desktop app |
@milaboratories/pl-model-common | lib/model/common/ | Desktop app, blocks |
@platforma-sdk/block-tools | tools/block-tools/ | Block build tooling |
@platforma-sdk/tengo-builder | tools/tengo-builder/ | Workflow compilation |
@platforma-sdk/package-builder | tools/package-builder/ | Software package builds |
Using local SDK packages in blocks
Blocks are separate pnpm workspaces. They pull SDK packages from the npm registry via catalog: versions in their pnpm-workspace.yaml. To develop with local (unpublished) SDK changes:
Step 1: Build and pack in the platforma monorepo
pnpm do-pack
pnpm do-pack --filter="@platforma-sdk/model" --filter="@platforma-sdk/ui-vue"
This creates package.tgz files in each package directory (e.g., sdk/model/package.tgz).
Step 2: Add pnpm overrides in the block
In the block's root package.json, add (or uncomment) a pnpm.overrides section pointing to the .tgz files:
{
"pnpm": {
"overrides": {
"@platforma-sdk/model": "file:/path/to/platforma/sdk/model/package.tgz",
"@platforma-sdk/ui-vue": "file:/path/to/platforma/sdk/ui-vue/package.tgz"
}
}
}
Use absolute paths or relative paths from the block root (e.g., file:../../core/platforma/sdk/model/package.tgz).
Convention: Most blocks keep commented-out overrides under "//pnpm" or "//pnpm2" keys — these are inactive JSON that serve as templates. Copy the structure to a real "pnpm" key and update the paths to your local environment.
Override all changed packages. If you modified multiple packages in the platforma monorepo, override all of them in the block — not just one. SDK packages have internal dependencies; overriding @platforma-sdk/model to a newer version while keeping @platforma-sdk/ui-vue at the old registry version causes type mismatches and build failures. The linked trio (model, ui-vue, test) should always be overridden together. Similarly, if you changed @milaboratories/uikit alongside @platforma-sdk/ui-vue, override both.
Step 3: Install and build
pnpm install
pnpm build
No --force is needed. When pnpm install resolves overrides pointing to local .tgz files, the lockfile changes (different integrity hashes). Turbo detects the lockfile change and invalidates the cache automatically.
Step 4: Iterate
After making more changes in the platforma monorepo:
pnpm do-pack --filter="@platforma-sdk/model"
pnpm install
pnpm build
pnpm tracks the integrity hash of file: dependencies. When the .tgz content changes (even at the same path), pnpm install updates the lockfile, and turbo rebuilds.
Cleanup before committing
Remove (or comment out) the pnpm.overrides section and restore the lockfile:
pnpm install
Never commit pnpm.overrides pointing to local paths — CI will fail.
Using local SDK packages in the desktop app
The desktop app (platforma-desktop-app/) uses pnpm with catalog: versions in pnpm-workspace.yaml, the same pattern as blocks. It consumes platforma SDK packages from the npm registry.
Step 1: Build and pack in the platforma monorepo
pnpm do-pack
Step 2: Add pnpm overrides in the desktop app
In the desktop app's root package.json, add overrides to the existing pnpm.overrides section:
{
"pnpm": {
"overrides": {
"@sentry/core": "10.40.0",
"jsonfile": "^6.0.1",
"@platforma-sdk/model": "file:../platforma/sdk/model/package.tgz",
"@milaboratories/uikit": "file:../platforma/lib/ui/uikit/package.tgz"
}
}
}
Use relative paths from the desktop app root (e.g., file:../platforma/sdk/model/package.tgz). Keep existing overrides (@sentry/core, jsonfile) in place — only add the packages you're developing.
Override all changed packages together. The same rule as for blocks: SDK packages have internal dependencies, so partial overrides cause type mismatches and build failures.
Step 3: Install and build
pnpm install
pnpm build
Step 4: Iterate
pnpm do-pack --filter="@platforma-sdk/model"
pnpm install
pnpm build
Running the desktop app in development mode
The desktop app uses Vite + Electron with hot-reload watchers.
First-time setup (or after pnpm clean-build)
The internal workspace packages (@platforma/core, @platforma/ipc, etc.) must be built before the dev server can start. The Vite renderer dev server needs @platforma/core for dependency pre-bundling at startup.
cd /path/to/platforma-desktop-app
pnpm install
pnpm build
pnpm run dev
For a faster start, pnpm --filter=@platforma/core run build is the minimum needed before pnpm run dev. The watch script builds the remaining packages automatically.
Subsequent runs
pnpm run dev is sufficient — the watch script rebuilds internal packages on change.
Running from Claude Code
pnpm run dev is a long-running process (Vite dev server + Electron). Run it as a background task:
Bash tool:
command: "cd /path/to/platforma-desktop-app && pnpm run dev 2>&1"
run_in_background: true
This returns a task ID. Use:
TaskOutput (with block: false) to read logs
TaskStop to terminate the app cleanly (kills both Vite and Electron)
Do NOT append & to the command — run_in_background handles backgrounding. Adding & causes the shell to exit immediately, orphaning the Electron process with no way to read logs or stop it.
Cleanup before committing
Reset package.json and pnpm-lock.yaml to their original state:
git checkout -- package.json pnpm-lock.yaml
pnpm install
Never commit pnpm.overrides pointing to local paths.
Normal build vs dev build
The difference applies to software packages — binary tools (MiXCR, ptexter, etc.) that run on the platform backend.
Normal build (pnpm build)
Software descriptors (sw.json) reference the remote registry using the version from package.json. The platform downloads the published software from the registry at runtime.
Use normal build when:
- You only changed workflow, model, or UI code (not the underlying software)
- You want to test on a remote server — the server pulls published software versions
- The published software version matches what you need
Dev build (pnpm build:dev / PL_PKG_DEV=local)
Software descriptors reference local file paths. The platform loads software from the local filesystem.
Use dev build when:
- You changed the software itself and need to test locally
- You need to run with the desktop app using local software binaries
In the platforma monorepo:
pnpm build:local
pnpm do-pack:local
In blocks:
pnpm build:dev
The block's turbo.json includes PL_PKG_DEV in the build task's env array, so turbo invalidates the cache when switching between normal and dev modes.
Running tests against a local backend
Spin up a local backend when the test task reads PL_* env (monorepo turbo.json test task passes through PL_ADDRESS, PL_TEST_USER, PL_TEST_PASSWORD). Not needed for unit tests, type checks, or anything that doesn't hit a server.
Below, <workspace> is the root of your mictx workspace — the directory holding mictx-helper/, core/platforma/, blocks/, and the scripts/ symlink the workspace sync creates. The wrapper lives at <workspace>/scripts/run-platforma.sh. The wrapper auto-attaches <workspace>/core/platforma/assets/ as data library library and enables --debug-enabled — required by some monorepo integration tests; see the run-platforma skill for the full list of hard-coded backend flags.
<workspace>/scripts/run-platforma.sh start --bg
eval "$(<workspace>/scripts/run-platforma.sh env)"
<workspace>/scripts/run-platforma.sh stop
Run a single test package
Root build/test scripts are turbo wrappers. Pass --filter=<pkg> to them; turbo handles dependency scoping. turbo run test depends on build, so build deps first (the trailing ... in the build filter pulls in dependencies):
cd <workspace>/core/platforma
pnpm install
pnpm build --filter='@platforma-sdk/workflow-tengo-tests...'
eval "$(<workspace>/scripts/run-platforma.sh env)"
pnpm test --filter='@platforma-sdk/workflow-tengo-tests'
pnpm test --filter='@platforma-sdk/workflow-tengo-tests' -- src/pt/pt.test.ts
pnpm test --filter='@platforma-sdk/workflow-tengo-tests' -- src/pt/pt.test.ts -t 'pt simple test'
The -- separator is required to forward args through turbo to vitest; without it, turbo eats them.
Run all integration tests
pnpm test:local
Committing and pushing in the platforma monorepo
The platforma monorepo has husky git hooks that run full-monorepo checks. These are slow but mandatory.
Hooks
| Hook | Runs | Time (cached) | Time (cold) |
|---|
| pre-commit | pnpm fmt (turbo fmt across all packages) | ~1-2s | ~2-3 min |
| pre-push | pnpm check (turbo check: tsc + oxlint + oxfmt) | ~5-10s | ~3-5 min |
Both hooks skip in CI ([ -n "$CI" ] && exit 0).
Efficient commit workflow
Before committing, do these steps to ensure hooks pass on the first try:
-
If you added/changed dependencies in any package.json: Run pnpm install from the monorepo root. This updates pnpm-lock.yaml. Stage the lockfile — the pre-push tsc check will fail if a new dependency isn't installed.
-
Format and lint changed packages first:
pnpm -C <package-path> fmt
This runs oxlint (with --deny-warnings --fix) and oxfmt. Fix any lint errors it reports (unused imports, etc.) before committing. The pre-commit hook runs the same check — doing it first avoids a wasted 2-minute turbo run that fails at the end.
-
Stage all files including any formatting changes and pnpm-lock.yaml.
-
Commit. The pre-commit hook runs pnpm fmt across all packages. With step 2 done, changed packages are turbo-cached and this takes ~1-2s. Use a long timeout (600s) since cold runs can take minutes.
-
Push. The pre-push hook runs pnpm check (tsc + lint + format check). With everything formatted and type-checked, this is mostly cache hits. Use a long timeout (600s).
Common pitfalls
- Adding a dependency without
pnpm install: The pre-push tsc check will fail with Cannot find module 'X'. Always run pnpm install after editing any package.json.
- Unused imports: oxlint with
--deny-warnings treats unused imports as errors. The pre-commit hook will fail. Run pnpm -C <pkg> fmt first — it auto-fixes unused imports.
- Timeout: Both hooks run turbo across 100+ packages. Use
timeout: 600000 (10 min) for commit and push bash commands from Claude Code.
- Pre-commit re-stages files: The hook runs
pnpm fmt then xargs git add on originally-staged files. If formatting changes a file, it gets re-staged automatically.
Changesets and PRs
Every PR to the platforma monorepo must include a changeset. Without it, no version bump or release happens.
Creating a changeset
Add a markdown file in .changeset/ (use any kebab-case name):
---
"@platforma-sdk/model": minor
"@platforma-sdk/ui-vue": minor
---
Type-safe `usePlugin` composable with `PluginHandle` branded type.
The YAML frontmatter lists affected packages with bump type (patch / minor / major). The body is a human-readable description of the change.
CRITICAL: list every modified package
The changeset must include every package whose source files changed in the PR — not just the "primary" package. If a PR adds a new tool but also adds exports to pl-middle-layer, both packages need entries in the changeset. Without an entry, the package won't get a version bump and npm consumers will get the old version without the new exports.
Verification step: Before creating the changeset, run git diff --name-only origin/main...HEAD and check which package.json directories have modified files. Each one needs an entry.
Package naming
Use the full npm package name (including scope):
"@platforma-sdk/model", "@platforma-sdk/ui-vue", "@platforma-sdk/test"
"@milaboratories/uikit", "@milaboratories/pl-middle-layer", etc.
"@milaboratories/build-configs", "@milaboratories/ts-builder", etc.
Linked packages
@platforma-sdk/model, @platforma-sdk/ui-vue, and @platforma-sdk/test are linked in the changeset config. When one bumps, the others bump together. Internal dependency updates propagate as patch.
Empty changesets
If a PR has no user-facing changes (CI fixes, internal refactors), use an empty changeset:
---
---
Examples
Bug fix in one package:
---
"@milaboratories/pl-model-common": patch
---
Version bump
Feature spanning multiple packages:
---
"@milaboratories/build-configs": minor
"@milaboratories/uikit": patch
"@platforma-sdk/ui-vue": patch
---
Replace css-injected-by-js with lib-inject-css for per-component CSS imports
Pre-commit checklist
After pnpm install && pnpm build, before committing, run git status and verify:
- No unstaged changes remain. Every modified file must be either staged or intentionally excluded. If
pnpm install modified pnpm-lock.yaml, it must be committed.
pnpm-lock.yaml is included whenever pnpm-workspace.yaml changed. CI rejects PRs without matching lock file updates.
- Changeset file is included. Every PR needs one in
.changeset/.
- Changeset covers all modified packages. Run
git diff --name-only origin/main...HEAD and verify every package with changed source files has an entry in the changeset. Missing entries mean no version bump — npm consumers get stale code.
- After pushing, run
git diff --name-only origin/main..HEAD to confirm the PR contains exactly the files you expect.
Troubleshooting
Build fails with missing binaries (rolldown, oxfmt, etc.)
Run pnpm install — dev dependencies with CLI binaries may need to be installed.
Turbo uses stale cache after changing overrides
This should not happen. pnpm install changes the lockfile when overrides change, which invalidates turbo cache. If it does happen, use --force:
pnpm build --force
Block build fails after adding overrides for only some packages
Override all related packages together. SDK packages have internal dependencies — overriding @platforma-sdk/model to a newer version while keeping @platforma-sdk/ui-vue at the old version causes type mismatches. Override the linked trio (model, ui-vue, test) together.
pnpm install warns about unmet peer dependencies
Expected when mixing SDK versions. The warnings are usually safe to ignore during local development. Address them if the build fails.
Common workflows
Developing an SDK feature and testing in a block
cd /path/to/platforma
pnpm do-pack --filter="@platforma-sdk/model" --filter="@platforma-sdk/ui-vue" --filter="@platforma-sdk/test"
cd /path/to/my-block
pnpm install
pnpm build
Testing SDK changes in the desktop app
cd /path/to/platforma
pnpm do-pack
cd /path/to/platforma-desktop-app
pnpm install
pnpm build
pnpm run dev
git checkout -- package.json pnpm-lock.yaml
pnpm install
Running integration tests locally
See the "Running tests against a local backend" section above. TL;DR: ./scripts/run-platforma.sh start --bg + eval "$(./scripts/run-platforma.sh env)" + pnpm test --filter='<pkg>'.