Skip to content

feat(web): add Plugin Composer -- visual drag-and-drop plugin builder - #413

Open
ChuckBuilds wants to merge 3 commits into
mainfrom
feat/plugin-composer
Open

feat(web): add Plugin Composer -- visual drag-and-drop plugin builder#413
ChuckBuilds wants to merge 3 commits into
mainfrom
feat/plugin-composer

Conversation

@ChuckBuilds

Copy link
Copy Markdown
Owner

Summary

Split out of PR #412 (chore/dead-code-removal), which had accidentally bundled this feature in alongside unrelated dead-code deletions.

A web UI (/composer/) for building a working LEDMatrix plugin without writing Python:

  • Drop elements onto a canvas matching the real panel's pixel grid: text, time, date, countdown, scrolling marquee text, bar/waveform graphics, groups/layers, and custom config variables.
  • Configure each element's properties with live canvas preview.
  • Generate a real plugin (manager.py + manifest.json + config_schema.json) from a code-gen template, downloadable as a ZIP or installed directly into plugins_dir.

⚠️ Not yet wired up: composer_bp is not registered in web_interface/app.py, so this blueprint is currently inert (unreachable). That registration wasn't part of the original commit either — needed before this is usable, tracking as a follow-up.

Also fixes 2 Codacy findings found while reviewing this code:

  • Dropped a pointless f-string prefix (no placeholders).
  • Replaced two bare except: pass/continue blocks with a logged warning before the same fallthrough behavior.

Skipped as false positives (verified against actual usage):

  • The Jinja2 Environment(autoescape=False) findings — this env renders manager.py.j2, a Python source-code generator, never HTML; autoescaping would corrupt generated code.
  • "Flask route directly returning a formatted string" on _as_rgb_filter — that's a Jinja filter function, not a Flask route.

Test plan

  • python3 -m py_compile web_interface/blueprints/composer.py passes
  • Full test suite green (same pre-existing failures as main, no new regressions)
  • Register composer_bp in app.py before merging or exposing this route

🤖 Generated with Claude Code

https://claude.ai/code/session_01KEZK1P1Q1fu5pcuVrkrCFZ

ChuckBuilds and others added 2 commits July 14, 2026 16:27
Web UI (/composer/) for building a working LEDMatrix plugin without
writing Python: drop elements (text, time, date, countdown, scrolling
text, bar/waveform, groups, custom config variables) onto a canvas
matching the real panel's pixel grid, configure them with live preview,
then generate a real plugin (manager.py + manifest.json + config_schema.json)
from manager.py.j2 -- downloadable as a ZIP or installed directly.

NOTE: composer_bp is not yet registered in web_interface/app.py, so this
blueprint is currently inert. Split out of the original chore/dead-code-
removal commit, which had accidentally bundled this in alongside unrelated
dead-code deletions; app.py registration was not part of that commit
either and still needs to be added before this is reachable.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01KEZK1P1Q1fu5pcuVrkrCFZ
- Dropped a pointless f-string prefix (no placeholders) on the default
  plugin description.
- Replaced two bare except:pass/continue blocks (manifest.json listing,
  config_schema.json parsing) with a logged warning before falling
  through to the same skip-this-entry behavior -- same control flow,
  now visible in logs instead of silent.

Skipped as false positives (verified against actual usage, not fixed):
- Jinja2 Environment(autoescape=False) -- this env renders manager.py.j2,
  a Python source-code generator, never HTML; autoescaping would corrupt
  generated code. Flagged by a generic XSS rule that assumes all Jinja2
  environments render HTML.
- "Flask route directly returning a formatted string" on _as_rgb_filter
  -- that's a Jinja *filter* function, not a Flask route.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01KEZK1P1Q1fu5pcuVrkrCFZ
@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@ChuckBuilds, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 59 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 0b81f46b-8e63-4981-87fa-cb25541905a4

📥 Commits

Reviewing files that changed from the base of the PR and between 14a59c8 and 47e3021.

📒 Files selected for processing (5)
  • web_interface/blueprints/composer.py
  • web_interface/static/v3/js/composer/composer-app.js
  • web_interface/static/v3/js/composer/composer-canvas.js
  • web_interface/templates/v3/composer.html
  • web_interface/templates/v3/composer/manager.py.j2
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/plugin-composer

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.

@github-advanced-security github-advanced-security AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

CodeQL found more than 20 potential problems in the proposed changes. Check the Files changed tab for more details.

@codacy-production

codacy-production Bot commented Jul 14, 2026

Copy link
Copy Markdown

Not up to standards ⛔

🔴 Issues 2 critical · 16 high · 1 medium

Alerts:
⚠ 19 issues (≤ 0 issues of at least minor severity)

Results:
19 new issues

Category Results
ErrorProne 6 high
Security 10 high
2 critical
1 medium

View in Codacy

🟢 Metrics 596 complexity · 27 duplication

Metric Results
Complexity 596
Duplication 27

View in Codacy

NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.

…stall

CodeQL flagged 16 high-severity "path depends on user-provided value"
alerts. Investigated each:

- install_locally() (/api/install) built a filesystem path from
  metadata.id without validating it at that point -- it was only
  implicitly safe because _generate_plugin_files() validates the same
  field (re-extracted independently) earlier in the same request. That's
  a real gap: reorder or change that earlier call and it's an exploitable
  path traversal / arbitrary file write. Fixed by validating plugin_id
  directly against _PLUGIN_ID_RE at the point the path is built, matching
  the pattern already used correctly in validate_id() and load_plugin().
- The other 10 flagged locations (serve_font's allowlist check,
  validate_id, load_plugin and its downstream reads) were already
  guarded by an explicit check earlier in the same function -- false
  positives from CodeQL not modeling those as sanitizers.

Also fixed 2 of the 5 "stack trace exposed" warnings that were genuine:
install_locally() and load_plugin() returned raw OSError/Exception text
to the client in a 500 response; now logged server-side with a generic
client-facing message. The other 3 (generate_zip/install_locally/
preview_code returning str(ValueError) from _generate_plugin_files) are
deliberate, human-authored validation messages, not exception internals
-- left as-is.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01KEZK1P1Q1fu5pcuVrkrCFZ
manifest = {}
if manifest_path.exists():
try:
manifest = json.loads(manifest_path.read_text())
logger.warning("Failed to parse config_schema.json for %s: %s", plugin_id, e)

manifest = {}
if manifest_path.exists():

if schema_path.exists():
try:
schema = json.loads(schema_path.read_text())
manifest_path = plugin_dir / 'manifest.json'
config_vars = []

if schema_path.exists():
state_path = plugin_dir / '_composer_state.json'
if state_path.exists():
try:
state = json.loads(state_path.read_text())
target = Path(composer_bp.plugins_dir) / plugin_id
force = bool(data.get('_force', False))

if target.exists() and not force:

def _save_composer_state(target_dir: Path, payload: dict) -> None:
"""Persist the raw composer payload alongside the generated plugin files."""
(target_dir / '_composer_state.json').write_text(
try:
files = _generate_plugin_files(data)
except ValueError as exc:
return jsonify({'status': 'error', 'message': str(exc)}), 422
try:
files = _generate_plugin_files(data)
except ValueError as exc:
return jsonify({'status': 'error', 'message': str(exc)}), 422
try:
files = _generate_plugin_files(data)
except ValueError as exc:
return jsonify({'status': 'error', 'message': str(exc)}), 422
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