pj_marketplace/docs/REQUIREMENTS.md
Version: 1.0.0 Last Updated: 2026-05-19 Purpose: Define WHAT the application should do, not HOW
PlotJuggler has grown significantly, evolving from an internal tool to a de facto standard for data visualization in robotics. With this growth comes a problem: how do we allow the community to contribute plugins without requiring a full PlotJuggler recompilation for each update?
| Category | Feature | Description |
|---|---|---|
| Discovery | Extension listing | Display all available extensions in VSCode-style cards |
| Search | Search by name, description, tags, and publisher | |
| Category filtering | Data Loader, Data Streamer, Parser, Toolbox | |
| Extension detail | Panel with complete information, changelog, and dependencies | |
| Startup snapshot | Host app may seed the marketplace with already-loaded plugin ids/versions before first render | |
| Installation | Secure download | ZIP artifact download with SHA256 verification |
| Automatic extraction | Decompression to extensions directory | |
| Platform detection | Automatic selection of correct artifact (Linux/Windows/macOS) | |
| Updates | Update detection | Local vs registry version comparison (semver); local-newer state is surfaced explicitly |
| Individual update | Update a specific extension | |
| Bulk update | "Update All" for multiple extensions | |
| Automatic backup | Backup of previous version before updating | |
| Uninstallation | Clean removal | Directory deletion + installed cache refresh |
| Confirmation | Confirmation dialog before uninstalling | |
| Management | Backup diagnostics | Report retained backup paths when an update install fails |
| Persistent state | Installed state derived from plugin DSOs; each embedded plugin manifest is the source of truth | |
| Registry URL settings | Configure registry URL at runtime via ⚙ settings dialog; rejects URLs with non-http(s)/file scheme; change triggers immediate refresh | |
| Registry URL persistence | Last configured registry URL saved and restored between sessions | |
| Unified diagnostics | All lifecycle events (install / staged-promotion / quarantine / uninstall failures, registry-fetch errors) are surfaced through ExtensionManager::diagnosticReported, the in-memory ring buffer, AND an optional PJ::DiagnosticSink so embedding hosts can fold marketplace events into their own diagnostic stream | |
| UI/UX | Download progress | Progress bar in status bar |
| Notifications | Status messages and available update alerts | |
| Context menu | Quick actions per installed extension |
| Category | Feature | Description |
|---|---|---|
| Build | Cross-platform compilation | Matrix build for Linux, Windows, and macOS |
| Static linking | All dependencies embedded in the artifact | |
| Dependency management | Support for Conan (current) and Pixi (future) | |
| Packaging | ZIP generation | Automatic packaging with plugin binaries and resources |
| Checksums | Automatic SHA256 generation per artifact | |
| Versioning | Version extraction from git tag | |
| Publishing | GitHub Release | Automatic release creation with attached artifacts |
| Registry update | Automatic PR to registry repo with new version | |
| Validation | Unit tests | Test execution on each platform |
| Lint/Format | Code style verification | |
| Schema validation | Registry JSON validation in PRs |
| Category | Feature | Description |
|---|---|---|
| Catalog | Complete listing | JSON with all available extensions |
| Metadata | Name, description, author, license, tags, category | |
| Versioning | Current version and minimum PlotJuggler versions | |
| Cross-platform | URLs and checksums per platform (Linux/Windows/macOS) | |
| Hosting | Static GitHub | JSON file accessible via raw.githubusercontent.com |
| Cache TTL | Support for local cache with expiration time | |
| Multiple registries | Configuration for alternative registries (enterprise) |
| Term | Definition |
|---|---|
| Extension | Marketplace distribution unit. Downloadable ZIP containing one or more plugins. |
| Plugin | C++ module dynamically loaded (.so/.dll/.dylib) implementing an SDK interface. |
| Registry | Static JSON file with the catalog of available extensions. |
| Plugin SDK | Abstract library (no Qt) that plugins use for UI and data access. |
| Artifact | Compiled binary of an extension for a specific platform. |
| Embedded manifest | JSON string exported by each plugin DSO describing the installed plugin. |
These concerns are intentionally not part of pj_marketplace and belong
elsewhere in the host application:
pj_app that filters which discovered DSOs the loader instantiates),
not a marketplace concern. The marketplace's job ends at "installed on
disk"; whether the host chooses to load that DSO at startup is decided by
the host's plugin loader and config.| ID | Requirement | Acceptance Criteria |
|---|---|---|
| F-01 | Fetch and parse registry JSON from configurable URL | Given a valid URL, the system loads and parses extension metadata |
| F-02 | List extensions in sidebar with cards | User sees all available extensions with name, description, version |
| F-03 | Search by name, description, tags | Typing "ros" shows all ROS-related extensions |
| F-04 | Filter by category | User can filter by Data Loader, Streamer, Parser, Toolbox |
| F-05 | Show selected extension detail | Clicking an extension shows full information panel |
| F-06 | Download ZIP with SHA256 verification | Download fails if checksum doesn't match |
| F-07 | Extract ZIP to extensions directory | ZIP contents are extracted to correct location |
| F-08 | Register installed extension | Installed state is derived from disk by scanning extension DSOs and reading each embedded plugin manifest |
| F-09 | Detect updates (local vs registry version) | User sees "Update available" badge when registry is newer, and "Local newer" when the installed version is ahead |
| F-10 | Uninstall extension | User can remove installed extensions |
| ID | Requirement | Acceptance Criteria |
|---|---|---|
| F-24 | Configure registry URL via settings dialog | User can open ⚙ settings, enter a custom URL, and the marketplace immediately fetches from the new URL |
| F-25 | Persist registry URL between sessions | The last configured registry URL is saved and automatically restored on next launch |
| F-11 | Local registry cache with TTL (deferred) | Not implemented today — RegistryManager always fetches fresh. Would be reintroduced only if a clear caching need emerges (see TODO.md). |
| F-12 | Backup previous version on updates | Old version saved before overwriting |
| F-13 | Automatic rollback if plugin fails | Deferred; backups may exist, but automatic restore is not implemented |
| F-14 | Deferred update: apply on restart | Updates are downloaded and staged, then applied only after restart; a fresh install activates immediately |
| F-16 | Cancel download in progress | User can abort a download |
| F-17 | Update All | Single action to update all extensions with available updates |
| F-18 | Confirmation dialogs | User confirms before install/uninstall/update actions |
| ID | Requirement | Acceptance Criteria |
|---|---|---|
| F-19 | Extension icons (download + cache) | Each extension displays its icon |
| F-20 | Changelog per extension | User can see version history |
| F-21 | Metrics (downloads, rating) | Extension cards show popularity metrics |
| F-22 | Notification: "N updates available" | User notified of available updates |
| F-23 | Multiple registry URLs | Support for private/enterprise registries |
| ID | Requirement | Metric |
|---|---|---|
| NF-01 | C++20 | Module builds at C++20 (CMAKE_CXX_STANDARD 20), matching the rest of PJ4 and plotjuggler_sdk |
| NF-02 | Qt 6.x Widgets | 6.11 now; 6.12 LTS target |
| NF-03 | Cross-platform | Works on Linux, Windows, macOS |
| NF-04 | Build system: CMake | Standard CMake project |
| NF-05 | Dependencies: Conan (current), Pixi (future) | Builds with specified tools |
| NF-06 | No external dependencies beyond Qt | Single binary, no runtime deps |
| NF-07 | Standalone → integrable into PlotJuggler | Works as standalone, then embeds |
| NF-08 | Download in background thread | UI remains responsive during downloads |
| NF-09 | Performance: <100ms to load/filter registry | With ~50 extensions |
| NF-10 | Static linking in extensions | Plugins are self-contained |
Actor: PlotJuggler User Preconditions: PlotJuggler is running, internet available Flow:
Postconditions: Extension installed and registered
Actor: PlotJuggler User Preconditions: Extension installed, newer version available Flow:
Postconditions: New version installed, old version backed up
Actor: PlotJuggler User Preconditions: Extension installed Flow:
Postconditions: Extension removed, installed cache refreshed
Actor: System Preconditions: Extension recently updated, backup exists Flow:
Postconditions: User is notified; any backup remains available for manual recovery
Actor: Plugin Developer Preconditions: Developer has GitHub account, uses template Flow:
Postconditions: Extension available to all users
| Category | Value | Description | Example |
|---|---|---|---|
| Data Loader | data_loader | Loads data from files (atomic operation) | CSV, MCAP, ROS bags |
| Data Streamer | data_streamer | Continuous streaming at 50Hz, thread-safe | ROS 2, MQTT, ZMQ |
| Parser | parser | Conversion from byte blob to individual fields | Protobuf, FlatBuffers |
| Toolbox | toolbox | Tools with GUI (FFT, CSV export, quaternion) | FFT analyzer |
| Bundle | bundle | ZIP with multiple plugins from different families | ROS 2 Complete |
| Scenario | Expected Behavior |
|---|---|
| No internet connection | Show cached registry (if available), disable install/update |
| Registry URL unreachable | Show error message, offer retry |
| Download interrupted | Partial file deleted, user can retry |
| Checksum mismatch | Download rejected, user notified |
| Scenario | Expected Behavior |
|---|---|
| Insufficient disk space | Error before extraction, suggest cleanup |
| No write permission to extensions dir | Clear error message, suggest running with permissions |
| Extension directory doesn't exist | Create it automatically |
| Corrupted ZIP file | Extraction fails gracefully, user notified |
| Scenario | Expected Behavior |
|---|---|
| Extension requires newer PlotJuggler | Show warning, prevent install |
| Downgrade requested | Reject with a diagnostic; keep the local install unchanged |
| Same version reinstall | Ask confirmation, then reinstall |
| Scenario | Expected Behavior |
|---|---|
| Update requested | Stage the new version and apply on restart — never hot-swap a plugin the running session has loaded |
| Invalid staged update | Remove staged files and leave the active install untouched |
| PlotJuggler crashes before applying update | Pending update remains for next start |
| Plugin DLL in use on uninstall (Windows) | Mark the directory for restart cleanup; removed on next start |
| Scenario | Expected Behavior |
|---|---|
| Plugin crashes on load | Report load failure; automatic rollback is deferred |
| Plugin incompatible with current SDK | Clear error message, don't load |
| Embedded manifest missing or invalid | Reject install or staged promotion with diagnostics |
| Marketplace opens in a host app | Seed the initial UI from the host's loaded-plugin snapshot, then reconcile with disk refreshes on demand |
plotjuggler_sdk/pj_plugins and
expose only a small embedded manifest plus their vtable. This keeps
cross-platform CI simple and decouples plugin builds from Qt
version churn.The minimum viable product is successful if:
{
"registry_version": "1.0",
"last_updated": "ISO8601 timestamp",
"extensions": [
{
"id": "unique-extension-id",
"name": "Display Name",
"description": "Short description",
"author": "Author Name",
"publisher": "Publisher Name",
"website": "https://...",
"repository": "https://github.com/...",
"license": "SPDX identifier",
"icon_url": "https://... (optional)",
"category": "data_loader|data_streamer|parser|toolbox|bundle",
"tags": ["tag1", "tag2"],
"version": "semver",
"min_plotjuggler_version": "semver",
"plugins": [
{
"name": "PluginClassName",
"type": "plugin_type",
"library": "library_name_without_extension"
}
],
"platforms": {
"linux-x86_64": {
"url": "https://...",
"checksum": "sha256:..."
}
},
"changelog": {
"1.0.0": "Initial release"
}
}
]
}
There is no separate local state file. Installed extensions are discovered at runtime by
scanning extensions_dir, loading candidate plugin DSOs, and reading each DSO's embedded
plugin manifest. The marketplace never writes installed-state or plugin-manifest sidecars.
Staged updates use a transient .pj_pending_install intent file so restart-time
promotion can revalidate the staged DSO against the registry id/version that created it.
Fields read from the embedded plugin manifest:
| Field | Source |
|---|---|
id | Embedded plugin manifest key "id" |
version | Embedded plugin manifest key "version" |
install_date | Last-modified timestamp of the extension root directory |
path | The scanned subdirectory itself |
enabled | Always true by default (no persistence yet) |
{
"id": "extension-id",
"version": "semver",
"min_plotjuggler_version": "semver",
"plugins": [
{
"name": "PluginClassName",
"type": "plugin_type",
"library": "library_name",
"ui_file": "optional_ui_file.ui"
}
]
}
Remaining implementation follow-ups are tracked in TODO.md.
This file should be updated when:
Do NOT add: