Skip to content

feat(bin-designer): organize saved designs with tags, filtering, and bulk actions#1939

Merged
andymai merged 5 commits into
mainfrom
feat/designs-manager-rebuild
May 30, 2026
Merged

feat(bin-designer): organize saved designs with tags, filtering, and bulk actions#1939
andymai merged 5 commits into
mainfrom
feat/designs-manager-rebuild

Conversation

@andymai
Copy link
Copy Markdown
Owner

@andymai andymai commented May 29, 2026

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-lg single-column dialog didn't keep up.

Stage 2 of 3 (builds on the synced tags field from #1936):

  1. Tags foundationfeat(bin-designer): add synced organization tags to saved designs #1936
  2. Designs-manager rebuildthis PR
  3. Header thumbnail quick-switch + management-only layout modal

Changes

  • Wider surfacemax-w-lgmax-w-4xl with a denser auto-fill thumbnail grid (≈5 columns on desktop), so 10–70 designs are scannable.
  • Tags on cards — each design shows its tag chips; an Edit tags action opens a tag editor (chips + add field) backed by updateDesignTags.
  • Tag filter bar — chip toggles above the list (AND-match, with Clear filters); hidden when no design has tags.
  • Bulk-selection mode — a Select toggle reveals checkboxes; the bulk bar can Delete (single confirm with count), Export, or Tag a whole batch.

New, each unit-tested

useDesignSelection reducer · tagFilter (collect/filter) utils · DesignTagChips · TagInput · TagEditDialog · TagFilterBar · BulkActionBar. Existing items/dialog/actions extended (chips, selection, edit-tags) with their tests updated.

Verification

  • Unit/component: 91 tests across the bin-designer manager (logic + rendering).
  • i18n: 23 keys added to all 9 locales; check:i18n (keys/interpolation/values/unused) green.
  • Playwright visual spot-check at desktop + tablet — normal grid, tag-filtered view, and bulk-selection mode all render correctly (screenshots below).
  • typecheck ✅ · lint ✅ · knip ✅ · design-system ✅

Screenshots

Normal (wider grid + chips + filter bar) Filtered by kitchen Bulk-selection mode
attached in review attached in review attached in review

Notes

  • Bulk Tag adds the entered tags to each selected design (union); per-design removal is via the single-design editor. Bulk remove can follow if needed.

…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.
Copilot AI review requested due to automatic review settings May 29, 2026 22:40
@vercel
Copy link
Copy Markdown

vercel Bot commented May 29, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
gridfinity-layout-tool Ready Ready Preview, Comment May 30, 2026 12:07am

Request Review

@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented May 29, 2026

Greptile Summary

Rebuilds the saved-designs manager with a wider dialog (max-w-4xl), per-design tag chips, a tag filter bar (AND-match), and a bulk-selection mode supporting delete, export, and tag operations. The new components are well-decomposed, fully i18n'd across all 9 locales, and backed by 91 unit/component tests.

  • New utilities & hook: tagFilter.ts (collect/filter/toggle), tags.ts adds tagsEqual, and useDesignSelection (reducer + hook) — all pure, well-tested, and free of side-effects.
  • New UI components: TagInput, TagEditDialog, TagFilterBar, BulkActionBar, DesignTagChips — each small, self-contained, and symmetrically applied to both grid and list views.
  • DesignListDialog orchestration: Two render-time state mutations (setActiveTags / selection.prune) are used to prune stale filters and selection after deletes; these are React-valid but diverge from the useEffect idiom. The bulk-tag path also exits silently on total storage failure with no error feedback to the user.

Confidence Score: 5/5

Safe 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

Filename Overview
src/features/bin-designer/components/DesignListDialog/DesignListDialog.tsx Main manager rebuilt with tag filtering, bulk selection, and tag editing. Two render-time state mutations (setActiveTags, selection.prune) are React-supported but not idiomatic; bulk-tag error path is silent on total failure.
src/features/bin-designer/components/DesignListDialog/useDesignSelection.ts Clean reducer + hook exposing a stable DesignSelection API; well-tested, ENTER/EXIT/TOGGLE/SELECT_ALL/PRUNE actions all look correct.
src/features/bin-designer/utils/tagFilter.ts Pure utility functions for tag collection, AND-filtering, and toggle; case-insensitive throughout and fully covered by the companion test file.
src/features/bin-designer/components/DesignListDialog/TagInput/TagInput.tsx Controlled tag editor with Enter/comma commit, Backspace-to-remove-last, and onBlur auto-commit; correctly delegates normalization/dedup to normalizeTags from the storage layer.
src/features/bin-designer/components/DesignListDialog/BulkActionBar/BulkActionBar.tsx Straightforward bulk action bar; bulk actions correctly disabled when count === 0, all strings use i18n keys.
src/features/bin-designer/components/DesignGridItem/DesignGridItem.tsx Extended with selection checkbox, tag chips, and onEditTags; activate() correctly dispatches to toggle or load depending on mode.

Fix All in Claude Code

Prompt To Fix All With AI
Fix 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

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +148 to +149
// Union of every tag across designs, for the filter bar.
const allTags = useMemo(() => collectTags(designs), [designs]);
Comment on lines +296 to +317
} 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)
@andymai
Copy link
Copy Markdown
Owner Author

andymai commented May 29, 2026

Thanks for the review! All four points addressed in 14062ae:

  1. Bulk-delete partial failure (Greptile): handleBulkDelete now removes only the IDs that actually deleted, so a partial storage error can't drop a still-present design from the UI.
  2. Stale tag filter (Copilot): active filters whose tag no longer exists are pruned, so the list can't get stuck empty while the (hidden) filter bar offers no clear path.
  3. No-op tag write (Copilot): single + bulk tag saves skip designs whose tag set doesn't change, avoiding a needless updatedAt bump + sync (new tested tagsEqual helper).
  4. Untranslated close label (Copilot): TagEditDialog now passes t('common.closeDialog') to Dialog.Header.

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.
@andymai andymai merged commit 86feab6 into main May 30, 2026
15 checks passed
@andymai andymai deleted the feat/designs-manager-rebuild branch May 30, 2026 00:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants