[!NOTE] The
manage-componentskill is available in this project. You can ask the AI directly to create a new component using that skill workflow.
This guide is a practical, minimal path to build a working component in this project, aligned with the manage-component workflow.
Use a cmp_ prefix so the loader picks it up.
Use cmp_NNNN_name naming, where NNNN controls load order (commonly in the 5000-7000 range unless you have a reason to place it earlier/later).
cp -r src/component/cmp_7000_hellocomp src/component/cmp_8000_dashboard
Then update identifiers:
manifest.json: set a new uuid, name, and route.schema.json: remove example-specific data you do not need.custom.json for ad-hoc overrides, or store centralized overrides in config/config.db table custom using:
comp_uuid = component UUIDvalue_json = JSON payload with custom.json shape (manifest and/or schema)enabled = 1 to apply overrideuuid should follow name_random format (example: dashboard_8x90s) and be unique.
Minimum recommended structure:
src/component/cmp_8000_dashboard/
├── manifest.json
├── schema.json
├── __init__.py
├── route/
│ ├── __init__.py
│ └── routes.py
└── neutral/
├── component-init.ntpl
└── route/
└── root/
├── data.json
└── content-snippets.ntpl
__init__.py is optional if you do not need initialization logic.
Minimal manifest.json example:
{
"uuid": "dashboard_8x90s",
"name": "Dashboard",
"description": "Dashboard component",
"version": "1.0.0",
"route": "/dashboard"
}
By default, define menu entries in schema.json (data.current.drawer and data.current.menu) so the component is reachable from navigation.
In route/__init__.py, create the blueprint via create_blueprint(...):
from app.components import create_blueprint
def init_blueprint(component, component_schema, _schema):
bp = create_blueprint(component, component_schema) # pylint: disable=unused-variable
from . import routes # pylint: disable=import-error,C0415,W0611
In route/routes.py, define a route that uses RequestHandler:
from flask import Response, g
from core.request_handler import RequestHandler
from . import bp
@bp.route("/", defaults={"route": ""}, methods=["GET"])
@bp.route("/<path:route>", methods=["GET"])
def catch_all(route) -> Response:
handler = RequestHandler(g.pr, route, bp.neutral_route)
return handler.render_route()
Add route metadata in neutral/route/root/data.json:
{
"data": {
"current": {
"route": {
"title": "Dashboard",
"description": "Dashboard page",
"h1": "Dashboard"
}
}
}
}
Then in neutral/route/root/content-snippets.ntpl:
{:data; #/data.json :}
{:snip; current:template:body-main-content >>
<h3>{:trans; {:;local::current->route->h1:} :}</h3>
<p>It works.</p>
:}
{:^;:}
Without this snippet, your route may render the layout without meaningful page content.
Run targeted tests while iterating:
source .venv/bin/activate
pytest -q src/component/cmp_8000_dashboard/tests
Run full suite before merging:
source .venv/bin/activate
pytest -q
Run pylint for new Python files before merging.
source .venv/bin/activate
pylint src/component/cmp_8000_dashboard
cmp_7000_hellocomp is an illustrative example component. In production, disable or remove it unless explicitly needed.