Skip to content

Plugin reviews test plan (desktop) ​

Portable, repo-resident test plan for the read-only public plugin-reviews surface: shared types, host-core RPC/HTTP client, Electron IPC bridge, renderer data layer, installed-grid/detail-sheet UI, review drawer, locale coverage, and the Electron fixtures.

References used as source of truth:

  • docs/spec/07-plugins/07-plugin-marketplace.md — marketplace/review behavior.
  • docs/spec/06-delivery/04-e2e-test-plan.md — fixture conventions and commands.
  • Center counterpart: the center repository's docs/reviews-test-plan.md (API rules and the Go/service/store evidence this plan pairs with).
  • Shared types: packages/shared/src/types/marketplace.ts; protocol channels in packages/shared/src/protocol.ts.
  • Automated evidence: apps/desktop/test/market-review-ipc.test.mjs, plugin-reviews-logic.test.mjs, plugin-reviews-surface.test.mjs, plugins-page-style.test.mjs; crates/host-core/src/plugins/reviews.rs unit tests; crates/host-core/src/rpc/plugin_reviews_rpc_tests.rs; fixtures under scripts/e2e/ with their runners in scripts/.

0. Result statuses ​

TagMeaning
verifiedFinal actual result recorded in the Final execution record after a run against this worktree.
baseline:failPre-existing full-suite failure in this environment, not a review regression.
executed; baseline failuresThe command ran end to end; only the known baselines failed — not green.

Test titles below are copied verbatim from the repository files named in each row. If a title no longer matches the source, update the row.

1. Scope and prohibitions ​

  • Task worktree only; no commits, pushes, tags, release, or packaging.
  • Never attach the user's running instance or profile; never use verify:ui:*. The sanctioned pattern is the headless Electron fixtures in §2.5 with isolated userData profiles under .cache/.
  • No broad config or ignore-file changes.
  • Desktop stays read-only: no login, posting, votes, reports, or credentials in the renderer.
  • Tests are the evidence; this plan does not claim any run happened.

2. Commands ​

2.1 Windows invocation workaround (tsc / electron-vite) ​

Canonical scripts are pnpm --filter @pi-desktop/desktop typecheck, pnpm --filter @pi-desktop/desktop build, and the repo pnpm test. When the pnpm script shim does not resolve on Windows, invoke the worktree-local binaries directly with node — same inputs and outputs, no configuration change:

powershell
cd apps/desktop
node node_modules/typescript/bin/tsc -p tsconfig.json --noEmit
node node_modules/electron-vite/bin/electron-vite.js build
cd ../..

Rebuild workspace dists first only if stale (the fixtures resolve them):

powershell
cd packages/shared;     node node_modules/typescript/bin/tsc -p tsconfig.json; cd ../..
cd packages/plugin-sdk; node node_modules/typescript/bin/tsc -p tsconfig.json; cd ../..

2.2 Targeted JS tests (final six-file run) ​

powershell
node --test apps/desktop/test/market-review-ipc.test.mjs apps/desktop/test/plugin-reviews-logic.test.mjs apps/desktop/test/plugin-reviews-surface.test.mjs apps/desktop/test/plugins-page-style.test.mjs apps/desktop/test/extensions-page.test.mjs apps/desktop/test/settings-dialog-overlay.test.mjs

Final independent run: 64/64 pass — IPC 6, logic 16, surface 14, style 8, extensions 16, overlay host suite 4. The review cases map to the first four files plus RVD-UI-14's installed-grid pin; extensions and overlay are the adjacent regression companions the renderer change touches (the detail sheet mounts on the viewport overlay host).

2.3 Targeted Rust tests ​

powershell
cargo test -p host-core --bin pi-desktop-host-core review
cargo fmt --check
cargo clippy -p host-core --all-targets

Known unrelated clippy warning (warn-level): crates/host-core/src/transcripts.rs:893clippy::filter_next.

2.4 Full host-core suite context ​

powershell
cargo test -p host-core --bin pi-desktop-host-core

Known baseline in this environment: 912 passed; 38 failed; 1 ignored, all 38 in Windows shell/worktree areas (background_tasks, data_relocation, rpc::*, session_worktrees, tools::bash_*, tools::powershell_*) — not review regressions. Report them as baseline:fail; do not attempt to fix them from this plan.

2.5 i18n ​

