Skip to content

docs: cover undocumented Indigo state-ID and plugin-host rules#41

Merged
simons-plugins merged 3 commits into
simons-plugins:mainfrom
Highsteads:clives-rules-from-2026-05
May 10, 2026
Merged

docs: cover undocumented Indigo state-ID and plugin-host rules#41
simons-plugins merged 3 commits into
simons-plugins:mainfrom
Highsteads:clives-rules-from-2026-05

Conversation

@Highsteads
Copy link
Copy Markdown

@Highsteads Highsteads commented May 10, 2026

Summary

Five small additions to the plugin-dev concepts documenting Indigo plugin-host rules I hit while building a capture-all Zigbee2MQTT bridge plugin (importing every MQTT field as a dynamic Indigo state). All come from real LowLevelBadParameterError or AttributeError failures that have no upstream documentation — and the error messages don't identify which key/method is at fault, so each one cost meaningful debug time.

These are additions and clarifications only — no existing content removed.

What's added

concepts/devices.md (+143 lines)

  • State ID naming rules — must be camelCase ASCII, no underscores (despite XML allowing them), no non-ASCII letters. Includes a snake_case → camelCase sanitiser and a strict validator helper.
  • Reserved state names — don't shadow native device properties; in particular batteryLevel silently routes updateStateOnServer writes to the native int property instead of Custom States, with no error. Use battery (Integer) instead.
  • Dynamic state declaration — three subtle rules for plugins overriding getDeviceStateList():
    1. The parent's list is a LIVE reference; mutating it permanently corrupts subsequent reads. Must shallow-copy via list(...).
    2. dev.pluginProps keys via replacePluginPropsOnServer cannot start with underscore (distinct from self.pluginPrefs).
    3. Roll back pluginProps on stateListOrDisplay failure so a single bad key doesn't permanently poison the device record.
  • deviceUpdated self-loop guard — when a plugin uses subscribeToChanges() AND writes its own device states, the guard must check pluginId (not id) at the top of deviceUpdated. Per-id checks don't prevent cross-device A→B→A loops.

concepts/events.md (+40 lines)

  • Common-mistake section warning that indigo.server.fireEvent("eventId") and self.triggerEvent("eventId", ...) both look correct but raise AttributeError — neither exists on PluginBase / ServerInfo. Documents the correct pattern with triggerStartProcessing / triggerStopProcessing lifecycle storing trigger objects, plus indigo.trigger.execute(trigger_object).

