# Roneat Plugin Dev

> Expert guide for creating plugins for Roneat Studio Pro — a professional Roneat Ek (Cambodian xylophone) score editor. Use when the user asks to build, write, scaffold, or debug a plugin for Roneat Studio Pro. Covers plugin structure, the roneat_api Python API (score editing, audio playback, UI injection, theme/language control), plugin.json manifests, lifecycle hooks, sandboxing/permissions, packaging as ZIP, and example plugin patterns.

- Skill: `vagabond404/roneat-plugin-dev` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds@latest add vagabond404/roneat-plugin-dev`
- Raw SKILL.md: https://api.skillmd.com/api/skills/vagabond404/roneat-plugin-dev/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Integrations & APIs
- Author: Vagabond404 (https://skillmd.com/u/vagabond404)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/vagabond404/roneat-plugin-dev

---


# Roneat Studio Pro — Plugin Development

## Plugin Architecture Overview

Plugins are **isolated Python packages** dropped into the app's `plugins/` directory. The host app scans that folder on startup, validates `plugin.json`, dynamically loads the Python entry point, and routes lifecycle events via **hooks**.

The **sandbox** restricts `os`, `shutil`, `subprocess`, `socket`, `urllib`, `requests`, and `http` unless the matching permission is declared in `plugin.json`.

**Plugin install path**: `%APPDATA%\RoneatStudioPro\plugins\` (runtime) or `<project_root>/plugins/` (dev).

---

## Plugin Structure

```
my-plugin/
├── plugin.json       # Manifest (required)
├── __init__.py       # Entry point (required)
└── assets/           # Optional: icons, data files
```

Distribute as **`my-plugin.zip`** — the zip must contain the folder (e.g. `my-plugin/plugin.json`).

---

## plugin.json — Manifest Reference

```json
{
  "id":          "my-plugin",
  "name":        "My Plugin",
  "version":     "1.0.0",
  "author":      "Your Name",
  "description": "Brief description of what this plugin does.",
  "entry_point": "__init__.py",
  "permissions": [],
  "dependencies": [],
  "hooks": {
    "on_load":       "on_load",
    "on_app_start":  "on_app_start",
    "on_unload":     "on_unload"
  }
}
```

**Required fields**: `id`, `name`, `version`, `author`, `entry_point`.

For all fields and optional hooks (`on_project_save`, `on_project_open`), see [references/manifest.md](references/manifest.md).

---

## roneat_api — Python API

Import the injected virtual package at the top of your entry point. **Do not install it via pip** — it is injected at runtime by the host app.

```python
import roneat_api.score   as score
import roneat_api.audio   as audio
import roneat_api.ui      as ui
import roneat_api.project as project
import roneat_api.core    as core
```

### Key namespaces at a glance

| Namespace | Purpose |
|---|---|
| `roneat_api.score` | Read/write the musical score text |
| `roneat_api.audio` | Play notes/frequencies, stop playback |
| `roneat_api.ui` | Inject tabs, buttons, dialogs, toasts; control theme/language |
| `roneat_api.project` | Get project data, trigger save/load |
| `roneat_api.core` | Read app settings |

For the full API reference with signatures and examples, see [references/api.md](references/api.md).

---

## Lifecycle Hooks

Hooks are Python **functions** in your entry point. Map them in `plugin.json` under `"hooks"`.

| Hook | When it fires |
|---|---|
| `on_load` | Plugin module loaded (app may not have UI yet) |
| `on_app_start` | App window fully built; **use this to inject UI** |
| `on_unload` | Plugin disabled/uninstalled |
| `on_project_save` | User saves a project — receives `data` dict |
| `on_project_open` | User opens a project — receives `data` dict |

```python
# __init__.py — minimal lifecycle skeleton
import roneat_api.ui as ui

def on_load():
    pass  # Module is ready; avoid UI calls here

def on_app_start():
    ui.show_toast("My Plugin loaded!", level="info")

def on_unload():
    pass  # Cleanup if needed
```

---

## Injecting UI

All UI injection must happen inside `on_app_start`.

### Add a Custom Tab

```python
import customtkinter as ctk
import roneat_api.ui as ui

class MyTab(ctk.CTkFrame):
    def __init__(self, master, **kwargs):
        super().__init__(master, **kwargs)
        ctk.CTkLabel(self, text="Hello from my plugin!").pack(pady=20)

def on_app_start():
    ui.add_custom_tab(
        tab_id="my_tab",
        label="My Tab",
        icon="🎵",
        widget_class=MyTab
    )
```

### Open a Custom Window

```python
def open_window():
    win = ui.create_window("My Tool", width=500, height=400)
    # win is a ctk.CTkToplevel; add widgets normally
```

### Show Feedback

```python
ui.show_toast("Done!", level="info")           # Bottom toast
ui.show_dialog("Error", "Something failed", dialog_type="error")
```

---

## Permissions Reference

Declare only the permissions your plugin actually uses:

| Permission | Unlocks |
|---|---|
| `"file_system"` | `os`, `shutil`, `pathlib`, `io` |
| `"network"` | `socket`, `urllib`, `requests`, `http` |
| `"system"` | Implied by `file_system`; covers `subprocess` |

```json
"permissions": ["file_system"]
```

---

## Packaging & Installation

1. Zip the plugin **folder** (not its contents directly):
   ```
   my-plugin/
   ├── plugin.json
   └── __init__.py
   → my-plugin.zip
   ```
2. Open **Roneat Studio Pro → Plugins tab → Install Plugin**.
3. Select `my-plugin.zip`. The plugin appears in the list immediately.
4. Enable/Disable/Reload/Uninstall from the same tab.

---

## Full Example: Score Transpose Plugin

See [references/example_transpose.md](references/example_transpose.md) for a complete, annotated, production-quality plugin that reads the score, transposes all notes up by N steps, and re-writes the score — with a custom tab UI.

---

## Common Pitfalls

- **UI in `on_load`** → Crashes. Always use `on_app_start` for UI.
- **Importing restricted modules without permissions** → `ImportError` with a security warning; plugin auto-disabled.
- **Long-running tasks in hooks** → Use `threading.Thread(daemon=True)` to avoid freezing the UI.
- **Assuming `roneat_api` is installable** → It is not a PyPI package. It is injected by the host at runtime only.
- **Missing required manifest fields** → Plugin silently fails to load.