powershell
# Catalogue integrity
cd packages/i18n
node node_modules/vitest/vitest.mjs run
cd ../..

# Review-block and key parity (every locale must print reviewsBlock=1 sevenKeys=7)
$root = 'C:\Code\ReactProjects\Nib\.slim\worktrees\plugin-reviews'
foreach ($l in 'de','en','es','fr','ko','pt-BR','tr','zh-CN','zh-TW') {
  $f = "$root\packages\i18n\src\locales\$l\index.ts"
  $block = (Select-String -Path $f -Pattern '^\s*reviews: \{' -CaseSensitive).Count
  $keys  = (Select-String -Path $f -Pattern 'summaryTitle:|viewAll:|composeHint:|composeCta:|reviewVersion:|filterLabel:|sortLabel:' -CaseSensitive).Count
  "$l : reviewsBlock=$block sevenKeys=$keys"
}

-CaseSensitive matters: without it the key count reads 8 because capabilityFilterLabel (an unrelated key) also matches filterLabel:. Verify reviewsBlock=1 sevenKeys=7 per locale.

2.6 Electron fixtures (real Chromium, headless) ​

powershell
node scripts/e2e-plugin-reviews-drawer.mjs     # five probes: drawer / batch-stale-guard / narrow drawer / stale summary / unavailable summary
node scripts/e2e-installed-grid.mjs            # installed grid + control rails + dialogs
node scripts/e2e-plugin-detail-focus.mjs       # detail-sheet focus interplay

Each runner builds the real components with esbuild, loads them in a hidden Electron window with a private profile, and prints one PASS line per probe. No install leaves the renderer; no user instance is touched.

3. Cases ​

3.1 Shared types and renderer API ​

IDCheckExpectedStatus
RVD-TYP-01PluginReviewSummary matches the center contract: ratingAvg: number | null, five floor buckets, three sentiment countsCompiles in shared + desktop typecheck; no private fields (userId, hiddenReason) in the public typeverified (2026-10-16 final typecheck + build)
RVD-TYP-02PluginReviewPage carries reviews, nextCursor: string | null, summary, host-built reviewUrl; PluginReviewQuery allows cursor?/limit?/sentiment?/sort?api.marketReviewSummaries(ids) / api.marketReviews(query) return the shared types through the preload whitelistverified (2026-10-16 final typecheck + build)

3.2 Electron IPC bridge — market-review-ipc.test.mjs ​

IDTest title (verbatim)Expected assertionStatus
RVD-IPC-01review channels are registered and allowed through the preload gateIPC.invoke.marketReviewSummaries === "pi-desktop/market/reviewSummaries", marketReviews === "pi-desktop/market/reviews", both in IPC_WHITELISTverified (2026-10-16 six-file run)
RVD-IPC-02summary reads forward the ids and return the host payload unchangedHandler calls host market.reviewSummaries with {pluginIds} and returns {summaries} unchangedverified (2026-10-16 six-file run)
RVD-IPC-03a missing or malformed id list becomes an empty request, never a crashMissing payload and pluginIds: "demo.hello" both forward {pluginIds: []}verified (2026-10-16 six-file run)
RVD-IPC-04review reads forward the query and return the page unchangedFull query (incl. cursor) forwarded; page returned unchangedverified (2026-10-16 six-file run)
RVD-IPC-05an unsupported or unreachable center reaches the caller unchangedRejection carries data.errorCode === "PLUGIN_NETWORK" — no remapping or maskingverified (2026-10-16 six-file run)
RVD-IPC-06review reads require a running hostNo host → rejects host unavailableverified (2026-10-16 six-file run)

3.3 Host RPC — plugin_reviews_rpc_tests.rs ​

Preconditions: a fixture HTTP server plays the trusted center on a loopback origin; host-core is driven through the real request handler.