concepts/actions.md (+71 lines)

  • uiPath attribute — must be PascalCase with no spaces; spaces cause NSInternalInconsistencyException crash in the Indigo client (confirmed 2026-04-30).
  • Calling other plugins' actions — known prop-name pitfalls:
    • Pushover (io.thechad.indigoplugin.pushover): action ID is \"send\" (not \"sendPushover\" — does not exist); prop names msgTitle/msgBody/msgUser/msgPriority/msgSound; priority is a string.
    • Email+ (com.indigodomo.email): use indigo.server.sendEmailTo() directly; calling executeAction(\"sendEmail\", props=...) silently drops the props dict during cross-plugin serialization, email never sends.

concepts/plugin-preferences.md (+24 lines)

  • Counter-warning that the existing "prefix hidden prefs with underscore" advice applies to self.pluginPrefs only — device-level dev.pluginProps keys cannot start with _ (writes via replacePluginPropsOnServer are XML-validated and reject them). Updates the Best Practices line to call out the distinction.

troubleshooting/common-issues.md (+14 lines)

  • New LowLevelBadParameterError -- illegal XML tag name character section linking back to the state-ID rules in concepts/devices.md, since the error message doesn't identify which key is bad.

Why these matter

Each rule below caused a real, hard-to-diagnose failure during the Z2M bridge work. Indigo's error messages and SDK don't mention any of them:

Rule Symptom Time to diagnose
State IDs forbid underscores Cryptic illegal XML tag name character on otherwise-valid Python identifiers ~15 min
getDeviceStateList returns LIVE reference Random failures that get worse over time as duplicates accumulate ~10 min
dev.pluginProps rejects _-prefixed keys Same XML error from replacePluginPropsOnServer, no indication that the key is the issue ~10 min
self.triggerEvent doesn't exist Custom events silently never fire (caught earlier on a different plugin)

Worth saving the next person the same debugging cycle.

Test plan

  • Confirm the new sections render correctly on GitHub
  • Skim each addition against your existing tone/style — happy to revise
  • Verify concepts/devices.md anchor links work (#state-id-naming-rules-undocumented-but-strict from the troubleshooting cross-link)

Notes

  • All edits are markdown additions — no code or schema changes
  • I was deliberately conservative: didn't refactor existing sections, added new ones with anchor headers so cross-links are stable
  • Branch: clives-rules-from-2026-05 on Highsteads/indigo-claude-plugin (fork)
  • Happy to split into smaller PRs (one per topic) if you'd prefer; let me know

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Documentation
    • Expanded plugin developer docs: action grouping into UI sub-menus, cross-plugin action invocation guidance, stricter custom state ID naming and reserved names, device state handling best practices, event-handling recommendations, and troubleshooting for XML serialization errors.
    • Clarified underscore prefix rules for plugin-level vs device-level properties.
  • Chores
    • Bumped plugin version to 1.9.5.

Review Change Stack

Five small additions to the plugin-dev concepts documenting rules I
hit while building a Zigbee2MQTT bridge plugin (capture-all dynamic
states).  All come from real LowLevelBadParameterError or
AttributeError failures with no upstream documentation.

concepts/devices.md
  + State ID naming rules — must be camelCase ASCII, no underscores
    (despite XML allowing them), no non-ASCII letters.  Includes a
    snake_case -> camelCase sanitiser and a strict validator.
  + Reserved state names — don't shadow native device properties; in
    particular `batteryLevel` silently routes writes to the native
    int property instead of Custom States.  Use `battery` (Integer).
  + Dynamic state declaration — three subtle rules for plugins
    overriding getDeviceStateList():
      1. The parent's list is a LIVE reference; mutating it
         permanently corrupts subsequent reads (must shallow-copy).
      2. dev.pluginProps keys via replacePluginPropsOnServer cannot
         start with underscore (distinct from self.pluginPrefs).
      3. Roll back pluginProps on stateListOrDisplay failure to
         avoid a sticky bad-key state.
  + deviceUpdated self-loop guard — when a plugin uses
    subscribeToChanges() AND writes its own device states, the guard
    must check pluginId (not id) at the top of deviceUpdated.

concepts/events.md
  + Common-mistake section warning that `indigo.server.fireEvent()`
    and `self.triggerEvent()` both look correct but raise
    AttributeError.  Use the triggerStartProcessing /
    triggerStopProcessing lifecycle plus
    indigo.trigger.execute(trigger_object).

concepts/actions.md
  + uiPath="..." attribute — must be PascalCase with no spaces;
    spaces cause NSInternalInconsistencyException in the Indigo
    client (confirmed 2026-04-30).
  + Calling other plugins' actions — Pushover (action ID is "send",
    not "sendPushover"; msg* prop names; priority is a string) and
    Email+ (use indigo.server.sendEmailTo() directly; executeAction
    "sendEmail" silently drops the props dict during cross-plugin
    serialization).

concepts/plugin-preferences.md
  + Counter-warning that the existing "prefix hidden prefs with
    underscore" advice applies to self.pluginPrefs ONLY — device-
    level dev.pluginProps keys cannot start with `_` (writes via
    replacePluginPropsOnServer are XML-validated and reject them).

troubleshooting/common-issues.md
  + LowLevelBadParameterError section linking back to the
    state-ID rules in concepts/devices.md, since the error message
    doesn't identify which key is bad.
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 10, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 3d6558c6-8fea-460c-899e-f192fb87e31b

📥 Commits

Reviewing files that changed from the base of the PR and between d650b8b and 7a737fc.

📒 Files selected for processing (5)
  • .claude-plugin/marketplace.json
  • .claude-plugin/plugin.json
  • docs/plugin-dev/concepts/actions.md
  • docs/plugin-dev/concepts/devices.md
  • docs/plugin-dev/concepts/plugin-preferences.md
✅ Files skipped from review due to trivial changes (3)
  • .claude-plugin/marketplace.json
  • .claude-plugin/plugin.json
  • docs/plugin-dev/concepts/plugin-preferences.md
🚧 Files skipped from review as they are similar to previous changes (1)
  • docs/plugin-dev/concepts/devices.md

📝 Walkthrough

Walkthrough

Documentation and manifest updates: action uiPath and cross-plugin execution, event trigger lifecycle, device state naming and dynamic-state rules, plugin vs device prop underscore rules, troubleshooting XML serialization errors, and manifest version bumps.

Changes

Plugin Development Documentation Updates

Layer / File(s) Summary
Action Execution Patterns
docs/plugin-dev/concepts/actions.md
Adds uiPath menu-grouping rules (PascalCase, no spaces/punctuation), reserved uiPath="hidden", and cross-plugin executeAction() guidance with exact action id and ConfigUI field-id matching.
Event Firing Patterns
docs/plugin-dev/concepts/events.md
Explains non-existent fireEvent mistakes and prescribes lifecycle-managed triggers via triggerStartProcessing/triggerStopProcessing and indigo.trigger.execute(), with exception-logging guidance.
State ID Naming Rules
docs/plugin-dev/concepts/devices.md
Defines strict custom state ID rules (ASCII-letter start, ASCII alphanumerics, no underscores/non-ASCII) and provides sanitization/validation helpers.
Reserved Names & Dynamic State Rules
docs/plugin-dev/concepts/devices.md
Documents reserved state name collisions with native properties (e.g., batteryLevel), recommends Integer for whole-number percentages, and details dynamic getDeviceStateList() rules (copy parent list, forbid underscore-prefixed device pluginProps, rollback on failure).
deviceUpdated Self-loop Guard
docs/plugin-dev/concepts/devices.md
Requires an early pluginId-based guard in deviceUpdated() to avoid infinite recursion across devices.
Plugin Preferences Serialization
docs/plugin-dev/concepts/plugin-preferences.md
Clarifies underscore prefix allowed for plugin-level self.pluginPrefs but prohibited for device-level dev.pluginProps; shows LowLevelBadParameterError example and corrected usage.
Troubleshooting XML Errors
docs/plugin-dev/troubleshooting/common-issues.md
Adds "LowLevelBadParameterError -- illegal XML tag name character" section with checklist: validate state IDs, validate pluginProps keys, copy returned lists before mutation.
Manifest Bumps
.claude-plugin/plugin.json, .claude-plugin/marketplace.json
Bumps plugin version from 1.9.4 to 1.9.5 in both manifest files.

Possibly Related PRs

Estimated Code Review Effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 I hop through docs with careful cheer,
State names tidy, no underscores near.
Triggers stored and fired with care,
Actions tucked where menus share—
Version bumped, the plugin leaps a year.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: adding documentation about undocumented Indigo state-ID and plugin-host rules across multiple files.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
docs/plugin-dev/concepts/actions.md (1)

337-338: ⚡ Quick win

Use Plugin capitalization consistently for Indigo Plugin references in this new section.

Several added lines use lowercase “plugin/plugins” while describing Indigo Plugin concepts (e.g., action grouping and cross-Plugin calls). Please normalize these to Plugin for guideline consistency.

As per coding guidelines, "Use 'Plugin' (capitalized) when referring to Indigo plugins (the tools this project helps build)".

Also applies to: 360-364, 357-358

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/plugin-dev/concepts/actions.md` around lines 337 - 338, Update
capitalization for Indigo Plugin references in this section: change all
occurrences of "plugin" or "plugins" to "Plugin" when referring to Indigo Plugin
concepts (e.g., in sentences describing uiPath on <Action> and <MenuItem>,
action grouping, and cross-Plugin calls). Search for the tokens uiPath,
<Action>, <MenuItem>, and any mentions of "action grouping" or "cross-Plugin
calls" within the added lines (around the new section) and normalize the wording
to use "Plugin" consistently.
docs/plugin-dev/concepts/devices.md (1)

300-301: ⚡ Quick win

Normalize Indigo terminology to Plugin capitalization in the newly added prose.

The added sections refer to Indigo Plugin concepts using lowercase “plugin” in a few places. Update those instances to Plugin to match repository terminology rules and avoid ambiguity.

As per coding guidelines, "Use 'Plugin' (capitalized) when referring to Indigo plugins (the tools this project helps build)".

Also applies to: 345-346, 422-423

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/plugin-dev/concepts/devices.md` around lines 300 - 301, Update the new
prose instances where "plugin" is used in lowercase to the repository-standard
capitalization "Plugin" (e.g., phrases like "Indigo plugin", "plugin host", or
any reference to the tools this project helps build) so they read "Indigo
Plugin" and "Plugin host" as appropriate; locate these occurrences in the added
sections around the discussion of custom state IDs and change "plugin" ->
"Plugin" consistently (also adjust the other occurrences noted in the same added
content).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@docs/plugin-dev/concepts/actions.md`:
- Around line 337-338: Update capitalization for Indigo Plugin references in
this section: change all occurrences of "plugin" or "plugins" to "Plugin" when
referring to Indigo Plugin concepts (e.g., in sentences describing uiPath on
<Action> and <MenuItem>, action grouping, and cross-Plugin calls). Search for
the tokens uiPath, <Action>, <MenuItem>, and any mentions of "action grouping"
or "cross-Plugin calls" within the added lines (around the new section) and
normalize the wording to use "Plugin" consistently.

In `@docs/plugin-dev/concepts/devices.md`:
- Around line 300-301: Update the new prose instances where "plugin" is used in
lowercase to the repository-standard capitalization "Plugin" (e.g., phrases like
"Indigo plugin", "plugin host", or any reference to the tools this project helps
build) so they read "Indigo Plugin" and "Plugin host" as appropriate; locate
these occurrences in the added sections around the discussion of custom state
IDs and change "plugin" -> "Plugin" consistently (also adjust the other
occurrences noted in the same added content).

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 3145b9b3-f868-4a26-9d60-c1c3173f7b6b

📥 Commits

Reviewing files that changed from the base of the PR and between 18a083b and d650b8b.

📒 Files selected for processing (5)
  • docs/plugin-dev/concepts/actions.md
  • docs/plugin-dev/concepts/devices.md
  • docs/plugin-dev/concepts/events.md
  • docs/plugin-dev/concepts/plugin-preferences.md
  • docs/plugin-dev/troubleshooting/common-issues.md

…ferences

Per repo style guideline: 'Plugin' (capitalized) when referring to Indigo
plugins (the tools this project helps build).  Normalised in the new
sections of actions.md, devices.md, and plugin-preferences.md.
@Highsteads
Copy link
Copy Markdown
Author

Thanks for the review @coderabbitai! Capitalisation normalised across the new sections (actions.md, devices.md, plugin-preferences.md) — pushed in 9d03c7a.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 10, 2026

Tip

For best results, initiate chat on the files or code changes.

@Highsteads: Thanks for the update! Great to hear the capitalisation has been normalised across actions.md, devices.md, and plugin-preferences.md in commit 9d03c7a. The documentation is looking much more consistent now. 🎉


If you found this review helpful, would you consider giving us a shout-out on X?

Thank you for using CodeRabbit!

@simons-plugins
Copy link
Copy Markdown
Owner

Cracking PR — really good catch on each of these. Most of it is exactly the kind of "the SDK doesn't tell you" knowledge worth landing.

Going to suggest pulling the Pushover + Email+ sections though, for one reason: this repo intentionally stays at the SDK/IOM concept level, and once we start documenting third-party plugins' action IDs and prop names we're on the hook for keeping them in sync as those plugins evolve. The next person reading them won't know whether the table was verified yesterday or 18 months ago.

For your own use, those notes are gold in ~/.claude/projects/.../memory/ — you get them as persistent context every time you build a plugin that needs to call out, without anyone having to maintain a public reference for them. If you're up for it, the same pattern works for any "how to call X plugin" lore that builds up over time.

The cross-plugin pattern you've documented (the general shape of indigo.server.getPlugin(...).executeAction(...) and watch out for prop names) could land in concepts/actions.md as a generic warning without naming specific plugins — happy to merge that flavour if you want to rework.

Everything else is great and I'd take it as-is:

  • ✅ State ID rules in concepts/devices.md — the camelCase ASCII validator and _sanitise_state_key helper are immediately useful
  • getDeviceStateList LIVE-reference gotcha + the list(...) defensive copy
  • deviceUpdated self-loop guard checking pluginId not id
  • batteryLevel reservation note (verified against iom/devices.md:35)
  • _ prefix distinction between pluginPrefs and pluginProps — the warning callout reads well
  • uiPath PascalCase rule — fills a real gap; actions.md didn't document uiPath at all before
  • fireEvent/triggerEvent-don't-exist warning in events.md
  • ✅ Cross-link from troubleshooting/common-issues.md works (anchor checks out)

One mechanical thing: branch needs a rebase + bump to 1.9.5 — just merged some other docs work into main as 1.9.4, so version-check is failing. Trivial.

Thanks for putting this together — saves the next plugin author a real amount of time.

- Replace the Pushover/Email+ specific examples with a generic
  "calling another plugin's actions" warning. Avoids committing this
  repo to tracking third-party plugins' action IDs and prop names as
  they evolve. The general shape (read the target's Actions.xml, prefer
  direct server APIs when one exists) is what's actually transferable.
- Bump plugin.json and marketplace.json to 1.9.5 so version-check passes
  now that 1.9.4 has landed on main.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@Highsteads
Copy link
Copy Markdown
Author

Thanks — totally fair call on the third-party plugin specifics. Pulled the Pushover and Email+ blocks and replaced them with a generic "calling another plugin's actions" note: read the target's Actions.xml for the action ID and prop names rather than guessing, and prefer a direct server API (like sendEmailTo) when one exists. Specific recipes are now living in my local memory where they belong.

Also rebased onto main and bumped to 1.9.5 so version-check passes.

@simons-plugins simons-plugins merged commit 05c6678 into simons-plugins:main May 10, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants