feat(plugins): redesign plugins view to match mockup#80
Conversation
Refactor the plugins catalogue page to align with the mockup design: - PluginsHeader with enabled/disabled counters and "Check updates" action - PluginsToolbar with segmented category pills replacing the Select - Category-grouped sections with uppercase labels and card containers - Monogram icons (accent-tinted for crawlers/extractors) - Switch toggle for installed plugins (calls plugin_disable) - Inline update pill for plugins with an available update - Kebab menu hosting the destructive "Uninstall" action Underlying mutations (install, update, disable, uninstall, refresh) are unchanged; the refactor is UI-only. i18n keys added for stats, categories and actions in both fr and en.
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughAdds a plugins UI: header, toolbar, grouped plugin listing with search/category filtering, redesigned plugin rows, i18n strings in English and French, grouping utility, tests, and a minor changelog wording edit about download completion event timing. Changes
Sequence Diagram(s)sequenceDiagram
actor User
participant Toolbar as "PluginsToolbar\n(rgba(70,130,180,0.5))"
participant View as "PluginsView\n(rgba(34,139,34,0.5))"
participant Header as "PluginsHeader\n(rgba(255,165,0,0.5))"
participant Row as "PluginStoreRow\n(rgba(147,112,219,0.5))"
participant API as "Backend / Mutations\n(rgba(220,20,60,0.5))"
User->>Toolbar: select category / type search
Toolbar->>View: onCategoryChange / onSearchChange
View->>View: filter entries (memoized)
View->>View: groupByCategory (memoized)
View->>Header: update enabledCount
View->>Row: render grouped rows
User->>Row: click install/update/uninstall/disable/enable
Row->>API: call mutation (install/update/disable/enable/uninstall)
API-->>Row: mutation result
Row->>View: callbacks / invalidate queries
View->>Header: refresh counts / rerender
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes Possibly related PRs
Suggested labels
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
Greptile SummaryThis PR replaces the flat plugins catalogue with a grouped, mockup-aligned layout: a compact Confidence Score: 5/5Safe to merge — all prior P1 findings are resolved; remaining items are minor P2 suggestions. Both previously flagged P1 issues are addressed: No files require special attention beyond the two P2 suggestions in
|
| Filename | Overview |
|---|---|
| src/views/PluginsView.tsx | Main view orchestrates header, toolbar, and grouped rows; optimistic locallyDisabled state and enabledCount derivation are correct; previous hardcoded-0 and Switch issues are resolved. |
| src/views/PluginsView/PluginStoreRow.tsx | Row renders monogram icon, conditional install/update/kebab actions; separator logic is correct; Switch replaced with a proper Disable/Enable/Uninstall kebab menu. |
| src/views/PluginsView/PluginsHeader.tsx | Compact header with enabled counter and refresh button; disabledCount prop removed since the DTO has no disabled status variant. |
| src/views/PluginsView/PluginsToolbar.tsx | Category pills + search input; uses role="tablist" / role="tab" / aria-selected without aria-controls or corresponding role="tabpanel" — the ARIA tab relationship is incomplete. |
| src/views/PluginsView/groupByCategory.ts | Simple Map-based grouping that preserves insertion order; well-tested by the accompanying test file. |
| src/i18n/locales/en.json | New plugins.* keys added; plugins.action.update and plugins.action.browseRepo are defined but never consumed by any component. |
| src/i18n/locales/fr.json | French translations for all new plugin keys match their English counterparts; pluralization is correct. |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart TD
PV[PluginsView]
PV --> PH[PluginsHeader\nenabled count + refresh]
PV --> PT[PluginsToolbar\ncategory pills + search]
PV --> GBC[groupByCategory\nfiltered entries]
GBC --> SEC[section per category]
SEC --> PSR[PluginStoreRow × N]
PSR -->|status = not_installed| INS[Install button\n→ plugin_store_install]
PSR -->|status = update_available| UPD[Version pill\n→ plugin_store_update]
PSR -->|installed-like| KEB[Kebab menu]
KEB -->|!locallyDisabled| DIS[Disable\n→ plugin_disable\nsets locallyDisabled]
KEB -->|locallyDisabled| ENB[Enable\n→ plugin_enable\nclears locallyDisabled]
KEB --> UNI[Uninstall\n→ plugin_uninstall\nclears locallyDisabled]
PH -->|onRefresh| REF[plugin_store_refresh]
Prompt To Fix All With AI
This is a comment left during a code review.
Path: src/i18n/locales/en.json
Line: 261
Comment:
**Unused `update` and `browseRepo` i18n keys**
`plugins.action.update` and `plugins.action.browseRepo` are defined in both `en.json` and `fr.json` but are never referenced in any component. The update button in `PluginStoreRow` renders `v{entry.version}` directly without a translated label, and no "Browse repository" action is wired up. These dead keys will silently diverge from reality if translators later update them.
Consider removing both keys until a component actually consumes them, or add a comment explaining they're intentionally reserved for upcoming work.
How can I resolve this? If you propose a fix, please make it concise.
---
This is a comment left during a code review.
Path: src/views/PluginsView/PluginsToolbar.tsx
Line: 23-46
Comment:
**Incomplete ARIA tab semantics**
The container has `role="tablist"` and each pill has `role="tab"` + `aria-selected`, but no `aria-controls` attribute links a tab to its panel, and the content sections in `PluginsView` have no matching `role="tabpanel"` / `id`. Screen readers will announce these as tabs that control a panel, but cannot navigate to that panel, leaving keyboard-only users stranded.
Either complete the pattern by assigning stable `id`s to the section panels and wiring up `aria-controls`, or replace `role="tablist"` / `role="tab"` with `role="toolbar"` / `role="button"` to match what the widget actually does (a filter control, not a tabbed interface).
How can I resolve this? If you propose a fix, please make it concise.Reviews (6): Last reviewed commit: "fix(plugins): clear locallyDisabled on u..." | Re-trigger Greptile
There was a problem hiding this comment.
Actionable comments posted: 4
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
CHANGELOG.md (1)
10-50:⚠️ Potential issue | 🔴 CriticalDuplicate
### Addedsection heading breaks changelog format.The changelog contains two
### Addedsections under## [Unreleased](lines 10 and 49), which violates the Keep a Changelog format. Each section type should appear only once per version.Merge these into a single
### Addedsection by moving theuseTauriMutationentry (lines 12-13) into the larger### Addedsection starting at line 49, then remove the first section heading and its surrounding blank lines.📝 Proposed fix to merge the duplicate sections
## [Unreleased] -### Added - -- `useTauriMutation` now accepts `silentError` (opt-out of the default toast) and `errorMessage` (remap the error message before toasting) options. (`#74`) - ### Changed - Plugins view refreshed to match the design mockup: a header with enabled/disabled counters and a "Check updates" action, a segmented category filter replacing the dropdown, grouped sections per category with uppercase labels, monogram icons with accent coloring for crawlers/extractors, a toggle for installed plugins, and a kebab menu hosting the destructive "Uninstall" action. Installable plugins keep a single `Install` button; pending updates surface as an inline pill on the row.Then move the
useTauriMutationbullet to the beginning of the### Addedsection at line 49:### Added +- `useTauriMutation` now accepts `silentError` (opt-out of the default toast) and `errorMessage` (remap the error message before toasting) options. (`#74`) - **Clear completed / Clear failed downloads**: two new toolbar buttons in the Downloads view, separated from the bulk actions by a vertical separator. Each opens a confirmation dialog with an optional "Also delete files from disk" checkbox gated behind a prominent red warning panel. Success and error outcomes are reported via toasts.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@CHANGELOG.md` around lines 10 - 50, There are two duplicate "### Added" headings; remove the first "### Added" heading block (including its surrounding blank lines) and move the `- useTauriMutation` bullet (the line mentioning `silentError` and `errorMessage`) into the existing larger "### Added" section later in the file so there is only one "### Added" section; ensure the moved bullet is placed at the start of that single "### Added" section and delete the now-empty original heading.
🧹 Nitpick comments (1)
src/views/PluginsView/PluginsToolbar.tsx (1)
23-46: Consider whether full tab semantics are appropriate here.The
role="tablist"androle="tab"pattern typically implies tab panels that get revealed/hidden. Here it's used for filtering, which is more of a toggle button group. This isn't strictly incorrect but may confuse assistive technology users expecting panel switching.If keeping tabs, consider adding
aria-controlspointing to the content area. Alternatively, a radio group or toggle button pattern could be more semantically accurate for filter controls.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/views/PluginsView/PluginsToolbar.tsx` around lines 23 - 46, The toolbar currently uses role="tablist" and role="tab" in PluginsToolbar (the categories.map buttons with onCategoryChange, categories, activeCategory and label variables), which implies tab panels; either add proper tab semantics by giving each button an aria-controls that references the ID of the associated content panel and ensure the panel has a matching id and appropriate aria-hidden toggling, or change the pattern to a more accurate filter control (e.g., role="radiogroup" on the container and role="radio" with aria-checked on each button or plain buttons with aria-pressed) and update the buttons' attributes (aria-selected → aria-checked/aria-pressed) to match that choice so assistive tech will interpret the control correctly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/i18n/locales/en.json`:
- Around line 250-255: The stats plural keys use the outdated `_plural` suffix;
update the keys under the "stats" object by replacing "enabled_plural" and
"disabled_plural" with i18next-standard "enabled_one"/"enabled_other" and
"disabled_one"/"disabled_other" respectively (leave the existing "enabled" and
"disabled" entries as appropriate), so the i18n pluralization for the stats
strings works with functions that pass a count.
In `@src/i18n/locales/fr.json`:
- Around line 250-254: The plural keys under "stats" (stats.enabled,
stats.enabled_plural, stats.disabled, stats.disabled_plural) don't match the
repo's i18next plural shape and will break PluginsHeader.tsx which passes {
count }; rename the plural keys to use the existing pattern (e.g.,
stats.enabled_one and stats.enabled_other, stats.disabled_one and
stats.disabled_other) and keep the corresponding French translations so
i18next's plural resolver picks the correct form when count > 1.
In `@src/views/PluginsView.tsx`:
- Around line 70-74: The disabledCount is hardcoded to 0 in PluginsView.tsx
which makes the UI counter meaningless; update the logic so disabledCount is
computed from entries (e.g., const disabledCount = useMemo(() =>
entries.filter(e => !isInstalled(e.status)).length, [entries]) or, if the data
model doesn't support a disabled state yet, remove the disabled counter from the
header or add a TODO comment next to the disabledCount declaration to mark it as
a placeholder; locate and modify the disabledCount declaration near enabledCount
and use entries and isInstalled to drive the value.
In `@src/views/PluginsView/PluginStoreRow.tsx`:
- Around line 78-88: The update-pill currently renders entry.version without a
leading "v"; update the JSX inside the Button (the element rendered when
entry.status === "update_available") to prepend a literal "v" before
entry.version (so it renders "vX.Y.Z"), matching the installed-version label
display; keep the existing onUpdate call, disabled={isUpdating}, and visual
children (ArrowUpCircle) intact.
---
Outside diff comments:
In `@CHANGELOG.md`:
- Around line 10-50: There are two duplicate "### Added" headings; remove the
first "### Added" heading block (including its surrounding blank lines) and move
the `- useTauriMutation` bullet (the line mentioning `silentError` and
`errorMessage`) into the existing larger "### Added" section later in the file
so there is only one "### Added" section; ensure the moved bullet is placed at
the start of that single "### Added" section and delete the now-empty original
heading.
---
Nitpick comments:
In `@src/views/PluginsView/PluginsToolbar.tsx`:
- Around line 23-46: The toolbar currently uses role="tablist" and role="tab" in
PluginsToolbar (the categories.map buttons with onCategoryChange, categories,
activeCategory and label variables), which implies tab panels; either add proper
tab semantics by giving each button an aria-controls that references the ID of
the associated content panel and ensure the panel has a matching id and
appropriate aria-hidden toggling, or change the pattern to a more accurate
filter control (e.g., role="radiogroup" on the container and role="radio" with
aria-checked on each button or plain buttons with aria-pressed) and update the
buttons' attributes (aria-selected → aria-checked/aria-pressed) to match that
choice so assistive tech will interpret the control correctly.
🪄 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: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 844f2013-0f34-46ac-b7b5-b19054691ef1
📒 Files selected for processing (13)
CHANGELOG.mdsrc/i18n/__tests__/issue30-ui-fr.test.tsxsrc/i18n/locales/en.jsonsrc/i18n/locales/fr.jsonsrc/views/PluginsView.tsxsrc/views/PluginsView/PluginStoreRow.tsxsrc/views/PluginsView/PluginsHeader.tsxsrc/views/PluginsView/PluginsToolbar.tsxsrc/views/PluginsView/__tests__/PluginStoreRow.test.tsxsrc/views/PluginsView/__tests__/PluginsHeader.test.tsxsrc/views/PluginsView/__tests__/PluginsToolbar.test.tsxsrc/views/PluginsView/__tests__/groupByCategory.test.tssrc/views/PluginsView/groupByCategory.ts
| "stats": { | ||
| "enabled": "enabled", | ||
| "enabled_plural": "enabled", | ||
| "disabled": "disabled", | ||
| "disabled_plural": "disabled" | ||
| }, |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Check how the stats keys are used in PluginsHeader
ast-grep --pattern 't("plugins.stats.$_"$$$)'
rg -n "plugins\.stats\." --type=ts --type=tsx -C2Repository: mpiton/vortex
Length of output: 84
🏁 Script executed:
# First, locate the en.json file and verify its content
find . -name "en.json" -type fRepository: mpiton/vortex
Length of output: 83
🏁 Script executed:
# Read the en.json file around the lines mentioned
head -300 src/i18n/locales/en.json | tail -60Repository: mpiton/vortex
Length of output: 1660
🏁 Script executed:
# Search for usage of the stats keys in the codebase with correct syntax
rg -n "plugins\.stats" --type ts -C2Repository: mpiton/vortex
Length of output: 828
🏁 Script executed:
# Also search for any ts files to understand the file structure
rg -l "plugins\.stats"Repository: mpiton/vortex
Length of output: 96
🏁 Script executed:
# Read lines 140-150 to see the titleCompleted pattern
sed -n '140,150p' src/i18n/locales/en.jsonRepository: mpiton/vortex
Length of output: 637
🏁 Script executed:
# Search for i18next configuration files
find . -name "*i18n*" -o -name "*i18next*" | grep -E "\.(ts|js|json)$" | head -20Repository: mpiton/vortex
Length of output: 99
🏁 Script executed:
# Search for other count-based pluralization in the codebase to see patterns
rg -n "{ count:" src/ --type ts | head -20Repository: mpiton/vortex
Length of output: 140
🏁 Script executed:
# Check if there are other _plural suffixes in en.json
rg -n "_plural" src/i18n/locales/en.jsonRepository: mpiton/vortex
Length of output: 135
🏁 Script executed:
# Read the i18n configuration file
cat src/i18n/i18n.tsRepository: mpiton/vortex
Length of output: 798
🏁 Script executed:
# Also check the test file to understand how pluralization is expected to work
cat src/i18n/i18n.test.tsRepository: mpiton/vortex
Length of output: 889
Change pluralization suffixes from _plural to _one/_other to match i18next standard format.
The stats keys use _plural suffixes, but i18next (configured with default pluralization) expects _one and _other suffixes when a count parameter is passed. This pattern is already correctly used elsewhere in the file (e.g., titleCompleted_one/titleCompleted_other). The current keys won't trigger proper pluralization behavior.
Proposed fix
"stats": {
- "enabled": "enabled",
- "enabled_plural": "enabled",
- "disabled": "disabled",
- "disabled_plural": "disabled"
+ "enabled_one": "enabled",
+ "enabled_other": "enabled",
+ "disabled_one": "disabled",
+ "disabled_other": "disabled"
},📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| "stats": { | |
| "enabled": "enabled", | |
| "enabled_plural": "enabled", | |
| "disabled": "disabled", | |
| "disabled_plural": "disabled" | |
| }, | |
| "stats": { | |
| "enabled_one": "enabled", | |
| "enabled_other": "enabled", | |
| "disabled_one": "disabled", | |
| "disabled_other": "disabled" | |
| }, |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/i18n/locales/en.json` around lines 250 - 255, The stats plural keys use
the outdated `_plural` suffix; update the keys under the "stats" object by
replacing "enabled_plural" and "disabled_plural" with i18next-standard
"enabled_one"/"enabled_other" and "disabled_one"/"disabled_other" respectively
(leave the existing "enabled" and "disabled" entries as appropriate), so the
i18n pluralization for the stats strings works with functions that pass a count.
| "stats": { | ||
| "enabled": "activé", | ||
| "enabled_plural": "activés", | ||
| "disabled": "désactivé", | ||
| "disabled_plural": "désactivés" |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
In the current i18next pluralization format used by react-i18next, does t("plugins.stats.enabled", { count: 2 })resolveenabled_plural, or does it expect enabled_one/enabled_other style keys?
💡 Result:
In the current i18next pluralization format used by react-i18next, t("plugins.stats.enabled", { count: 2 }) expects enabled_other style keys (Intl.PluralRules suffixes like _one, _other, etc.), not enabled_plural. The modern JSON v4 format (default since i18next v21+) uses suffixes matching Intl.PluralRules: _one, _other, and language-specific forms like _few, _many, _zero. For English with count=2, it resolves plugins.stats.enabled_other. Older formats used _plural, but v4 is current. react-i18next inherits this from i18next. To use legacy formats, set compatibilityJSON: 'v3' or earlier, but current expects _one/_other style. Example for English: { "plugins": { "stats": { "enabled_one": "1 stat enabled", "enabled_other": "{{count}} stats enabled" } } } t("plugins.stats.enabled", { count: 2 }) → "2 stats enabled"
Citations:
- 1: https://www.i18next.com/translation-function/plurals
- 2: https://www.i18next.com/translation-function/plurals?q=_other
- 3: https://github.com/i18next/i18next-gitbook/blob/master/translation-function/plurals.md
- 4: http://www.i18next.com/misc
- 5: https://www.i18next.com/misc/json-format
- 6: https://github.com/i18next/i18next-gitbook/blob/master/misc/json-format.md
- 7: https://www.i18next.com/misc/migration-guide
🏁 Script executed:
# Find PluginsHeader.tsx and check the t() calls
find . -name "PluginsHeader.tsx" -type f 2>/dev/null | head -5Repository: mpiton/vortex
Length of output: 98
🏁 Script executed:
# Find the French locale file
find . -path "*/locales/fr.json" -type f 2>/dev/nullRepository: mpiton/vortex
Length of output: 83
🏁 Script executed:
# Check if fr.json exists and examine the stats section
find . -name "fr.json" -path "*/locales/*" -type f 2>/dev/null -exec wc -l {} +Repository: mpiton/vortex
Length of output: 87
🏁 Script executed:
# Read PluginsHeader.tsx to check the t() calls
cat -n ./src/views/PluginsView/PluginsHeader.tsx | grep -A 5 -B 5 "plugins.stats"Repository: mpiton/vortex
Length of output: 879
🏁 Script executed:
# Read fr.json around lines 250-254 to see the stats section and context
sed -n '240,260p' ./src/i18n/locales/fr.jsonRepository: mpiton/vortex
Length of output: 554
🏁 Script executed:
# Search for _one and _other patterns in fr.json to verify existing plural format
rg "_one|_other" ./src/i18n/locales/fr.jsonRepository: mpiton/vortex
Length of output: 958
Use the repo's existing i18next plural key shape here.
PluginsHeader.tsx passes { count } into these keys, but the locale file consistently uses _one / _other elsewhere (e.g., selectedCount_one, activeCount_one, titleCompleted_one, etc.). The current enabled_plural / disabled_plural format is incompatible with the modern i18next pluralization resolver, so counts above 1 will render the singular label.
Suggested fix
"stats": {
- "enabled": "activé",
- "enabled_plural": "activés",
- "disabled": "désactivé",
- "disabled_plural": "désactivés"
+ "enabled_one": "activé",
+ "enabled_other": "activés",
+ "disabled_one": "désactivé",
+ "disabled_other": "désactivés"
},📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| "stats": { | |
| "enabled": "activé", | |
| "enabled_plural": "activés", | |
| "disabled": "désactivé", | |
| "disabled_plural": "désactivés" | |
| "stats": { | |
| "enabled_one": "activé", | |
| "enabled_other": "activés", | |
| "disabled_one": "désactivé", | |
| "disabled_other": "désactivés" | |
| }, |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/i18n/locales/fr.json` around lines 250 - 254, The plural keys under
"stats" (stats.enabled, stats.enabled_plural, stats.disabled,
stats.disabled_plural) don't match the repo's i18next plural shape and will
break PluginsHeader.tsx which passes { count }; rename the plural keys to use
the existing pattern (e.g., stats.enabled_one and stats.enabled_other,
stats.disabled_one and stats.disabled_other) and keep the corresponding French
translations so i18next's plural resolver picks the correct form when count > 1.
There was a problem hiding this comment.
4 issues found across 13 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="src/views/PluginsView.tsx">
<violation number="1" location="src/views/PluginsView.tsx:74">
P2: `disabledCount` is hardcoded to `0`, so the disabled plugins stat in the header is always incorrect.</violation>
</file>
<file name="src/i18n/locales/fr.json">
<violation number="1" location="src/i18n/locales/fr.json:251">
P2: Pluralization keys use `_plural` format, but the app is configured for i18next v4-style plural suffixes (`_one`/`_other`), so count-based labels may render incorrectly.</violation>
</file>
<file name="src/views/PluginsView/PluginStoreRow.tsx">
<violation number="1" location="src/views/PluginsView/PluginStoreRow.tsx:108">
P1: `<Switch checked />` is equivalent to `<Switch checked={true} />`, so the toggle is permanently "on" for every installed plugin. After the user clicks to disable, the query is invalidated and refetched, but since `PluginStoreEntry.status` has no `"disabled"` variant the entry still comes back as `"installed"` — the switch stays checked and the user gets no visual feedback. At minimum, make this conditional (e.g., `checked={entry.status !== "disabled"}`) so the UI will reflect the correct state once a disabled status is supported.</violation>
</file>
<file name="src/i18n/locales/en.json">
<violation number="1" location="src/i18n/locales/en.json:252">
P1: Pluralization keys use `_plural` suffix but i18next v21+ (JSON v4 format, the default) expects `_one`/`_other` suffixes. Passing `{ count }` to `t("plugins.stats.enabled")` will never resolve `enabled_plural` — it falls back to the base key for all counts. The rest of this codebase already uses `_one`/`_other` (e.g., `titleCompleted_one`/`titleCompleted_other`).</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
- Drop the Switch toggle on installed rows. The PluginStoreEntry DTO has no "disabled" variant, so the Switch was permanently checked and click → plugin_disable produced no visible feedback. The disable action now lives in the kebab menu alongside uninstall, matching what the backend can actually reflect today. - Drop the hardcoded "disabled" counter from the header. Same reason: until the DTO exposes a disabled state, the counter was always 0. - Prefix the update pill with "v" (e.g. "↑ v1.2.0") for consistency with the installed-version label. - Fix i18next pluralization keys: _plural → _one/_other (v4 format used by the rest of the codebase).
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (1)
src/views/PluginsView.tsx (1)
26-28: Share the installed-status predicate.This exact status list is duplicated in
src/views/PluginsView/PluginStoreRow.tsxasisInstalledLike(). The two copies already drive different UI paths here and in the row, so the next status change can desync header counts from row actions. Please extract one shared helper and reuse it in both places.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/views/PluginsView.tsx` around lines 26 - 28, The installed-status predicate is duplicated between isInstalled (in PluginsView) and isInstalledLike (in PluginStoreRow); extract a single exported helper (e.g., export function isPluginInstalled(status: string): boolean) in a shared module (or promote one of the existing functions to a shared export) that implements the status check for "installed" | "update_available" | "downgrade", then replace both isInstalled and isInstalledLike usages to call the new isPluginInstalled helper so header counts and row actions use the same logic.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/views/PluginsView/PluginStoreRow.tsx`:
- Around line 109-137: The dropdown still shows a Disable action even though the
store DTO has no persistent disabled state; update PluginStoreRow to stop
rendering the disable item unless a real disabled flag exists or you implement
local optimistic state: either remove the onDisable menu branch (the conditional
rendering around DropdownMenuItem that uses onDisable and calls
onDisable(entry.name)) or change the condition to require a persistent flag on
entry (e.g., entry.disabled) and only show it when that flag is present, or
alternatively implement local/optimistic disabled state in PluginStoreRow that
flips on select and persists visually until PluginsView.tsx refetch resolves;
modify the rendering around onDisable, the onSelect handler that calls
onDisable(entry.name), and any toast/invalidations accordingly.
- Around line 68-73: The category is rendered raw in PluginStoreRow.tsx as
{entry.category}; resolve it through the i18n translator instead by calling t
with the key built from the category slug (e.g.
"plugins.categories.<entry.category>") and a safe fallback to entry.category
when the translation is missing; update the JSX in the component (where
entry.description/entry.category/entry.author are rendered) to use that t(...)
call so category labels show localized values for users.
---
Nitpick comments:
In `@src/views/PluginsView.tsx`:
- Around line 26-28: The installed-status predicate is duplicated between
isInstalled (in PluginsView) and isInstalledLike (in PluginStoreRow); extract a
single exported helper (e.g., export function isPluginInstalled(status: string):
boolean) in a shared module (or promote one of the existing functions to a
shared export) that implements the status check for "installed" |
"update_available" | "downgrade", then replace both isInstalled and
isInstalledLike usages to call the new isPluginInstalled helper so header counts
and row actions use the same logic.
🪄 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: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: f168c5ff-5c42-4505-8364-6da8e1a75737
📒 Files selected for processing (7)
src/i18n/locales/en.jsonsrc/i18n/locales/fr.jsonsrc/views/PluginsView.tsxsrc/views/PluginsView/PluginStoreRow.tsxsrc/views/PluginsView/PluginsHeader.tsxsrc/views/PluginsView/__tests__/PluginStoreRow.test.tsxsrc/views/PluginsView/__tests__/PluginsHeader.test.tsx
✅ Files skipped from review due to trivial changes (2)
- src/i18n/locales/en.json
- src/i18n/locales/fr.json
🚧 Files skipped from review as they are similar to previous changes (2)
- src/views/PluginsView/tests/PluginsHeader.test.tsx
- src/views/PluginsView/PluginsHeader.tsx
- Translate the inline category label in PluginStoreRow through
t("plugins.categories.<slug>") so French users see "Crawlers"
instead of the raw "crawler" slug. Falls back to the slug when no
translation exists.
- Drop the Disable menu item and its plumbing. With the store DTO
reporting no disabled state, clicking it was a no-op beyond the
toast. Keep plugin_disable wiring out of the UI until the DTO
grows a matching field. Removes the disable i18n keys
(action.disable, toast.disableSuccess) and the onDisable prop.
There was a problem hiding this comment.
🧹 Nitpick comments (2)
src/views/PluginsView.tsx (2)
26-28: Code duplication:isInstalledduplicatesisInstalledLikefrom PluginStoreRow.Both
isInstalledhere andisInstalledLikeinPluginStoreRow.tsx(Lines 29-31) implement identical logic. Consider extracting this to a shared utility to avoid drift.♻️ Suggested extraction
Create a shared utility, e.g., in
src/views/PluginsView/utils.ts:import type { PluginStoreEntry } from "@/types/plugin-store"; export function isInstalledLike(status: PluginStoreEntry["status"]): boolean { return status === "installed" || status === "update_available" || status === "downgrade"; }Then import and use it in both files.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/views/PluginsView.tsx` around lines 26 - 28, The isInstalled function in PluginsView duplicates the logic of isInstalledLike in PluginStoreRow; extract the shared logic into a single utility function (e.g., export function isInstalledLike(status: PluginStoreEntry["status"]) { ... }) and replace both the existing isInstalled in PluginsView and isInstalledLike in PluginStoreRow to import and call that shared utility; update imports in PluginsView and PluginStoreRow to use the new exported function and remove the duplicate implementation.
47-50: Consider adding error handling for the uninstall mutation.The mutation has
onSuccessbut noonErrorcallback. If uninstall fails, users won't receive feedback.♻️ Suggested fix
const uninstallMutation = useTauriMutation<void, { name: string }>("plugin_uninstall", { invalidateKeys: STORE_INVALIDATE_KEYS, onSuccess: () => toast.success(t("plugins.toast.uninstallSuccess")), + onError: () => toast.error(t("plugins.toast.uninstallError")), });Note: This requires adding
plugins.toast.uninstallErrorto the i18n locale files.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/views/PluginsView.tsx` around lines 47 - 50, The uninstallMutation created with useTauriMutation (named uninstallMutation) lacks an onError handler so failures produce no user feedback; add an onError callback to the mutation options that calls toast.error(t("plugins.toast.uninstallError")) and optionally logs the error, keeping existing invalidateKeys/STORE_INVALIDATE_KEYS and onSuccess intact, and add the plugins.toast.uninstallError key to the i18n locale files.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@src/views/PluginsView.tsx`:
- Around line 26-28: The isInstalled function in PluginsView duplicates the
logic of isInstalledLike in PluginStoreRow; extract the shared logic into a
single utility function (e.g., export function isInstalledLike(status:
PluginStoreEntry["status"]) { ... }) and replace both the existing isInstalled
in PluginsView and isInstalledLike in PluginStoreRow to import and call that
shared utility; update imports in PluginsView and PluginStoreRow to use the new
exported function and remove the duplicate implementation.
- Around line 47-50: The uninstallMutation created with useTauriMutation (named
uninstallMutation) lacks an onError handler so failures produce no user
feedback; add an onError callback to the mutation options that calls
toast.error(t("plugins.toast.uninstallError")) and optionally logs the error,
keeping existing invalidateKeys/STORE_INVALIDATE_KEYS and onSuccess intact, and
add the plugins.toast.uninstallError key to the i18n locale files.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: e33f09e7-261d-43a2-a95b-55a91c450848
📒 Files selected for processing (5)
src/i18n/locales/en.jsonsrc/i18n/locales/fr.jsonsrc/views/PluginsView.tsxsrc/views/PluginsView/PluginStoreRow.tsxsrc/views/PluginsView/__tests__/PluginStoreRow.test.tsx
✅ Files skipped from review due to trivial changes (1)
- src/views/PluginsView/tests/PluginStoreRow.test.tsx
🚧 Files skipped from review as they are similar to previous changes (2)
- src/i18n/locales/en.json
- src/i18n/locales/fr.json
There was a problem hiding this comment.
1 issue found across 5 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="src/views/PluginsView/PluginStoreRow.tsx">
<violation number="1" location="src/views/PluginsView/PluginStoreRow.tsx:109">
P1: Installed plugin rows no longer expose a disable action, so users cannot trigger plugin disable from the catalog UI.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
cubic-dev-ai flagged that removing the disable menu item in 831ceb6 regressed a feature users had before the PR. The original catalog had a "Désactiver" button that called plugin_disable. Add it back to the kebab menu, alongside uninstall. The UX caveat raised earlier (no persistent visual state because the DTO has no "disabled" field) is documented as a comment next to the mutation declaration — it's an architectural gap that needs a backend change, not something to fix by hiding the feature.
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (2)
src/views/PluginsView/__tests__/PluginStoreRow.test.tsx (2)
33-37: Test name and assertions are slightly out of sync.Line 33 says category is verified, but Lines 35–36 only assert name/description. Either rename the test or add a category assertion.
Proposed tidy-up
- it("renders the plugin name, description and category", () => { + it("renders the plugin name and description", () => {🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/views/PluginsView/__tests__/PluginStoreRow.test.tsx` around lines 33 - 37, The test titled "renders the plugin name, description and category" is only asserting the name and description; update the test to either rename it to accurately reflect what's asserted (e.g., "renders the plugin name and description") or add an assertion for the category by using the same helpers as the existing assertions (e.g., call renderRow() and add a screen.getByText(...) for the category string to match how name/description are checked). Ensure you update the test title string and/or include the category assertion near the existing expect calls (renderRow, screen.getByText).
39-42: Make this locale-sensitive assertion deterministic.Line 41 hardcodes English (
Crawlers). If test language is switched to French in shared state, this can become flaky. Pin language toenin this suite (or assert using locale-agnostic strategy).Example stabilization
-import { describe, it, expect, vi } from "vitest"; +import { describe, it, expect, vi, beforeEach } from "vitest"; @@ describe("PluginStoreRow", () => { + beforeEach(() => { + window.localStorage.setItem("i18nextLng", "en"); + }); + it("renders the plugin name, description and category", () => {🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/views/PluginsView/__tests__/PluginStoreRow.test.tsx` around lines 39 - 42, The test currently asserts a hardcoded English label ("Crawlers") which makes it flaky when the shared i18n language changes; update the test suite to pin the locale to English before rendering (e.g., set the i18n language to 'en' in the beforeEach or the test) or change the assertion to a locale-agnostic check (e.g., assert on the category key or data attribute instead of the translated string); locate the assertion in PluginStoreRow.test.tsx that calls renderRow({ category: "crawler" }) and replace the hardcoded-text expectation with either a pinned i18n setup or an assertion that does not depend on the translated label.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/views/PluginsView/__tests__/PluginStoreRow.test.tsx`:
- Around line 95-96: The French matcher in PluginStoreRow.test.tsx uses an ASCII
apostrophe in the regex passed to screen.getByRole to match "plus d'actions" and
similarly for the disable menuitem; update those regexes to accept both ASCII
(') and typographic (’) apostrophes (e.g. use a character class like ['\u2019]
or an alternation) so the queries still match translations using ’; apply the
same fix to the second occurrence around the menuitem disable matcher.
---
Nitpick comments:
In `@src/views/PluginsView/__tests__/PluginStoreRow.test.tsx`:
- Around line 33-37: The test titled "renders the plugin name, description and
category" is only asserting the name and description; update the test to either
rename it to accurately reflect what's asserted (e.g., "renders the plugin name
and description") or add an assertion for the category by using the same helpers
as the existing assertions (e.g., call renderRow() and add a
screen.getByText(...) for the category string to match how name/description are
checked). Ensure you update the test title string and/or include the category
assertion near the existing expect calls (renderRow, screen.getByText).
- Around line 39-42: The test currently asserts a hardcoded English label
("Crawlers") which makes it flaky when the shared i18n language changes; update
the test suite to pin the locale to English before rendering (e.g., set the i18n
language to 'en' in the beforeEach or the test) or change the assertion to a
locale-agnostic check (e.g., assert on the category key or data attribute
instead of the translated string); locate the assertion in
PluginStoreRow.test.tsx that calls renderRow({ category: "crawler" }) and
replace the hardcoded-text expectation with either a pinned i18n setup or an
assertion that does not depend on the translated label.
🪄 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: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 986283ae-4269-43fb-a0bc-623bba28ae0c
📒 Files selected for processing (5)
src/i18n/locales/en.jsonsrc/i18n/locales/fr.jsonsrc/views/PluginsView.tsxsrc/views/PluginsView/PluginStoreRow.tsxsrc/views/PluginsView/__tests__/PluginStoreRow.test.tsx
🚧 Files skipped from review as they are similar to previous changes (4)
- src/i18n/locales/en.json
- src/views/PluginsView/PluginStoreRow.tsx
- src/i18n/locales/fr.json
- src/views/PluginsView.tsx
There was a problem hiding this comment.
1 issue found across 5 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="src/views/PluginsView.tsx">
<violation number="1" location="src/views/PluginsView.tsx:139">
P2: The new `onDisable` wiring exposes a disable action that cannot be persisted in UI state with the current DTO, so users can trigger disable but still see the plugin as installed after refetch.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
cubic-dev-ai flagged (again) that the Disable action has no UI reflect because PluginStoreEntryDto lacks a disabled variant. Instead of hiding the feature, carry the state on the frontend: - PluginsView keeps a session-local Set<string> of disabled names. - disableMutation.onSuccess adds the name; enableMutation.onSuccess removes it. - PluginStoreRow accepts isLocallyDisabled + onEnable. When set, the row renders dimmed, shows an "inactive" badge, and the kebab swaps "Disable" for "Enable". - enabledCount in the header excludes locally-disabled plugins so the counter matches what the user just did. The state doesn't persist across reloads — that's on purpose, same as pre-PR behaviour. When the DTO grows a disabled field we can replace the Set with a derived value. Also fixes the French matcher regex in the row tests to accept the typographic apostrophe (coderabbit).
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/views/PluginsView.tsx`:
- Around line 78-81: The uninstallMutation's onSuccess currently only shows a
toast but doesn't clear the in-memory locallyDisabled state, leaving a plugin
marked inactive after reinstall; update the onSuccess handler of
uninstallMutation to also remove/clear the plugin's entry from the
locallyDisabled state (the state variable/collection tracking disabled plugins)
for the uninstalled plugin (use the passed { name } argument) so the UI and
action state are reconciled when uninstall succeeds.
🪄 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: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 14655a18-e62d-470f-a7cd-171739d04043
📒 Files selected for processing (5)
src/i18n/locales/en.jsonsrc/i18n/locales/fr.jsonsrc/views/PluginsView.tsxsrc/views/PluginsView/PluginStoreRow.tsxsrc/views/PluginsView/__tests__/PluginStoreRow.test.tsx
🚧 Files skipped from review as they are similar to previous changes (4)
- src/i18n/locales/fr.json
- src/views/PluginsView/PluginStoreRow.tsx
- src/i18n/locales/en.json
- src/views/PluginsView/tests/PluginStoreRow.test.tsx
There was a problem hiding this comment.
1 issue found across 5 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="src/views/PluginsView.tsx">
<violation number="1" location="src/views/PluginsView.tsx:40">
P2: `locallyDisabled` state is not cleared when a plugin is uninstalled. If a user disables a plugin and then uninstalls it, the name persists in the `locallyDisabled` set. Reinstalling the same plugin will incorrectly render it as inactive. The `uninstallMutation.onSuccess` handler should remove the plugin name from `locallyDisabled`.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
Uninstalling a plugin removed it from the store but kept its name in the session-local disabled set. Reinstalling then rendered the row as inactive until the user reloaded the view.
Summary
Refactor the plugins catalogue page to match the mockup design while keeping every existing Tauri IPC command behind it unchanged.
PluginsHeader— title, enabled/disabled counters, "Check updates" actionPluginsToolbar— segmented category pills replacing the Select dropdown, inline searchplugin_disable)InstallbuttonUninstallaction moved to a kebab menu (reduces noise)issue30-ui-frupdated to match the new French copyTest plan
npx vitest run— 389/389 greennpx oxlint src/views/PluginsView src/views/PluginsView.tsx src/i18n— cleannpx tsc --noEmit— clean/plugins, confirm header / grouped sections renderplugin_disableInstallon a not-installed plugin → catalogue row refreshesUninstalltriggersplugin_uninstallfr→ every label translatedSummary by cubic
Redesigned the Plugins catalog to match the mockup, with a compact header, segmented filter, grouped sections, and streamlined row actions. Adds an optimistic Disable/Enable flow that dims rows and updates the enabled counter; IPC mutations remain unchanged.
New Features
crawler/extractor).en/fr: title, stats, categories, actions (incl. Enable), search, empty/error/loading/retry, badges (“official”, “inactive”), toasts (incl. enable success); pluralization uses_one/_other.PluginStoreRow,PluginsHeader,PluginsToolbar, andgroupByCategory.Bug Fixes
Written for commit 891a113. Summary will update on new commits.
Summary by CodeRabbit
New Features
UX
Localization
Tests
Documentation