IDTest (verbatim)Expected assertionStatus
RVD-RPC-01review_summaries_rpc_returns_only_the_centers_answersRequested-and-answered ids only; unrequested center answers droppedverified (2026-10-16 hardened Rust run)
RVD-RPC-02review_summaries_rpc_dedupes_and_chunks_installed_idsDuplicates collapse; chunks of ≤100 ids; requests recordedverified (2026-10-16 hardened Rust run)
RVD-RPC-03review_summaries_rpc_is_empty_without_asking_the_centerEmpty ids → {summaries:{}} with no HTTP requestverified (2026-10-16 hardened Rust run)
RVD-RPC-04review_summaries_rpc_rejects_malformed_center_answersBad JSON/shape → PLUGIN_MARKET_INVALIDverified (2026-10-16 hardened Rust run)
RVD-RPC-05review_summaries_rpc_reports_a_legacy_center_as_unavailable404 → PLUGIN_NETWORK (unavailable), never zerosverified (2026-10-16 hardened Rust run)
RVD-RPC-06review_summaries_rpc_never_fabricates_zeros_when_the_center_is_downDown center → error; no zero summaries inventedverified (2026-10-16 hardened Rust run)
RVD-RPC-07review_summaries_rpc_stays_bounded_when_the_center_stallsStall → fails within the 5 s review deadlineverified (2026-10-16 hardened Rust run)
RVD-RPC-08review_summaries_rpc_never_holds_the_host_state_lock_across_ioTrusted origin captured, state lock released before I/Overified (2026-10-16 hardened Rust run)
RVD-RPC-09review_summaries_rpc_refuses_a_non_http_centerfile:// catalog → unavailable, no requestverified (2026-10-16 hardened Rust run)
RVD-RPC-10review_summaries_rpc_keeps_the_center_boundedRequest carries 5 s timeout, 1 MiB cap, no redirect followingverified (2026-10-16 hardened Rust run)
RVD-RPC-11reviews_rpc_returns_the_page_with_a_trusted_review_urlPage + summary + reviewUrl from the effective trusted origin (fixture override included)verified (2026-10-16 hardened Rust run)
RVD-RPC-12reviews_rpc_refuses_non_visible_rows_in_the_public_listNon-visible row → PLUGIN_MARKET_INVALIDverified (2026-10-16 hardened Rust run)
RVD-RPC-13reviews_rpc_propagates_center_outagesCenter error → propagated as PLUGIN_NETWORKverified (2026-10-16 hardened Rust run)
RVD-RPC-14reviews_rpc_rejects_invalid_input_before_any_networkBad pluginId/limit/cursor/sentiment/sort → INVALID_PARAMS, zero requestsverified (2026-10-16 hardened Rust run)
RVD-RPC-15reviews_rpc_rejects_pages_that_break_the_public_contractOver-limit page, wrong pluginId, bad rating/timestamps/bounds → PLUGIN_MARKET_INVALIDverified (2026-10-16 hardened Rust run)
RVD-RPC-16reviews_rpc_never_follows_redirects3xx from the center → refusal, not a hopverified (2026-10-16 hardened Rust run)

A 2026-10-16 cross-host run additionally exercised this layer end to end against a real running center (host stdio RPC, zero-state 9/9 and nonzero 11/11) — see §5 row 12. It complements, but does not replace, the focused tests above.

3.4 Host client bounds/validation — plugins/reviews.rs ​

IDTest (verbatim)ExpectedStatus
RVD-CLI-01review_reads_are_boundedTimeout 5 s; 1 MiB response cap; 100 summary idsverified (2026-10-16 hardened Rust run)
RVD-CLI-02the_origin_keeps_only_the_catalogs_hosthttp(s) origins kept incl. port; file://, credential URLs, query-smuggled authority refusedverified (2026-10-16 hardened Rust run)
RVD-CLI-03summaries_batches_dedupe_and_cap_at_one_hundred205 unique + duplicates → chunks 100/100/5, each id onceverified (2026-10-16 hardened Rust run)
RVD-CLI-04review_urls_stay_on_the_trusted_originCursor percent-encoded; list/summary/page URLs stay on the originverified (2026-10-16 hardened Rust run)
RVD-CLI-05summary_consistency_is_enforcedDistribution and sentiments sum to ratingCount; zero count ⇔ null average; 0..5 rangeverified (2026-10-16 hardened Rust run)
RVD-CLI-06review_contract_is_enforcedRight plugin, visible status, 2..=10 half stars, ISO timestamps, body ≤2000 chars, version ≤128, name ≤200; empty version + editedAt acceptedverified (2026-10-16 hardened Rust run)

3.5 Renderer pure logic — plugin-reviews-logic.test.mjs ​

