feat(plugins): waveflow:ui/v1 world host surface + redacted library read (#443 Part 1) - #474
Conversation
…ry read (#443) Part 1 of #443, backend-first (frontend renderer + dynamic sidebar land in a follow-up PR). Clean rebuild of the closed WIP #312 — drives sidebar off manifest() instead of hardcoding one plugin, reuses source host types via bindgen with: instead of duplicating them. WIT + SDK: - Promote the ui/v1 world skeleton to the manifest / render / on-event triad (guest returns a JSON view descriptor the host draws with native React — no code injection). - Align the ui host.wit with source (adds config) + a new redacted library interface: list-artists -> [{id, name, track-count}]. - Add the library.read_artists SDK permission constant. Core runtime: - ui bindgen reuses source's http/log/storage/config host types via with: (one impl set); only library is fresh. - library::Host on HostCtx: permission-gated, doubly clamped (MAX_LIBRARY_ARTISTS), snapshot-injected so the guest never touches the DB; local-only so it works offline. Denied plugins get Err even with the snapshot present. - manifest.rs gates library_read_artists; runtime ui_manifest/ui_render/ui_event. App commands: list_ui_plugins / plugin_ui_render / plugin_ui_event + an async-side redacted artist-snapshot loader; registered in lib.rs. Test: a minimal, never-bundled ui-fixture component (plugins/ui-fixture/) + tests/plugin_ui.rs proving manifest/render/on-event round-trip and BOTH sides of the permission gate against the same wasm. Validated: cargo clippy (core + app) clean, core suite green (193 tests). Docs: plugins.md ui-world section + CLAUDE.md clause.
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
📝 WalkthroughWalkthroughLe PR ajoute le monde ChangesContrats UI et permissions
Runtime et services hôte
Commandes et intégration Tauri
Fixture et validation E2E
Estimated code review effort: 4 (Complex) | ~60 minutes Possibly related issues
Possibly related PRs
Sequence Diagram(s)sequenceDiagram
participant Frontend
participant TauriPlugins
participant PluginRuntime
participant HostLibrary
Frontend->>TauriPlugins: plugin_ui_render(plugin_id, path)
TauriPlugins->>HostLibrary: charger le snapshot autorisé
TauriPlugins->>PluginRuntime: ui_render(plugin_id, path, snapshot)
PluginRuntime->>HostLibrary: library.list-artists(limit)
HostLibrary-->>PluginRuntime: artistes limités
PluginRuntime-->>TauriPlugins: descripteur JSON validé
TauriPlugins-->>Frontend: vue UI
Frontend->>TauriPlugins: plugin_ui_event(event, payload)
TauriPlugins->>PluginRuntime: ui_event(event, payload, snapshot)
PluginRuntime-->>TauriPlugins: nouveau descripteur JSON
TauriPlugins-->>Frontend: vue UI mise à jour
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 4
🤖 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.
Inline comments:
In `@src-tauri/crates/app/src/commands/plugins.rs`:
- Around line 1026-1032: Update load_library_artist_snapshot to check the plugin
manifest’s library.read_artists permission before acquiring the profile pool or
running the database query, returning Vec::new() immediately when permission is
absent. Preserve the existing capped query behavior for authorized plugins so
both shared callers inherit the optimization.
- Around line 1113-1119: Validate the strings returned by both plugin_ui_render
and plugin_ui_event as JSON before returning them to the UI, using serde_json
parsing. Convert parsing failures into an AppError that identifies the plugin,
while preserving the existing spawn_blocking and successful descriptor flow.
In `@src-tauri/plugins/ui-fixture/Cargo.toml`:
- Around line 28-30: Align the wit-bindgen runtime dependency with the generated
bindings: either regenerate the bindings using wit-bindgen 0.44, or change the
wit-bindgen-rt dependency in Cargo.toml to version 0.41. Ensure both components
use the same minor version.
In `@src-tauri/plugins/ui-fixture/src/lib.rs`:
- Around line 64-79: Update the render function’s call to library::list_artists
so its limit is derived from the received path or otherwise exceeds the host’s
MAX_LIBRARY_ARTISTS, ensuring the fixture exercises host-side clamping while
preserving the existing success and error handling.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: e637769c-48dd-4655-a153-08243fa4582b
⛔ Files ignored due to path filters (2)
src-tauri/crates/core/tests/fixtures/ui-fixture/plugin.wasmis excluded by!**/*.wasmsrc-tauri/plugins/ui-fixture/Cargo.lockis excluded by!**/*.lock
📒 Files selected for processing (19)
CLAUDE.mddocs/features/plugins.mdsrc-tauri/crates/app/src/commands/plugins.rssrc-tauri/crates/app/src/lib.rssrc-tauri/crates/core/src/plugin/bindings.rssrc-tauri/crates/core/src/plugin/host_impl.rssrc-tauri/crates/core/src/plugin/manifest.rssrc-tauri/crates/core/src/plugin/runtime.rssrc-tauri/crates/core/tests/fixtures/ui-fixture/manifest.tomlsrc-tauri/crates/core/tests/plugin_ui.rssrc-tauri/crates/plugin-sdk/src/lib.rssrc-tauri/crates/plugin-sdk/wit/ui/deps/host/host.witsrc-tauri/crates/plugin-sdk/wit/ui/plugin.witsrc-tauri/plugins/ui-fixture/.gitignoresrc-tauri/plugins/ui-fixture/Cargo.tomlsrc-tauri/plugins/ui-fixture/src/bindings.rssrc-tauri/plugins/ui-fixture/src/lib.rssrc-tauri/plugins/ui-fixture/wit/deps/waveflow-host/host.witsrc-tauri/plugins/ui-fixture/wit/world.wit
…r JSON (#443) CodeRabbit review on #474: - load_library_artist_snapshot short-circuits to an empty list (skipping the profile pool + query) when the plugin didn't declare library.read_artists — the host gate denies it anyway, so there's no reason to materialize the redacted data. Authorized plugins keep the capped query. - plugin_ui_render / plugin_ui_event validate the guest's output is well-formed JSON before returning it, surfacing a malformed descriptor as a plugin-tagged error instead of a raw frontend JSON.parse throw. - ui-fixture now requests u32::MAX artists so the new ui_fixture_render_clamps_snapshot_to_host_max test exercises the host-side MAX_LIBRARY_ARTISTS clamp against an oversized snapshot. Skipped: wit-bindgen-rt 0.44 vs bindings generated by 0.41 — mirrors the shipped web-radio plugin exactly (cargo-component bundles its own 0.41 generator; the 0.44 runtime crate is compatible, build + tests green).
|
@coderabbitai addressed in 12bba19:
Validation: |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
git show --stat --oneline --decorate --no-renames 12bba19a
printf '\n--- changed files ---\n'
git diff-tree --no-commit-id --name-only -r 12bba19a
printf '\n--- command module outline ---\n'
ast-grep outline src-tauri/crates/app/src/commands/plugins.rs --items all --type function --match 'load_library_artist_snapshot|plugin_grants_library_read|validate_ui_descriptor|plugin_ui_render|plugin_ui_event'
printf '\n--- relevant command implementations ---\n'
rg -n -C 20 'fn (plugin_grants_library_read|load_library_artist_snapshot|validate_ui_descriptor|plugin_ui_render|plugin_ui_event)\b' src-tauri/crates/app/src/commands/plugins.rs
printf '\n--- UI test outline and relevant assertions ---\n'
ast-grep outline src-tauri/crates/core/tests/plugin_ui.rs --items all --type function --match 'ui_fixture_render_clamps_snapshot_to_host_max|ui_fixture'
rg -n -C 16 'ui_fixture_render_clamps_snapshot_to_host_max|MAX_LIBRARY_ARTISTS|u32::MAX|list_artists' src-tauri/crates/core/tests/plugin_ui.rs src-tauri/plugins/ui-fixture/src/lib.rs
printf '\n--- inspect commit patch for affected files ---\n'
git show --format=fuller --find-renames --find-copies 12bba19a -- \
src-tauri/crates/app/src/commands/plugins.rs \
src-tauri/crates/core/tests/plugin_ui.rs \
src-tauri/plugins/ui-fixture/src/lib.rsLength of output: 28095
Les remarques sont traitées. 🐇 [resolve] You are interacting with an AI system. |
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src-tauri/crates/app/src/commands/plugins.rs (1)
1044-1089: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick winÉvitez le tronquage de
track_count.
COUNT(DISTINCT t.id)est lu comme uni64. La conversionas u32tronque une valeur supérieure àu32::MAX. Le plugin peut alors recevoir un nombre de pistes incorrect.Bornez la valeur haute avant la conversion.
Correction proposée
- track_count: r.track_count.max(0) as u32, + track_count: r.track_count.clamp(0, i64::from(u32::MAX)) as u32,🤖 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 `@src-tauri/crates/app/src/commands/plugins.rs` around lines 1044 - 1089, In load_library_artist_snapshot, clamp each row’s track_count to u32::MAX before converting it to u32, instead of using a direct as u32 cast. Preserve the existing non-negative handling so negative database values still produce zero.
🤖 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.
Outside diff comments:
In `@src-tauri/crates/app/src/commands/plugins.rs`:
- Around line 1044-1089: In load_library_artist_snapshot, clamp each row’s
track_count to u32::MAX before converting it to u32, instead of using a direct
as u32 cast. Preserve the existing non-negative handling so negative database
values still produce zero.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 2506c546-203d-451a-82cc-fa1e8e1cec0a
⛔ Files ignored due to path filters (1)
src-tauri/crates/core/tests/fixtures/ui-fixture/plugin.wasmis excluded by!**/*.wasm
📒 Files selected for processing (3)
src-tauri/crates/app/src/commands/plugins.rssrc-tauri/crates/core/tests/plugin_ui.rssrc-tauri/plugins/ui-fixture/src/lib.rs
CodeRabbit review on #474: a bare `as u32` on the i64 `track_count` would wrap a value above u32::MAX. Unreachable in practice (it's a track COUNT) but make the cast lossless with `.clamp(0, u32::MAX as i64)` — the `.max(0)` floor on negatives is preserved by the clamp's lower bound.
|
@coderabbitai addressed in 241e46d: |
|
Tip For best results, initiate chat on the files or code changes.
You are interacting with an AI system. |
…art 2) Frontend half of the waveflow:ui/v1 surface (backend = #474). Consumes the list_ui_plugins / plugin_ui_render / plugin_ui_event commands to draw a ui plugin's JSON view descriptor with native React — the guest ships no UI code. - PluginUIView: generic descriptor renderer (header + sections + item cards + badges + action buttons). open-url actions open in the OS browser via openUrl; event actions round-trip through the plugin and swap in the next descriptor. Respects section titles; bakes in no per-plugin assumptions. Non-destructive error banner keeps the last good view. - Dynamic sidebar: useUiPlugins -> list_ui_plugins renders one nav entry per enabled ui plugin, label + icon + landing path all from the plugin's manifest() (no hardcoded per-plugin wiring). Icon resolved from a curated lucide allowlist (pluginIcons), unknown/absent -> generic Puzzle glyph. - Routing: a plugin-ui HistoryEntry carries the plugin id; AppLayout keys the view remount on it so switching plugins reloads cleanly. - plugins.ts: descriptor types (single source) + parsePluginUiDescriptor (validates schemaVersion) + wrappers; libraryReadArtists on PluginPermissionsInfo. - PluginsCard: a "reads your artists" permission chip for library.read_artists. - i18n: pluginView.* + settings.plugins.permissions.libraryReadArtists x17. Validated: bun run typecheck + bun run lint clean.
…art 2) Frontend half of the waveflow:ui/v1 surface (backend = #474). Consumes the list_ui_plugins / plugin_ui_render / plugin_ui_event commands to draw a ui plugin's JSON view descriptor with native React — the guest ships no UI code. - PluginUIView: generic descriptor renderer (header + sections + item cards + badges + action buttons). open-url actions open in the OS browser via openUrl; event actions round-trip through the plugin and swap in the next descriptor. Respects section titles; bakes in no per-plugin assumptions. Non-destructive error banner keeps the last good view. - Dynamic sidebar: useUiPlugins -> list_ui_plugins renders one nav entry per enabled ui plugin, label + icon + landing path all from the plugin's manifest() (no hardcoded per-plugin wiring). Icon resolved from a curated lucide allowlist (pluginIcons), unknown/absent -> generic Puzzle glyph. - Routing: a plugin-ui HistoryEntry carries the plugin id; AppLayout keys the view remount on it so switching plugins reloads cleanly. - plugins.ts: descriptor types (single source) + parsePluginUiDescriptor (validates schemaVersion) + wrappers; libraryReadArtists on PluginPermissionsInfo. - PluginsCard: a "reads your artists" permission chip for library.read_artists. - i18n: pluginView.* + settings.plugins.permissions.libraryReadArtists x17. Validated: bun run typecheck + bun run lint clean.
Part 1 of #443 (Release Radar + UI plugin surface), backend-first. A clean rebuild of the closed WIP #312, fixing its two biggest smells: the sidebar is driven off
manifest()(not hardcoded to one plugin), and theuiworld reusessource's host types via bindgenwith:(not a duplicated impl set).The frontend generic renderer + dynamic sidebar land in a follow-up PR — this one is self-contained and fully covered by an integration test.
What a
ui-world plugin isIt renders a custom view without shipping any React. The guest returns a JSON view descriptor (sections / cards / images / action buttons) the host draws with native components — never HTML/CSS/JS/React, so a hostile descriptor can't run code in the app's origin. Exported
extensioninterface:manifest() -> mount-point— sidebar registration (label + optional lucide icon + initial path).render(path) -> result<string, string>— the current view as a descriptor string.on-event(event, payload) -> result<string, string>— a user action round-trips here; the plugin returns the next full descriptor (no diff protocol).open-urlactions are handled host-side and never reach the guest.The redacted
library.read_artistscapabilityA new host import,
library.list-artists(limit) -> [{id, name, track-count}]— names + aggregate counts + an opaque id only, no file paths or per-track rows. Permission-gated (library.read_artists), doubly clamped (MAX_LIBRARY_ARTISTS), and snapshot-injected: the host queries the active profile async-side and hands the guest a ready list, so the guest never touches SQLite (local-only ⇒ works offline). The redaction is host-enforced — a plugin without the permission getsErr("permission denied: library.read_artists")even when the snapshot is present.Changes
ui/v1skeleton to the manifest/render/on-event triad; align theuihost.wit withsource(addsconfig) + the newlibraryinterface; add thelibrary.read_artistsSDK permission.uibindgen (with:reuse);library::HostonHostCtx(gated + clamped + snapshot); manifest gate;runtime::{ui_manifest, ui_render, ui_event}.list_ui_plugins/plugin_ui_render/plugin_ui_event+ an async redacted-snapshot loader; registered inlib.rs.plugins/ui-fixture/component +tests/plugin_ui.rsproving the round-trip and both sides of the permission gate against the same wasm.plugins.mdUI-world section +CLAUDE.mdclause.Validation
cargo clippy -p waveflow-core --features plugins,sqlite --all-targets— cleancargo clippy -p waveflow --lib— cleancargo test -p waveflow-core --features plugins,sqlite— 193 passed (188 unit + 4 new ui + 1 web-radio)Relates to #443. Follow-up: frontend renderer + dynamic sidebar.
Summary by CodeRabbit
Nouvelles fonctionnalités
Documentation
Tests