feat(bin-designer): organize saved designs with tags, filtering, and bulk actions#1939
Conversation
…bulk actions Rebuilds the saved-designs manager so it scales with the power-user tail (production design counts reach p95=15, max 69 per user) instead of the cramped single-column list. - Wider dialog (max-w-lg → max-w-4xl) with a denser auto-fill thumbnail grid - Per-design tag chips on cards; "Edit tags" action opens a tag editor (single design) backed by updateDesignTags - Tag filter bar (chip toggles, AND-match, clear-filters) above the list - Bulk-selection mode: multi-select to Delete (single confirm), Export, or Tag a batch at once New pieces, each unit-tested: useDesignSelection reducer, tagFilter (collect/filter) utils, DesignTagChips, TagInput, TagEditDialog, TagFilterBar, BulkActionBar. Tags reuse the synced field added in #1936. i18n added across all 9 locales. Verified visually (normal / filtered / selection) via Playwright.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Greptile SummaryRebuilds the saved-designs manager with a wider dialog (
Confidence Score: 5/5Safe to merge — all new functionality is additive UI work with no domain coordinate math, storage-layer changes, or mutation of existing data paths. The change is purely UI: new components, a pure-function utility layer, and wiring in DesignListDialog. The two observations (render-body state sync and silent bulk-tag failure) are both non-blocking quality notes; no functional defects were found in the logic paths. src/features/bin-designer/components/DesignListDialog/DesignListDialog.tsx — hosts both the render-time prune calls and the bulk-tag error-handling gap. Important Files Changed
Prompt To Fix All With AIFix the following 2 code review issues. Work through them one at a time, proposing concise fixes.
---
### Issue 1 of 2
src/features/bin-designer/components/DesignListDialog/DesignListDialog.tsx:153-167
**Render-time state dispatch**
Both `setActiveTags` and `selection.prune` are called unconditionally in the render body on every render pass. React supports render-time `setState`/`dispatch` for deriving state, but the standard guidance is to use `useEffect` for these synchronizations so the intent is explicit and the dependency set is auditable. An alternative is to compute `effectiveActiveTags` as a `useMemo` (no state write needed) and pass that derived value to both `filterByTags` and `TagFilterBar`, keeping `activeTags` as the raw user intent. For the prune call, a `useEffect` on `[designs]` would be the idiomatic choice.
### Issue 2 of 2
src/features/bin-designer/components/DesignListDialog/DesignListDialog.tsx:483-505
**Silent failure in bulk-tag path**
When every `updateDesignTags` call fails (e.g. offline, storage error), `updated.size` stays 0, no toast fires, the dialog closes, and selection exits — giving the user no indication that the operation failed. Compare this with `handleDuplicate`, which shows an explicit error toast on failure. A simple `else` branch after the `if (updated.size > 0)` block to show an error or partial-failure toast would close the gap.
Reviews (5): Last reviewed commit: "style(bin-designer): bottom-align design..." | Re-trigger Greptile |
There was a problem hiding this comment.
Pull request overview
This PR rebuilds the Bin Designer saved-designs manager to better support larger saved-design libraries with tag organization, filtering, and bulk workflows.
Changes:
- Adds tag collection/filtering utilities and tag UI components for chips, editing, and filter bars.
- Extends design grid/list items and actions with tag display, tag editing, and bulk-selection support.
- Adds bulk action controls for selecting, deleting, exporting, and tagging multiple designs, plus i18n strings across locales.
Reviewed changes
Copilot reviewed 38 out of 38 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
src/features/bin-designer/components/DesignActions/DesignActions.tsx |
Adds an Edit tags menu action. |
src/features/bin-designer/components/DesignActions/DesignActions.test.tsx |
Covers the new Edit tags action. |
src/features/bin-designer/components/DesignGridItem/DesignGridItem.tsx |
Adds tag chips and bulk-selection behavior to grid cards. |
src/features/bin-designer/components/DesignGridItem/DesignGridItem.test.tsx |
Updates grid item test props. |
src/features/bin-designer/components/DesignListDialog/BulkActionBar/BulkActionBar.tsx |
Adds bulk action controls. |
src/features/bin-designer/components/DesignListDialog/BulkActionBar/BulkActionBar.test.tsx |
Tests bulk action bar behavior. |
src/features/bin-designer/components/DesignListDialog/BulkActionBar/index.ts |
Exports bulk action bar. |
src/features/bin-designer/components/DesignListDialog/DesignListDialog.tsx |
Wires tag filtering/editing, bulk actions, and wider dialog layout. |
src/features/bin-designer/components/DesignListDialog/TagEditDialog/TagEditDialog.tsx |
Adds modal tag editor. |
src/features/bin-designer/components/DesignListDialog/TagEditDialog/TagEditDialog.test.tsx |
Tests tag editor dialog behavior. |
src/features/bin-designer/components/DesignListDialog/TagEditDialog/index.ts |
Exports tag editor dialog. |
src/features/bin-designer/components/DesignListDialog/TagFilterBar/TagFilterBar.tsx |
Adds tag filter chip bar. |
src/features/bin-designer/components/DesignListDialog/TagFilterBar/TagFilterBar.test.tsx |
Tests tag filter bar behavior. |
src/features/bin-designer/components/DesignListDialog/TagFilterBar/index.ts |
Exports tag filter bar. |
src/features/bin-designer/components/DesignListDialog/TagInput/TagInput.tsx |
Adds controlled tag input/chip editor. |
src/features/bin-designer/components/DesignListDialog/TagInput/TagInput.test.tsx |
Tests tag input behavior. |
src/features/bin-designer/components/DesignListDialog/TagInput/index.ts |
Exports tag input. |
src/features/bin-designer/components/DesignListDialog/useDesignSelection.ts |
Adds reducer hook for bulk-selection state. |
src/features/bin-designer/components/DesignListDialog/useDesignSelection.test.ts |
Tests selection reducer behavior. |
src/features/bin-designer/components/DesignListItem/DesignListItem.tsx |
Adds tag chips and bulk-selection behavior to list rows. |
src/features/bin-designer/components/DesignListItem/DesignListItem.test.tsx |
Updates list item test props. |
src/features/bin-designer/components/DesignTagChips/DesignTagChips.tsx |
Adds read-only tag chip display. |
src/features/bin-designer/components/DesignTagChips/DesignTagChips.test.tsx |
Tests tag chip rendering. |
src/features/bin-designer/components/DesignTagChips/index.ts |
Exports tag chips. |
src/features/bin-designer/utils/tagFilter.ts |
Adds tag collection and AND-filtering utilities. |
src/features/bin-designer/utils/tagFilter.test.ts |
Tests tag utility behavior. |
src/i18n/locales/de.json |
Adds German strings for tags and bulk actions. |
src/i18n/locales/en.json |
Adds English runtime strings for tags and bulk actions. |
src/i18n/locales/en.ts |
Adds canonical English strings for tags and bulk actions. |
src/i18n/locales/es.json |
Adds Spanish strings for tags and bulk actions. |
src/i18n/locales/fr.json |
Adds French strings for tags and bulk actions. |
src/i18n/locales/ja.json |
Adds Japanese strings for tags and bulk actions. |
src/i18n/locales/nb.json |
Adds Norwegian Bokmål strings for tags and bulk actions. |
src/i18n/locales/nl.json |
Adds Dutch strings for tags and bulk actions. |
src/i18n/locales/pt-BR.json |
Adds Brazilian Portuguese strings for tags and bulk actions. |
src/i18n/locales/sv.json |
Adds Swedish strings for tags and bulk actions. |
src/i18n/locales/uk.json |
Adds Ukrainian strings for tags and bulk actions. |
scripts/design-system-allowlist.txt |
Allowlists new dialog subcomponents with raw element usage. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // Union of every tag across designs, for the filter bar. | ||
| const allTags = useMemo(() => collectTags(designs), [designs]); |
| } else if (edit.mode === 'bulk') { | ||
| const ids = selection.selectedIds; | ||
| const targets = designs.filter((d) => ids.has(d.id)); | ||
| const updated = new Map<string, SavedDesign>(); | ||
| for (const d of targets) { | ||
| // Bulk = add the entered tags to each design's existing set (union). | ||
| const result = await updateDesignTags( | ||
| d.id, | ||
| normalizeTags([...(d.tags ?? []), ...rawTags]) | ||
| ); | ||
| if (isOk(result)) updated.set(result.value.id, result.value); | ||
| } | ||
| if (updated.size > 0) { | ||
| setDesigns((prev) => prev.map((d) => updated.get(d.id) ?? d)); | ||
| addToast({ | ||
| message: t('binDesigner.bulk.toastTagged', { count: updated.size }), | ||
| type: 'success', | ||
| duration: 2000, | ||
| }); | ||
| } | ||
| selection.exit(); | ||
| } |
|
|
||
| return ( | ||
| <Dialog.Root open={open} onClose={onClose} size="sm"> | ||
| <Dialog.Header title={title} /> |
- handleBulkDelete: remove only successfully-deleted IDs from the list so a partial storage failure can't drop a still-present design from the UI (Greptile) - prune active tag filters whose tag no longer exists, so the list can't get stuck showing nothing while the (hidden) filter bar offers no way to clear it - skip the tag write (updatedAt bump + sync) when a single/bulk tag edit doesn't actually change a design's tag set (new tagsEqual helper, tested) - TagEditDialog: pass a localized close-button aria-label (common.closeDialog)
|
Thanks for the review! All four points addressed in 14062ae:
typecheck ✅ · lint ✅ · 51 affected tests ✅ |
- wire the previously-dead useDesignSelection.prune: drop selected IDs for designs removed via the row menu while in selection mode, so the count and bulk actions stay accurate - drop unused selection API (CLEAR action, enter() id-seeding) — nothing in the UI used them and they can't be caught by knip as object properties - extract toggleTag() helper for the case-insensitive filter chip toggle (was an inline ternary in JSX); tested - remove restatement/section-divider comments and the redundant DesignTagChips container aria-label; keep only WHY comments
Pin the date/actions row to the card bottom (mt-auto) so dates line up across a grid row regardless of how many tags each card shows — cards with fewer tags were floating their date higher, breaking the row's visual baseline.
What & why
Rebuilds the saved-designs manager so it scales with the power-user tail. Production cloud-sync data shows design counts are long-tailed — median 1, but p90=10, p95=15, max 69 per user — and the old
max-w-lgsingle-column dialog didn't keep up.Stage 2 of 3 (builds on the synced
tagsfield from #1936):Tags foundation✅ feat(bin-designer): add synced organization tags to saved designs #1936Changes
max-w-lg→max-w-4xlwith a denser auto-fill thumbnail grid (≈5 columns on desktop), so 10–70 designs are scannable.updateDesignTags.New, each unit-tested
useDesignSelectionreducer ·tagFilter(collect/filter) utils ·DesignTagChips·TagInput·TagEditDialog·TagFilterBar·BulkActionBar. Existing items/dialog/actions extended (chips, selection, edit-tags) with their tests updated.Verification
check:i18n(keys/interpolation/values/unused) green.Screenshots
kitchenNotes