IDTest title (verbatim)Expected assertionStatus
RVD-LOG-01the official review URL encodes the plugin id into the fallback pageOfficial URL with percent-encoded id (/, space, #)verified (2026-10-16 six-file run)
RVD-LOG-02the page URL wins only when it is a usable http(s) addressHost reviewUrl used when http(s); javascript:, data:, relative, blank → official fallbackverified (2026-10-16 six-file run)
RVD-LOG-03one query key names exactly one plugin, sentiment and sortDistinct plugin/filter/sort → distinct keysverified (2026-10-16 six-file run)
RVD-LOG-04mergeReviews appends unseen rows once and keeps the shown orderDeduped append, first-seen orderverified (2026-10-16 six-file run)
RVD-LOG-05rating formatting follows the contract: one decimal, half stars trimmed4.5→"4.5", 5→"5.0", null/NaN → null; half stars trimmed to whole/half star textverified (2026-10-16 six-file run)
RVD-LOG-06sentiment shares split the good / medium / bad counts into percentages[2,1,0] → 67/33/0; null summary → zerosverified (2026-10-16 six-file run)
RVD-LOG-07a query carries the page limit and only adds a cursor when pagingLimit always; cursor only when pagingverified (2026-10-16 six-file run)
RVD-LOG-08a first page fills the list and closes the loadReady state, rows, nextCursor, summary, effective URLverified (2026-10-16 six-file run)
RVD-LOG-09a summary of zero reviews is carried through as zero, not as unavailableratingCount 0, null average — distinct from unavailableverified (2026-10-16 six-file run)
RVD-LOG-10a summary answer replaces the requested ids, so an omitted id loses its ratingRequested subset replaces the map; unrequested key never enters; zero stays zero (stale-chip behavior)verified (2026-10-16 six-file run)
RVD-LOG-11a page that does not carry reviewUrl falls back to the official pageMissing reviewUrl → official URLverified (2026-10-16 six-file run)
RVD-LOG-12an answer for another query cannot reach the list on screenLate answer for an abandoned key → droppedverified (2026-10-16 six-file run)
RVD-LOG-13switching filter drops the old rows; retrying the same filter keeps themFilter switch clears to loading; same-key retry keeps rowsverified (2026-10-16 six-file run)
RVD-LOG-14paging appends without repeating a row that moved between pagesDedup by id; cursor cleared at endverified (2026-10-16 six-file run)
RVD-LOG-15a failure keeps readable rows and only replaces an empty listRows kept on later-page failure; first-page failure is an error stateverified (2026-10-16 six-file run)
RVD-LOG-16a failure for a query that is no longer on screen is ignoredStale failure → state unchangedverified (2026-10-16 six-file run)

3.6 UI source contracts — plugin-reviews-surface.test.mjs ​

IDTest title (verbatim)Expected assertionStatus
RVD-UI-01installed plugins keep their state groups and their disclosureGroup order attention, updates, active, disabled; role="list"; details disclosure keptverified (2026-10-16 six-file run)
RVD-UI-02an installed card grows one call to action: update, then open panelUpdate button names the target version; panel only when enabled+declared; settings behind enabled+declared; disabled card has no primaryverified (2026-10-16 six-file run)
RVD-UI-03installed and marketplace cards carry the rating chip in their metadataRatingChip inside the existing meta line; hidden when ratingCount <= 0verified (2026-10-16 six-file run)
RVD-UI-04the installed card shows the trust glyph only from installed metadataShield from installed trust tier; no "verified installed" wordingverified (2026-10-16 six-file run)
RVD-UI-05the detail sheet puts the ratings summary between permissions and versionsSection order Permissions < Ratings & reviews < Versions; unavailable copy; drawer entry not gated on the summary (unavailable-entry behavior)verified (2026-10-16 six-file run)
RVD-UI-06the review drawer mounts over the detail sheet without unmounting itSame layer, later DOM; absolute z-index 1; sheet + scrim inert while openverified (2026-10-16 six-file run)
RVD-UI-07Escape closes the top drawer only and focus comes back to its openerEscape claimed before the sheet; Tab trap wraps both directions; dialog focus + return to openerverified (2026-10-16 six-file run)
RVD-UI-08the drawer is a read-only surface whose one write path is the centre pageDisabled scoped-composer field; real anchor; click + middle-click open via api.browserOpenExternal; failure toast; no login/vote/report/fetchverified (2026-10-16 six-file run)
RVD-UI-09a review's version reads as self-reported, never as an installed versionreviewVersion label; no bare v{version} spanverified (2026-10-16 six-file run)
RVD-UI-10the drawer filters, sorts, and pages with stale answers droppedSentiment/sort groups with aria-pressed; cursor load-more; retry; role="alert"; generation invalidation incl. unmountverified (2026-10-16 six-file run)
RVD-UI-11every installed and marketplace id is batched without a tab refreshOne Set of installed+market ids → one summaries call; success replaces via nextSummaryMap(ids, …); failure → unavailable, never zeroverified (2026-10-16 six-file run)
RVD-UI-12closing the detail closes the drawer with itDrawer id and selection cleared togetherverified (2026-10-16 six-file run)
RVD-UI-13the review drawer keeps a real-Chromium user-path fixtureHarness pins the drawer probes — now five, including pluginReviewsBatchProbe and pluginReviewsNarrowDrawerProbe (with the 320×720 setContentSize switch); fixture drives the page hook, Tab/Escape, inert, write linkverified (2026-10-16 six-file run + 2026-10-17 probes)
RVD-UI-14the installed grid keeps a real-Chromium user-path fixturePins the installed-grid harness + fixture: installedGridLayoutProbe/installedGridActionsProbe, real InstalledPluginsPanel + PluginDialogs, the scope control and its menu (scope-compact-trigger, scope-compact-menu.is-open), the rail (plugins-row-controls), the action path (marketInstall, disablePlugin, setPluginScope), and the 340px grid floor (gridTemplateColumns)verified (2026-10-16 six-file run)

3.7 Page integration (fetcher semantics) ​

IDCheckExpectedStatus
RVD-PAGE-01Summary batch under rapid typing: settled cards come from the final query, a held earlier answer cannot overwrite thempluginReviewsBatchProbe (ordering-controlled — the first summaries answer is gated and released only after the final answer lands; no wall-clock debounce claim): settled cards are exactly the final query's plugins; the fresh chip reads 4.5 (3); the plugin the final batch does not rate shows no chip; the final batch's requested ids equal the displayed ids; after the held answer is released the fresh chip is unchanged and no stale chip appearsverified (2026-10-17 batch-stale-guard probe)
RVD-PAGE-02Summary failure is isolatedUnavailable status; install/update never waits on itverified (2026-10-16 six-file run / drawer probes)
RVD-PAGE-03Center predating the review APIUnavailable — distinct from zero; no fabricated zerosverified (2026-10-16 six-file run / drawer probes)
RVD-PAGE-04Drawer list failure independent from summaryDrawer retries its own list; disabled composer + link out remain reachableverified (2026-10-16 six-file run / drawer probes)

3.8 Electron fixture E2E — scripts/e2e-plugin-reviews-drawer.mjs ​

IDProbe (verbatim)Steps / expectedStatus
RVD-E2E-01pluginReviewsDrawerProbeCard chip (aria-label "Rated 4.5 out of 5 from 3 ratings", text 4.5 (3)) → detail section order → opener scrolled into view → drawer takes focus, sheet+scrim inert → Tab trap wraps both ways → first page with "User-reported version v1.2.0" → Load more appends r1/r2/r3 deduplicated → slow "All" answer outrun by a filter switch is dropped → Negative failure shows error + retry recovers → write link href is the host URL; primary and middle-click open it; right-click does not → open failure toast → Escape closes only the drawer, focus and sheet scroll restoredverified (2026-10-16 six-probe rerun)
RVD-E2E-02pluginReviewsStaleSummaryProbeA summaries refresh whose answer omits a requested id removes that plugin's stale rating chipverified (2026-10-16 six-probe rerun)
RVD-E2E-03pluginReviewsUnavailableSummaryProbeSummary read fails → no chip; detail shows "Ratings aren't available from this source." with the drawer entry present; drawer retry state, disabled composer, official fallback link; retry recovers the list and the host reviewUrl replaces the fallbackverified (2026-10-16 six-probe rerun)

The drawer harness runs five probes: the three above plus pluginReviewsBatchProbe (RVD-PAGE-01) and pluginReviewsNarrowDrawerProbe (RVD-NARROW-01, with the 320×720 useContentSize switch). Full run: 5/5 PASS.

3.9 Installed grid, detail focus, and the installed-card fixture ​

IDRunnerScope / expectedStatus
RVD-E2E-04node scripts/e2e-installed-grid.mjs (scripts/e2e/installed-grid.tsx), probes installedGridLayoutProbe + installedGridActionsProbeReal InstalledPluginsPanel + PluginDialogs in a 1100×900 fixture window, only the IPC boundary fixtured; seven plugins (error, disabled-with-update, plain disabled, panel-only, settings-only, updater, long id). Layout probe PASS: groups/members/error, rail order + disabled gating, disclosure, real scope menu, chip inline + long-id truncation, 340px grid floor + sub-340 fallback, narrow rail wrap. Actions probe PASS: update→permission→marketInstall(v1.2.3), panel id, settings sheet opens/closes, overflow auto-update false + uninstall. Reported 2/2 pass; the final rerun decidesverified (2026-10-16 six-probe rerun, 2/2)
RVD-E2E-05node scripts/e2e-plugin-detail-focus.mjs (scripts/e2e/plugin-detail-focus.tsx)Detail-sheet focus/scroll interplay while the summary read returns {} — sheet behavior independent of the review surfaceverified (2026-10-16 six-probe rerun)

Installed-card real-component fixture (landed). Runner scripts/e2e-installed-grid.mjs, fixture scripts/e2e/installed-grid.tsx, probes installedGridLayoutProbe (groups, rail, disclosure, scope, meta, grid) and installedGridActionsProbe (update path, panel, settings, row menu). Only the IPC boundary is fixtured; nothing installs outside the renderer. Rows below state only what the fixture asserts.

IDCaseSteps / expected (as asserted by the fixture)Status
RVD-GRID-01Grid — wideAt the 1100px host, .plugins-list computed columns are ≥2 and every column is ≥340−1px (the 340px floor)verified (2026-10-16 installed-grid layout probe)
RVD-GRID-02Grid — narrowHost forced to 300px: exactly one column ≤300px; the grid does not overflow horizontally (scrollWidth <= clientWidth + 1); the card's right edge stays inside the gridverified (2026-10-16 installed-grid layout probe)
RVD-GRID-03Control orderUpdater rail exactly 4: scope-control → primary "Update to v1.2.3" → settings icon (aria-label "Plugin settings") → menu wrap; panel-only rail 3: scope → "Open panel" → menu; settings-only rail 3 with the settings icon second; each disabled card: rail 2 (scope + menu), no primary, no settings icon, no panel actionverified (2026-10-16 installed-grid layout probe)
RVD-GRID-04Update CTAPrimary click → permission dialog "Install Acme Updater?" naming "v1.2.3" → accept → marketInstall recorded as {id:"acme.updater", version:"1.2.3", enable:true, autoUpdate:true}; the install dialog steps asideverified (2026-10-16 installed-grid actions probe)
RVD-GRID-05Panel CTA"Open panel" on the panel-only card → openPluginPanel("acme.panel"); disabled cards offer no panel actionverified (2026-10-16 installed-grid actions probe)
RVD-GRID-06SettingsSettings icon → .plugins-settings-modal titled "Acme Updater settings"; the close control dismisses itverified (2026-10-16 installed-grid actions probe)
RVD-GRID-07Groups, membership, disabled statesSeven fixtures in four groups: Needs attention 1 (Acme Broken, error text "Manifest could not be read"); Updates available 2 (Acme Dormant, Acme Updater); Active 3 (Acme Panel, Acme Settings, Long Identifier); Turned off 1 (Acme Off). Disabled gating as in GRID-03verified (2026-10-16 installed-grid layout probe)
RVD-GRID-08ScopeCompact scope trigger (first control) opens .scope-compact-menu.is-open with 3 role="menuitemradio" choices; "Off" → disablePlugin("acme.updater") and the menu closes; "Projects" → setPluginScope("acme.updater", {mode:"projects", projects:[]})verified (2026-10-16 installed-grid layout probe)
RVD-GRID-09Row overflow menuTrigger aria-label "Actions for Acme Updater": "Disable auto-update" → setPluginAutoUpdate("acme.updater", false), menu closes; reopen → "Uninstall" → uninstallPlugin("acme.updater"). Source-level (not exercised — no such plugin seeded): Reload appears only for source === "dev", and bundled plugins omit Uninstallverified (2026-10-16 installed-grid actions probe)
RVD-GRID-10DisclosureUpdater's details.plugins-row-details starts closed, opens on summary click, and its body includes "Capabilities"; the settings-only card has no details sectionverified (2026-10-16 installed-grid layout probe)
RVD-GRID-11Rating chip geometryChip sits inside .plugins-row-meta (chipRect within metaRect ±1px — it does not wrap onto its own line); meta line height ≤24px; the zero-summary card renders no chip; the long-id card's .plugins-row-id truncates (scrollWidth > clientWidth + 8) while staying inside the card. No card-height comparison between chip present/absent is asserted hereverified (2026-10-16 installed-grid layout probe)

3.10 Locales, accessibility, narrow width, style ​

IDCheckExpectedStatus
RVD-I18N-01Review strings in all nine locales (de,en,es,fr,ko,pt-BR,tr,zh-CN,zh-TW)Every locale carries exactly one nested reviews: { block and all seven key names (summaryTitle, viewAll, composeHint, composeCta, reviewVersion, filterLabel, sortLabel); the §2.5 check prints reviewsBlock=1 sevenKeys=7 per locale; vitest catalogue suite greenverified (catalogue 30/30 + shared 1272; locale parity re-checked)
RVD-A11Y-01Accessible names/states of the drawer surfacerole="dialog", aria-modal="true", labelled title; filters/sorts aria-pressed; list label; load error role="alert"; star rating role="img" with a rating label; Tab trap + inert; Escape returns focusverified (2026-10-16 six-probe rerun)
RVD-NARROW-01Narrow drawer at 320×720 (real geometry)pluginReviewsNarrowDrawerProbe on the real components with the hidden window resized to 320×720 (useContentSize): drawer rect fully inside the viewport; document and drawer-body have no horizontal overflow; 7+ controls (4 sentiment, 2 sort, close, write link) stay inside the drawer; filter/sort aria-pressed; body scrolls (scrollTop > 0); composer inside the viewport; write link routes browserOpenExternal(REVIEW_URL); close leaves the detail sheet. Harness applies base.css and the repo's reduced-motion animation: none. 320 is the only asserted narrow width — 375 is covered by the probe's <= 375 guard, not a second runverified (2026-10-17 narrow-drawer probe)
RVD-STYLE-01plugins-page-style.test.mjs — "review surfaces use design tokens in both themes"Chip tabular numerals; summary uses --plugins-tile; scrim uses --ds-modal-veil; drawer reserves --ds-toolbar-height; composer field token; write link styledverified (2026-10-16 six-file run)

3.11 Non-interference invariants ​

IDCheckExpectedStatus
RVD-REL-01Center downtime / legacy center never affects install, update, or plugin actionsReview reads fail in isolation; installed grid and marketplace actions stay usable while summary/list reads failverified (probe + focused-test evidence, 2026-10-16)
RVD-REL-02No renderer network or auth for reviewsOnly host RPC channels; no fetch in the drawer; browserOpenExternal is the sole external navigation, host-URL onlyverified (probe + focused-test evidence, 2026-10-16)
RVD-REL-03URL trustNo URL derived from package or review content; reviewUrl only from the effective trusted origin (fixture override included)verified (probe + focused-test evidence, 2026-10-16)

4. Final evidence summary ​

All behavior rows below are closed on the frozen source; the execution record in §5 carries the artifacts.

BehaviorCases
Unavailable entry retained (drawer entry, retry, link out)Probe verified 2026-10-16 (RVD-E2E-03); RVD-UI-05 verified in the six-file run; RVD-PAGE-04 verified (drawer failure independence pinned by the probes)
Stale chip omission removes the chipProbe verified 2026-10-16 (RVD-E2E-02); RVD-LOG-10 / RVD-UI-11 verified in the six-file run
Bridge hardening: response identity, redirect refusal, time boundsVerified — 22 Rust tests (16 RPC + 6 client-unit) passed after hardening with no Rust source changes since (host-core-after-hardening.txt)
Installed-card real-component fixtureVerified 2026-10-16 (RVD-E2E-04 + RVD-GRID-01 … RVD-GRID-11); RVD-UI-14 verified in the six-file run

5. Final execution record (this run) ​

Local execution environment for this run: Windows / PowerShell; isolated Electron fixture profiles under .cache/; worktree revision recorded per row. Results below are the final recorded execution; each row cites its evidence artifact. Sources are frozen; the two manual geometry/behavior checks remain listed as manual, separately from the automated evidence.

Dated result for this run: 2026-10-16/17, coordinator-verified — 64 targeted JS tests across the six files and the eight Electron probes (5 drawer + 2 installed-grid + 1 detail-focus) pass on the current exact source; full desktop typecheck and the production electron-vite build exit 0 (main/preload/renderer built). The cross-host contract check (zero 9/9, nonzero 11/11) was independently read and accepted. No further desktop production-code changes are expected for this run; this plan is the only remaining mutable artifact.

#Command / pathStatusEvidence recordedNotes
1node --test … market-review-ipc / plugin-reviews-logic / plugin-reviews-surface / plugins-page-style / extensions-page / settings-dialog-overlay …verified2026-10-16 coordinator targeted six-file run: 64/64 pass (shell output)Per file: IPC 6, logic 16, surface 14, style 8, extensions 16, overlay host suite 4. Confirm the overlay filename against the run's shell output when archiving the log
2cargo test -p host-core --bin pi-desktop-host-core reviewverified22 Rust tests (16 RPC + 6 client-unit) passed after hardening; no Rust source changes since (host-core-after-hardening.txt)Accepted existing evidence — no gratuitous rerun
3cargo fmt --check / cargo clippy -p host-core --all-targetsverifiedfmt clean; clippy exit 0 (clippy-after-hardening.txt)Known unrelated warn at transcripts.rs:893
4node node_modules/typescript/bin/tsc -p tsconfig.json --noEmit (desktop)verified2026-10-16 final rerun on the F7/F8 code: exit 0 (plugin-reviews-desktop-finalbuild.log)Full desktop typecheck passed
5node node_modules/electron-vite/bin/electron-vite.js build (desktop)verified2026-10-16 final rerun on the F7/F8 code: exit 0 (plugin-reviews-desktop-finalbuild.log)main/preload/renderer built; only the pre-existing large-chunk warning
6node scripts/e2e-plugin-reviews-drawer.mjsverified2026-10-17: drawer harness 5/5 PASS, exit 0 (incl. the batch-stale-guard and narrow-drawer probes)Total Electron probes now 8 (5 drawer + 2 grid + 1 detail-focus); supersedes the earlier 3-probe drawer run
7node scripts/e2e-installed-grid.mjs (installedGridLayoutProbe + installedGridActionsProbe)verified2026-10-16 coordinator six-probe rerun (exit 0)Installed grid 2/2; carries RVD-GRID-01 … 11
8node scripts/e2e-plugin-detail-focus.mjsverified2026-10-16 coordinator six-probe rerun (exit 0)Detail focus passed
9i18n §2.5 checks + vitest catalogue suiteverifiedCatalogue suite 30/30 and shared 1272 passed (recorded earlier); reviewsBlock=1 sevenKeys=7 re-checked mechanically on the current source for all nine localesReview keys present in every locale; no UI reruns
10cargo test -p host-core --bin pi-desktop-host-core (full)executed; baseline failures912 passed / 38 baseline:fail / 1 ignored (host-core-after-hardening.txt)Environment failures only; not a green suite
11Narrow-window checkverifiedpluginReviewsNarrowDrawerProbe at 320×720 (2026-10-17): no overflow, controls inside, body scrolls, composer inside, link routes, close leaves the sheet. Installed-grid narrow covered separately by the 300px probe (RVD-GRID-02)320 is the only asserted narrow width; 375 covered by the <= 375 guard only
12Cross-host contract check (host stdio RPC → real center)verified2026-10-16 — zero run 9/9 + nonzero run 11/11, independently read and acceptedSame host binary (no rebuild); nonzero seed via the center's real write API; no private fields despite one report fixture. Complements the focused RVD-RPC-01…16 tests, which passed after hardening (row 2)
13Final oracle gatePASSAll eight known findings closed on the frozen source; no further production fixes expectedCross-repo gate covering both plans

6. Change control ​

  • This document is the repo deliverable; corrections land here only.
  • Final results are recorded in §5 and the affected case rows are updated in place.
  • No commits, pushes, tags, or packaging from this plan.

Local-first · Model-agnostic · Plugin-powered. nib.am