[#80] feat: web plugin with extension point architecture #81

Merged
k9ert merged 5 commits from story-80 into main 2026-02-22 14:46:15 +00:00
Collaborator

Closes #80

Changes

  • Web plugin with extension_points: ["web.panels", "web.routes", "web.settings"]
  • Built-in plugin graph panel contributed via implements: {"web.panels": "get_builtin_panels"}
  • Uses registry.get_implementations() for extension point discovery
  • Starlette server with graceful shutdown handling
  • Localhost-only binding (127.0.0.1) by default
  • No base class methods for web contribution — pure extension point pattern

Files Created

cobot/plugins/web/
├── __init__.py
├── plugin.py      # WebPlugin with extension points
├── server.py      # Starlette app with uvicorn
├── README.md      # Documentation
├── templates/
│   ├── base.html
│   ├── admin.html
│   └── plugins.html
├── static/
│   └── .gitkeep
└── tests/
    ├── __init__.py
    └── test_plugin.py  # 24 tests

Config Schema

web:
  enabled: true
  host: "127.0.0.1"
  port: 8080

Dependencies Added

New [project.optional-dependencies.web] in pyproject.toml:

  • starlette>=0.35
  • uvicorn>=0.25
  • jinja2>=3.1

AC Checklist

  • web plugin created with extension_points: ["web.panels", "web.routes", "web.settings"]
  • No base class methods for web contribution
  • At least one panel contributed via implements (plugin graph)
  • registry.get_implementations() used for discovery
  • Plugin graph visible as admin panel
  • Starlette server starts on configured port
  • Graceful shutdown handling
  • Localhost-only binding by default

Test Results

24 passed in 0.03s
Closes #80 ## Changes - **Web plugin** with `extension_points: ["web.panels", "web.routes", "web.settings"]` - **Built-in plugin graph panel** contributed via `implements: {"web.panels": "get_builtin_panels"}` - Uses `registry.get_implementations()` for extension point discovery - **Starlette server** with graceful shutdown handling - **Localhost-only binding** (`127.0.0.1`) by default - No base class methods for web contribution — pure extension point pattern ## Files Created ``` cobot/plugins/web/ ├── __init__.py ├── plugin.py # WebPlugin with extension points ├── server.py # Starlette app with uvicorn ├── README.md # Documentation ├── templates/ │ ├── base.html │ ├── admin.html │ └── plugins.html ├── static/ │ └── .gitkeep └── tests/ ├── __init__.py └── test_plugin.py # 24 tests ``` ## Config Schema ```yaml web: enabled: true host: "127.0.0.1" port: 8080 ``` ## Dependencies Added New `[project.optional-dependencies.web]` in pyproject.toml: - starlette>=0.35 - uvicorn>=0.25 - jinja2>=3.1 ## AC Checklist - [x] `web` plugin created with `extension_points: ["web.panels", "web.routes", "web.settings"]` - [x] No base class methods for web contribution - [x] At least one panel contributed via `implements` (plugin graph) - [x] `registry.get_implementations()` used for discovery - [x] Plugin graph visible as admin panel - [x] Starlette server starts on configured port - [x] Graceful shutdown handling - [x] Localhost-only binding by default ## Test Results ``` 24 passed in 0.03s ```
[#80] feat: web plugin with extension point architecture
All checks were successful
CI / lint (pull_request) Successful in 9s
CI / test (3.11) (pull_request) Successful in 20s
CI / test (3.12) (pull_request) Successful in 22s
E2E Tests / e2e (pull_request) Successful in 10s
CI / test (3.13) (pull_request) Successful in 23s
CI / build (pull_request) Successful in 6s
a0c8bfb9ae
- Web plugin with extension_points: ['web.panels', 'web.routes', 'web.settings']
- Built-in plugin graph panel via implements: {'web.panels': 'get_builtin_panels'}
- Uses registry.get_implementations() for extension point discovery
- Starlette server with graceful shutdown
- Localhost-only binding (127.0.0.1) by default
- No base class methods for web contribution - pure extension point pattern
- 24 tests covering meta, config, panels, and lifecycle
doxios left a comment
Author
Collaborator

PR Review: Web Plugin Extension Point Architecture

Extension Point Architecture

Follows CLI pattern from #78 — Uses extension_points and implements in PluginMeta correctly.

  • web.panels, web.routes, web.settings properly defined
  • registry.get_implementations() used correctly in collect_panels() and collect_routes()
  • Plugin implements its own extension point (dogfooding) with get_builtin_panels()

Code Quality

plugin.py:

  • Clean separation of concerns
  • Factory function pattern
  • Proper lifecycle methods (configure, start, stop)
  • Graceful task cancellation in stop()

server.py:

  • Starlette best practices followed
  • Conditional dependency import with HAS_DEPS guard
  • autoescape=True in Jinja2 — XSS protection
  • Clean uvicorn integration

Templates:

  • Modern CSS with variables
  • Interactive SVG-based plugin graph (nice touch!)
  • Responsive sidebar design

Tests — 24/24 Passed

All tests pass with good coverage of:

  • Meta validation
  • Configuration (enabled, host, port)
  • Panel collection and sorting
  • Graph data generation
  • Lifecycle methods

Lint/Format

  • ruff check → All checks passed!
  • ruff format --check → All files formatted

Security

  • Default host: 127.0.0.1 (localhost-only)
  • Autoescape: Enabled in templates
  • No auth needed for localhost admin interface

📝 Suggestions (Non-blocking)

  1. web.routes not wired up: collect_routes() is defined but never called in server.py. Routes from other plugins won't actually be mounted. Consider:

    • Adding dynamic route mounting in v1, or
    • Documenting it as "planned" like web.settings
  2. Missing test for collect_routes(): Would be good to add coverage.


Verdict: APPROVE

Solid implementation following established patterns. The extension point architecture is clean and matches the CLI plugin design from #78. Minor gaps in route mounting can be addressed in follow-up work.

Nice work on the plugin graph visualization! 🔌📊

## PR Review: Web Plugin Extension Point Architecture ### ✅ Extension Point Architecture **Follows CLI pattern from #78** — Uses `extension_points` and `implements` in PluginMeta correctly. - `web.panels`, `web.routes`, `web.settings` properly defined - `registry.get_implementations()` used correctly in `collect_panels()` and `collect_routes()` - Plugin implements its own extension point (dogfooding) with `get_builtin_panels()` ### ✅ Code Quality **plugin.py:** - Clean separation of concerns - Factory function pattern ✅ - Proper lifecycle methods (configure, start, stop) - Graceful task cancellation in `stop()` **server.py:** - Starlette best practices followed - Conditional dependency import with `HAS_DEPS` guard - `autoescape=True` in Jinja2 — XSS protection ✅ - Clean uvicorn integration **Templates:** - Modern CSS with variables - Interactive SVG-based plugin graph (nice touch!) - Responsive sidebar design ### ✅ Tests — 24/24 Passed All tests pass with good coverage of: - Meta validation - Configuration (enabled, host, port) - Panel collection and sorting - Graph data generation - Lifecycle methods ### ✅ Lint/Format - `ruff check` → All checks passed! - `ruff format --check` → All files formatted ### ✅ Security - **Default host:** `127.0.0.1` (localhost-only) ✅ - **Autoescape:** Enabled in templates ✅ - No auth needed for localhost admin interface ### 📝 Suggestions (Non-blocking) 1. **`web.routes` not wired up:** `collect_routes()` is defined but never called in `server.py`. Routes from other plugins won't actually be mounted. Consider: - Adding dynamic route mounting in v1, or - Documenting it as "planned" like `web.settings` 2. **Missing test for `collect_routes()`:** Would be good to add coverage. --- ### Verdict: **APPROVE** ✅ Solid implementation following established patterns. The extension point architecture is clean and matches the CLI plugin design from #78. Minor gaps in route mounting can be addressed in follow-up work. Nice work on the plugin graph visualization! 🔌📊
doxios left a comment
Author
Collaborator

PR Review: Web Plugin Extension Point Architecture

Extension Point Architecture

Follows CLI pattern from #78 — Uses extension_points and implements in PluginMeta correctly.

  • web.panels, web.routes, web.settings properly defined
  • registry.get_implementations() used correctly in collect_panels() and collect_routes()
  • Plugin implements its own extension point (dogfooding) with get_builtin_panels()

Code Quality

plugin.py:

  • Clean separation of concerns
  • Factory function pattern
  • Proper lifecycle methods (configure, start, stop)
  • Graceful task cancellation in stop()

server.py:

  • Starlette best practices followed
  • Conditional dependency import with HAS_DEPS guard
  • autoescape=True in Jinja2 — XSS protection
  • Clean uvicorn integration

Templates:

  • Modern CSS with variables
  • Interactive SVG-based plugin graph
  • Responsive sidebar design

Tests — 24/24 Passed

All tests pass with good coverage of meta validation, configuration, panel collection and sorting, graph data generation, and lifecycle methods.

Lint/Format — All checks passed

Security

  • Default host: 127.0.0.1 (localhost-only)
  • Autoescape enabled in templates

Suggestions (Non-blocking)

  1. web.routes not wired up: collect_routes() is defined but never called in server.py. Consider adding dynamic route mounting or documenting it as planned.

  2. Missing test for collect_routes(): Would be good to add coverage.


Verdict: APPROVE (self-review - awaiting external approval)

Solid implementation following established patterns. Minor gaps in route mounting can be addressed in follow-up work.

## PR Review: Web Plugin Extension Point Architecture ### ✅ Extension Point Architecture **Follows CLI pattern from #78** — Uses extension_points and implements in PluginMeta correctly. - web.panels, web.routes, web.settings properly defined - registry.get_implementations() used correctly in collect_panels() and collect_routes() - Plugin implements its own extension point (dogfooding) with get_builtin_panels() ### ✅ Code Quality **plugin.py:** - Clean separation of concerns - Factory function pattern - Proper lifecycle methods (configure, start, stop) - Graceful task cancellation in stop() **server.py:** - Starlette best practices followed - Conditional dependency import with HAS_DEPS guard - autoescape=True in Jinja2 — XSS protection - Clean uvicorn integration **Templates:** - Modern CSS with variables - Interactive SVG-based plugin graph - Responsive sidebar design ### ✅ Tests — 24/24 Passed All tests pass with good coverage of meta validation, configuration, panel collection and sorting, graph data generation, and lifecycle methods. ### ✅ Lint/Format — All checks passed ### ✅ Security - Default host: 127.0.0.1 (localhost-only) - Autoescape enabled in templates ### Suggestions (Non-blocking) 1. **web.routes not wired up:** collect_routes() is defined but never called in server.py. Consider adding dynamic route mounting or documenting it as planned. 2. **Missing test for collect_routes():** Would be good to add coverage. --- ### Verdict: APPROVE (self-review - awaiting external approval) Solid implementation following established patterns. Minor gaps in route mounting can be addressed in follow-up work.
fix: actually mount routes from web.routes extension point
All checks were successful
CI / lint (pull_request) Successful in 9s
CI / test (3.11) (pull_request) Successful in 21s
CI / test (3.12) (pull_request) Successful in 23s
E2E Tests / e2e (pull_request) Successful in 11s
CI / test (3.13) (pull_request) Successful in 23s
CI / build (pull_request) Successful in 7s
590b6e20c4
- Call collect_routes() in server._create_app()
- Add contributed routes to Starlette app
- Add tests for collect_routes()
docs: comprehensive web plugin README with examples
All checks were successful
CI / lint (pull_request) Successful in 9s
CI / test (3.11) (pull_request) Successful in 21s
CI / test (3.12) (pull_request) Successful in 23s
E2E Tests / e2e (pull_request) Successful in 10s
CI / test (3.13) (pull_request) Successful in 23s
CI / build (pull_request) Successful in 7s
d82172f337
- Clarify that panels are nav entries, routes provide content
- Add complete example showing panel + route combo
- Add JSON API endpoint example (no panel)
- Add dynamic content example accessing other plugins
- Improve structure with tables and clear sections
refactor: extract CSS into separate files
All checks were successful
CI / lint (pull_request) Successful in 9s
CI / test (3.11) (pull_request) Successful in 22s
E2E Tests / e2e (pull_request) Successful in 11s
CI / test (3.12) (pull_request) Successful in 25s
CI / test (3.13) (pull_request) Successful in 24s
CI / build (pull_request) Successful in 7s
140f0d6013
- Move core styles from base.html to static/css/main.css
- Move graph styles from plugins.html to static/css/graph.css
- Templates now link to external stylesheets
- Cleaner separation of concerns
feat: auto-prefix routes with plugin id
All checks were successful
CI / lint (pull_request) Successful in 9s
CI / test (3.11) (pull_request) Successful in 21s
CI / test (3.12) (pull_request) Successful in 23s
E2E Tests / e2e (pull_request) Successful in 11s
CI / test (3.13) (pull_request) Successful in 24s
CI / build (pull_request) Successful in 7s
f9a3661e4e
Routes are now auto-prefixed with the plugin id to avoid collisions:
- Relative paths (no leading /): 'balance' → '/<plugin_id>/balance'
- Absolute paths (leading /): '/health' → '/health' (no change)

Panel routes are also prefixed the same way for consistency.

Updated README with route prefixing documentation and examples.
k9ert merged commit 4efd0628df into main 2026-02-22 14:46:15 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
ultanio/cobot!81
No description provided.