Skip to content

include/exclude quickly identity from wave group#1544

Merged
simo6529 merged 64 commits intomainfrom
block-add-identity-to-wave
Oct 27, 2025
Merged

include/exclude quickly identity from wave group#1544
simo6529 merged 64 commits intomainfrom
block-add-identity-to-wave

Conversation

@simo6529
Copy link
Copy Markdown
Collaborator

@simo6529 simo6529 commented Oct 14, 2025

Summary by CodeRabbit

  • New Features

    • Menu-driven group editing with Include/Exclude identity modals and customizable edit/remove triggers.
    • New compact overflow menu for tabs and improved tab overflow UX.
    • Scope-aware wave permissions surfaced for view/drop/vote/chat.
  • Improvements

    • Identity search: debounced typing, keyboard navigation, ARIA/listbox support, highlighting, optional autofocus.
    • Wallet handling: normalization, deduplication, clearer FontAwesome icons.
    • Consolidated group create/test/publish flow with stronger validation and clearer feedback.
  • Bug Fixes

    • Tightened close/cancel behavior and keyboard/accessibility fixes for menus and modals.

Signed-off-by: Simo <simo@6529.io>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Oct 14, 2025

Walkthrough

Adds new group mutation services/hooks, a wave-scope permissions hook, a CompactMenu system with focus helpers, a controller-driven wave-group identity management flow (modals, forwardRef triggers, onEdit → onWaveUpdate), profile/wallet utilities, UI refactors (menus, overflow, dropdowns), many test updates, and codex ticket docs.

Changes

Cohort / File(s) Summary
Hooks: wave & group
\hooks/waves/useWaveScopePermissions.ts`, `hooks/groups/useGroupMutations.ts``
Add useWaveScopePermissions (wave scope summaries, scopes per view/drop/vote/chat, admin/eligibility flags) and useGroupMutations (submit/runTest/updateVisibility, types, re-exports for limits/validation).
Group mutation service
\services/groups/groupMutations.ts``
New group mutation utilities: sanitise, validate (include/exclude limits, NO_FILTERS, range checks), createGroup, publishGroup, hideGroup, toErrorMessage, and export constants/types (GROUP_INCLUDE_LIMIT, GROUP_EXCLUDE_LIMIT, ValidationIssue/Result).
Wave-group controller & utils
\components/waves/specs/groups/group/edit/buttons/hooks/useWaveGroupEditButtonsController.ts`, `components/waves/specs/groups/group/edit/buttons/utils/waveGroupEdit.ts`, `components/waves/specs/groups/group/edit/utils/waveGroupUpdate.ts``
New controller hook orchestrating include/exclude lifecycle (create/publish/poll/validate), and utilities to build/clear/read update bodies plus immutable update/get helpers for nested group_id paths.
Wave-group UI & modals
\components/waves/specs/groups/group/edit/**`` (multiple files)`
Menu-driven UI refactor: forwardRef edit/remove buttons exposing handles and optional renderTrigger, prop rename onEditonWaveUpdate (with optional opts), new WaveGroupEditMenu, WaveGroupManageIdentitiesModal, WaveGroupManageIdentitiesModals, WaveGroupEditButtons props and wiring.
CompactMenu system
\components/compact-menu/**`, `components/common/CompactMenu.tsx``
New CompactMenu component, types, constants, subcomponents, and focus hook (useCompactMenuFocus); re-exported for common usage.
TabToggle overflow & i18n
\components/common/TabToggleWithOverflow.tsx`, `i18n/messages.ts``
Replace inline overflow with CompactMenu-based overflow, add OverflowTrigger, keyboard/focus changes, TabOption adds label, add i18n messages for overflow.
Identity / profile search
\components/utils/input/identity/IdentitySearch.tsx`, `components/utils/input/profile-search/*.tsx`, `components/utils/input/profile-search/getSelectableIdentity.ts``
Add autoFocus prop, stable IDs, highlighted-index plumbing, keyboard navigation, selectProfile helper, ARIA/listbox improvements, new getSelectableIdentity util, and FontAwesome icon usage.
Group create flows & wallets
\components/groups/page/create/**`, `helpers/WalletHelpers.ts``
Switch create/test flows to useGroupMutations, fetch original excluded wallets, add wallet helpers (normaliseWalletList, walletListsMatch, dedupeWallets), and replace inline SVGs with FontAwesome icons.
Group visibility & deletion
\components/groups/page/list/card/actions/delete/GroupCardDeleteModal.tsx``, tests
Replace per-request useMutation with useGroupMutations.updateVisibility, update concurrency/loading/toast handling; tests adapted to new hook.
Wave group refactors & types
\components/waves/specs/groups/group/WaveGroup.tsx`, `components/waves/specs/groups/group/WaveGroup.types.ts`, `components/waves/groups/WaveGroups.tsx``
Move WaveGroupType to WaveGroup.types.ts, clarify WaveGroup props, add optional useRing?, adjust layout/overflow and imports.
Dropdown & menu wrappers
\components/utils/select/dropdown/CommonDropdown*.tsx``, tests
Simplify buttonPosition shape to { right }, add dynamic positioning (useLayoutEffect), refine outside-click handling; tests updated accordingly.
Helpers, tests & docs
\helpers/WalletHelpers.ts`, `tests/**`, `codex/STATE.md`, `codex/tickets/*.md``
Add wallet helpers, many tests updated for prop/hook/component renames and new hooks/components, and add codex STATE plus tickets TKT-0011..TKT-0014 documentation.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    actor User
    participant UI as WaveGroup UI / Menu
    participant Ctrl as useWaveGroupEditButtonsController
    participant Mut as useGroupMutations
    participant API
    participant Wave as Wave update handler

    User->>UI: open group options
    UI->>Ctrl: request include/exclude/change/remove
    alt Include/Exclude identity
        Ctrl->>UI: open identities modal
        User->>UI: select identity
        UI->>Ctrl: confirm identity
        Ctrl->>Mut: submit(payload)
        Mut->>API: createGroup / publishGroup
        API-->>Mut: success
        Mut-->>Ctrl: result.ok
        Ctrl->>Wave: onWaveUpdate(updateBody)
    else Change/Add group
        UI->>UI: open group editor (ref.open)
        User->>UI: choose group
        UI->>Wave: onWaveUpdate(updateBody)
    else Remove group
        UI->>Ctrl: confirm remove
        Ctrl->>Mut: updateVisibility(hide)
        Mut->>API: publish/hide
        API-->>Mut: success
        Mut-->>Ctrl: result
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Files/areas to inspect closely:

  • services/groups/groupMutations.ts — validation rules, sanitisation, error mapping, create/publish/hide implementations
  • hooks/groups/useGroupMutations.ts and useWaveGroupEditButtonsController — auth gating, abortable polling, mutation semantics, return shapes
  • ForwardRef components and updated callsites for prop rename (onEdit → onWaveUpdate) across wave group edit/remove/button files
  • CompactMenu accessibility/focus logic, useCompactMenuFocus, and TabToggleWithOverflow keyboard/focus handling
  • Tests updated to mock new hooks/components and to assert new prop/hook shapes

Possibly related PRs

Suggested reviewers

  • ragnep

Poem

🐰 I hopped through menus, hooks, and change,

I nudged the props and honed the range,
Identities tended, wallets trimmed neat,
Waves and menus now dance in beat,
The rabbit hops back — the code’s complete.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The PR title "include/exclude quickly identity from wave group" is directly related to the main changes in the changeset. The pull request introduces significant new functionality for managing wave group identities, including a new WaveGroupManageIdentitiesModal component, a useWaveGroupEditButtonsController hook for orchestrating identity inclusion/exclusion workflows, and updates to WaveGroupEditButtons to support these operations. The title accurately captures this core feature, though the phrasing is slightly awkward grammatically. The word "quickly" appears to convey the UX intent of making these identity management operations convenient and efficient. The title is specific enough that a teammate reviewing the commit history would understand the primary feature being added.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch block-add-identity-to-wave

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 57d0aa4 and 40fa0b9.

📒 Files selected for processing (1)
  • components/waves/specs/groups/group/edit/buttons/hooks/useWaveGroupEditButtonsController.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{ts,tsx}: Do not include any comments in the code
Use react-query for data fetching
Always add readonly before props

Use TypeScript across the codebase

Files:

  • components/waves/specs/groups/group/edit/buttons/hooks/useWaveGroupEditButtonsController.ts
🧬 Code graph analysis (1)
components/waves/specs/groups/group/edit/buttons/hooks/useWaveGroupEditButtonsController.ts (7)
services/api/common-api.ts (2)
  • commonApiFetch (18-45)
  • commonApiPost (166-191)
generated/models/ApiGroupFull.ts (1)
  • ApiGroupFull (17-85)
generated/models/ApiCreateGroup.ts (1)
  • ApiCreateGroup (16-49)
generated/models/ApiWave.ts (1)
  • ApiWave (27-177)
services/groups/groupMutations.ts (5)
  • ValidationIssue (8-15)
  • validateGroupPayload (52-134)
  • createGroup (136-153)
  • publishGroup (155-171)
  • toErrorMessage (22-30)
components/waves/specs/groups/group/edit/buttons/utils/waveGroupEdit.ts (4)
  • getScopedGroup (31-49)
  • isGroupAuthor (51-81)
  • getGroupIdFromUpdateBody (26-29)
  • buildWaveUpdateBody (17-24)
generated/models/ApiUpdateWaveRequest.ts (1)
  • ApiUpdateWaveRequest (21-95)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Analyze (javascript-typescript)

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
hooks/waves/useWaveScopePermissions.ts (1)

50-50: Simplify redundant ternary operator.

The ternary is redundant since both matchesId and matchesHandle already evaluate to false when hasGroup is false (lines 40 and 46).

Apply this diff to simplify:

-  const isGroupAuthor = hasGroup ? matchesId || matchesHandle : false;
+  const isGroupAuthor = matchesId || matchesHandle;
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0425519 and b2c4f3b.

📒 Files selected for processing (1)
  • hooks/waves/useWaveScopePermissions.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{ts,tsx}: Do not include any comments in the code
Use react-query for data fetching
Always add readonly before props

**/*.{ts,tsx}: Use TypeScript for source code and follow existing code style and naming conventions
Adhere to clean code standards as measured by SonarQube

Files:

  • hooks/waves/useWaveScopePermissions.ts
🧬 Code graph analysis (1)
hooks/waves/useWaveScopePermissions.ts (6)
generated/models/ApiGroup.ts (1)
  • ApiGroup (16-70)
generated/models/ApiWave.ts (1)
  • ApiWave (27-177)
generated/models/ApiWaveScope.ts (1)
  • ApiWaveScope (19-38)
hooks/useWaveData.ts (1)
  • useWaveData (12-45)
contexts/wave/WaveEligibilityContext.tsx (1)
  • useWaveEligibility (24-37)
components/auth/Auth.tsx (1)
  • useAuth (95-97)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (1)
hooks/waves/useWaveScopePermissions.ts (1)

60-116: Hook implementation looks solid.

The hook correctly aggregates data from multiple sources (wave data, eligibility, auth), properly uses useMemo to avoid unnecessary recomputations, and returns a well-structured result. The dependency array for useMemo is accurate, containing only the values actually used in the memoized computation.

Comment thread hooks/waves/useWaveScopePermissions.ts
Signed-off-by: Simo <simo@6529.io>
Signed-off-by: Simo <simo@6529.io>
Signed-off-by: Simo <simo@6529.io>
Signed-off-by: Simo <simo@6529.io>
Signed-off-by: Simo <simo@6529.io>
Signed-off-by: Simo <simo@6529.io>
Signed-off-by: Simo <simo@6529.io>
Signed-off-by: Simo <simo@6529.io>
Signed-off-by: Simo <simo@6529.io>
Signed-off-by: Simo <simo@6529.io>
Signed-off-by: Simo <simo@6529.io>
Signed-off-by: Simo <simo@6529.io>
Signed-off-by: Simo <simo@6529.io>
Signed-off-by: Simo <simo@6529.io>
Signed-off-by: Simo <simo@6529.io>
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (4)
components/groups/page/create/config/wallets/GroupCreateWallets.tsx (1)

115-128: Final wallets effect misses deps and can emit redundant updates

When iAmIncluded, primary_wallet, or type change, setWallets isn’t recomputed until another state changes. Also, you always call setWallets even if the value is unchanged.

Apply:

-  useEffect(() => {
-    const uploaded = uploadedWallets ?? [];
-    const emma = emmaWallets ?? [];
-    const selected = selectedWallets ?? [];
-    const all = Array.from(new Set([...uploaded, ...emma, ...selected]));
-    if (
-      iAmIncluded &&
-      connectedProfile?.primary_wallet &&
-      type === GroupCreateWalletsType.INCLUDE
-    ) {
-      all.push(connectedProfile.primary_wallet);
-    }
-    setWallets(all.length ? Array.from(new Set(all)) : null);
-  }, [uploadedWallets, emmaWallets, selectedWallets]);
+  useEffect(() => {
+    const uploaded = uploadedWallets ?? [];
+    const emma = emmaWallets ?? [];
+    const selected = selectedWallets ?? [];
+    let all = Array.from(new Set([...uploaded, ...emma, ...selected]));
+    if (
+      iAmIncluded &&
+      connectedProfile?.primary_wallet &&
+      type === GroupCreateWalletsType.INCLUDE
+    ) {
+      all = [...all, connectedProfile.primary_wallet];
+    }
+    const next = all.length ? Array.from(new Set(all)) : null;
+    if (!walletListsMatch(next, wallets)) {
+      setWallets(next);
+    }
+  }, [
+    uploadedWallets,
+    emmaWallets,
+    selectedWallets,
+    iAmIncluded,
+    connectedProfile?.primary_wallet,
+    type,
+  ]);

Optional: tighten boolean type

-  const isOverLimit = wallets?.length && wallets.length > walletsLimit;
+  const isOverLimit = (wallets?.length ?? 0) > walletsLimit;

Want a focused test that toggles iAmIncluded and asserts parent setWallets updates immediately?

components/utils/select/dropdown/CommonDropdownItemsDefaultWrapper.tsx (1)

23-27: Click-away should use contains(), not direct equality.

Equality fails when the trigger has child elements; clicking those will be treated as “away,” causing flicker or unwanted close.

-  useClickAway(listRef, (e) => {
-    if (e.target !== buttonRef.current) {
-      setOpen(false);
-    }
-  });
+  useClickAway(listRef, (e) => {
+    const target = e.target as Node | null;
+    if (!buttonRef.current || !buttonRef.current.contains(target)) {
+      setOpen(false);
+    }
+  });
components/waves/specs/groups/group/WaveGroup.tsx (1)

37-37: Missing dependencies in useEffect cause stale showEdit.

canEditGroup() depends on activeProfileProxy and scope.group?.is_direct_message but they’re not in the deps array.

Apply this diff:

-useEffect(() => setShowEdit(canEditGroup()), [connectedProfile, wave]);
+useEffect(() => {
+  setShowEdit(canEditGroup());
+}, [connectedProfile, activeProfileProxy, wave, scope]);
components/waves/specs/groups/group/edit/WaveGroupRemoveModal.tsx (1)

29-41: Replace inline SVGs with FontAwesome icons.

Guidelines require FontAwesome in TSX. Swap trash/X SVGs for faTrash/faXmark.

Apply this diff:

@@
-import { useRef } from "react";
+import { useRef } from "react";
 import { createPortal } from "react-dom";
 import { useClickAway, useKeyPressEvent } from "react-use";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { faTrash, faXmark } from "@fortawesome/free-solid-svg-icons";
@@
-                  <span className="tw-inline-flex tw-items-center tw-justify-center tw-rounded-xl tw-h-11 tw-w-11 tw-bg-red/10 tw-border tw-border-solid tw-border-red/10">
-                    <svg
-                      className="tw-flex-shrink-0 tw-h-6 tw-w-6 tw-text-red tw-transition tw-duration-300 tw-ease-out"
-                      viewBox="0 0 24 24"
-                      fill="none"
-                      xmlns="http://www.w3.org/2000/svg">
-                      <path
-                        d="M16 6V5.2C16 4.0799 16 3.51984 15.782 3.09202C15.5903 2.71569 15.2843 2.40973 14.908 2.21799C14.4802 2 13.9201 2 12.8 2H11.2C10.0799 2 9.51984 2 9.09202 2.21799C8.71569 2.40973 8.40973 2.71569 8.21799 3.09202C8 3.51984 8 4.0799 8 5.2V6M10 11.5V16.5M14 11.5V16.5M3 6H21M19 6V17.2C19 18.8802 19 19.7202 18.673 20.362C18.3854 20.9265 17.9265 21.3854 17.362 21.673C16.7202 22 15.8802 22 14.2 22H9.8C8.11984 22 7.27976 22 6.63803 21.673C6.07354 21.3854 5.6146 20.9265 5.32698 20.362C5 19.7202 5 18.8802 5 17.2V6"
-                        stroke="currentColor"
-                        strokeWidth="2"
-                        strokeLinecap="round"
-                        strokeLinejoin="round"
-                      />
-                    </svg>
-                  </span>
+                  <span className="tw-inline-flex tw-items-center tw-justify-center tw-rounded-xl tw-h-11 tw-w-11 tw-bg-red/10 tw-border tw-border-solid tw-border-red/10">
+                    <FontAwesomeIcon icon={faTrash} className="tw-h-6 tw-w-6 tw-text-red" />
+                  </span>
@@
-                <button
-                  onClick={closeModal}
+                <button
+                  onClick={closeModal}
                   type="button"
                   className="tw-p-2.5 tw-flex tw-items-center tw-justify-center tw-rounded-full tw-bg-iron-950 tw-border-0 tw-text-iron-400 hover:tw-text-iron-50 focus:tw-outline-none tw-transition tw-duration-300 tw-ease-out">
                   <span className="tw-sr-only tw-text-sm">Close</span>
-                  <svg
-                    className="tw-h-6 tw-w-6"
-                    aria-hidden="true"
-                    fill="none"
-                    viewBox="0 0 24 24"
-                    strokeWidth="1.5"
-                    stroke="currentColor">
-                    <path
-                      strokeLinecap="round"
-                      strokeLinejoin="round"
-                      d="M6 18L18 6M6 6l12 12"
-                    />
-                  </svg>
+                  <FontAwesomeIcon icon={faXmark} className="tw-h-6 tw-w-6" />
                 </button>

Also applies to: 59-71

🧹 Nitpick comments (35)
codex/STATE.md (1)

16-18: Add PR linkage and update dates for auditability

If this PR implements these tickets, link it in PRs and bump Last Updated.

Apply this diff if applicable:

-| TKT-0010 | Fix wave group menu overflow clipping | In-Progress | P1 | simo6529 | — | 2025-10-15 |
+| TKT-0010 | Fix wave group menu overflow clipping | In-Progress | P1 | simo6529 | [#1544](https://github.com/6529-Collections/6529seize-frontend/pull/1544) | 2025-10-17 |
-| TKT-0011 | Restore identity search keyboard navigation | In-Progress | P1 | openai-assistant | — | 2025-10-16 |
+| TKT-0011 | Restore identity search keyboard navigation | In-Progress | P1 | openai-assistant | [#1544](https://github.com/6529-Collections/6529seize-frontend/pull/1544) | 2025-10-17 |
-| TKT-0012 | Refactor wave group edit buttons for modular clarity | In-Progress | P1 | openai-assistant | — | 2025-10-16 |
+| TKT-0012 | Refactor wave group edit buttons for modular clarity | In-Progress | P1 | openai-assistant | [#1544](https://github.com/6529-Collections/6529seize-frontend/pull/1544) | 2025-10-17 |

Based on learnings

components/utils/input/profile-search/CommonProfileSearchItems.tsx (1)

36-36: Add listbox/option semantics for keyboard/A11y

Expose roles to align with highlightedIndex navigation.

Apply:

-              <ul className="tw-flex tw-flex-col tw-gap-y-1 tw-px-2 tw-mx-0 tw-mb-0 tw-list-none">
+              <ul className="tw-flex tw-flex-col tw-gap-y-1 tw-px-2 tw-mx-0 tw-mb-0 tw-list-none" role="listbox">
                 {profiles.length ? (
                   profiles.map((profile, index) => (
                     <CommonProfileSearchItem
                       key={profile.wallet}
                       profile={profile}
                       selected={selected}
                       isHighlighted={highlightedIndex === index}
                       onProfileSelect={onProfileSelect}
                     />
                   ))

Pair with the item-level change below in CommonProfileSearchItem.

Also applies to: 38-45

components/utils/input/profile-search/CommonProfileSearchItem.tsx (1)

25-31: Improve A11y: mark options and selection state

Expose option role and selection for SR/keyboard tooling.

Apply:

-  return (
-    <li className="tw-h-full">
+  return (
+    <li className="tw-h-full" role="option" aria-selected={isHighlighted || isSelected}>
       <button
         type="button"
         className={`hover:tw-bg-iron-700 tw-py-2 tw-w-full tw-h-full tw-border-none tw-text-left tw-flex tw-items-center tw-justify-between tw-text-white tw-rounded-lg tw-relative tw-cursor-pointer tw-select-none tw-px-2 focus:tw-outline-none focus:tw-ring-1 focus:tw-ring-primary-400 tw-transition tw-duration-300 tw-ease-out ${
           isHighlighted ? "tw-bg-iron-700" : "tw-bg-transparent"
         }`}
         onClick={onProfileClick}
       >

Optionally add a data-highlighted attr for tests if needed.

Also applies to: 60-76

components/groups/page/create/config/wallets/GroupCreateWallets.tsx (1)

73-83: Sync effect is fine; consider case-insensitive de‑dupe

Today you de‑dupe by exact case. If sources can differ only by case, normalize before Set to avoid duplicates.

Example:

-    setUploadedWallets(Array.from(new Set(wallets)));
+    setUploadedWallets(Array.from(new Set(wallets.map(w => w.toLowerCase()))));

Same applies to onUploadedWalletsChange/onEmmaWalletsChange.

codex/tickets/TKT-0010.md (1)

26-30: Link this ticket to the open PR (#1544).

Fill in Primary PR to satisfy ticket hygiene and cross-referencing.

As per coding guidelines.

- - Primary PR: _(add when available)_
+ - Primary PR: #1544
components/utils/input/identity/IdentitySearch.tsx (1)

106-112: Avoid duplicate autofocus (attribute + imperative focus).

Using both can cause double focus events and scroll jumps. Prefer the ref-based focus and drop the input’s autoFocus prop.

-        autoFocus={autoFocus}

Also applies to: 192-193

components/utils/select/dropdown/CommonDropdownItemsDefaultWrapper.tsx (1)

32-40: Clamp computed left to the viewport.

When near the left edge, right - width can be negative. Clamp to ≥ 0 (or use a positioning lib like Floating UI).

-    if (buttonPosition?.right && dropdownRef.current) {
-      const { right } = buttonPosition;
-      dropdownRef.current.style.left = `${
-        right - dropdownRef.current.offsetWidth
-      }px`;
-    }
+    if (buttonPosition?.right && dropdownRef.current) {
+      const { right } = buttonPosition;
+      const el = dropdownRef.current;
+      const left = Math.max(0, right - el.offsetWidth);
+      el.style.left = `${left}px`;
+    }
components/utils/select/dropdown/CommonDropdownItem.tsx (1)

61-76: Use FontAwesome for the check icon per TSX icon guideline.

Replace inline SVG with FontAwesome to align with repo conventions.

As per coding guidelines.

+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { faCheck } from "@fortawesome/free-solid-svg-icons";
@@
-          {item.value === activeItem && (
-            <svg
-              className="tw-h-5 tw-w-5 tw-flex-shrink-0 tw-text-primary-300 tw-transition tw-duration-200 tw-ease-out"
-              viewBox="0 0 24 24"
-              fill="none"
-              aria-hidden="true"
-              xmlns="http://www.w3.org/2000/svg">
-              <path
-                d="M20 6L9 17L4 12"
-                stroke="currentColor"
-                strokeWidth="2"
-                strokeLinecap="round"
-                strokeLinejoin="round"
-              />
-            </svg>
-          )}
+          {item.value === activeItem && (
+            <FontAwesomeIcon
+              icon={faCheck}
+              className="tw-h-5 tw-w-5 tw-flex-shrink-0 tw-text-primary-300 tw-transition tw-duration-200 tw-ease-out"
+              aria-hidden="true"
+            />
+          )}
components/waves/specs/groups/group/edit/buttons/subcomponents/WaveGroupEditMenu.tsx (1)

107-130: Avoid mounting hidden editor components solely to capture “open” handles.

Current pattern mounts WaveGroupEditButton/RemoveButton in a tw-hidden container to grab open callbacks. Prefer exposing an imperative handle via forwardRef or lifting the open state into this menu to avoid hidden interactive elements in the DOM and reduce render cost.

components/common/CompactMenu.tsx (1)

157-221: Flatten interactive hierarchy: render MenuItem as “button” to avoid nested controls.

Headless UI already manages role/keyboard on MenuItem. Rendering a button inside MenuItem creates nested interactive elements. Render MenuItem as button and move props up.

-                  {items.map((item) => (
-                    <MenuItem key={item.id} disabled={item.disabled}>
-                      {({ active }) => {
+                  {items.map((item) => (
+                    <MenuItem
+                      key={item.id}
+                      as="button"
+                      type="button"
+                      disabled={item.disabled}
+                      role={item.role}
+                      aria-selected={item.ariaSelected}
+                      aria-label={item.ariaLabel}
+                      data-compact-menu-item="true"
+                      data-menu-item-id={item.id}
+                      data-active={(item.active ?? activeItemId === item.id) ? "true" : "false"}
+                      data-disabled={item.disabled ? "true" : "false"}
+                      onClick={() => {
+                        if (item.disabled) return;
+                        if (closeOnSelect) close();
+                        item.onSelect?.();
+                        onItemSelect?.(item.id);
+                      }}
+                      className={clsx(
+                        DEFAULT_ITEM_CLASSES,
+                        (item.active ?? activeItemId === item.id)
+                          ? (unstyledItems ? undefined : DEFAULT_ACTIVE_ITEM_CLASSES)
+                          : (unstyledItems ? undefined : DEFAULT_INACTIVE_ITEM_CLASSES),
+                        active && !item.disabled && !(item.active ?? activeItemId === item.id)
+                          ? (unstyledItems ? undefined : DEFAULT_FOCUS_ITEM_CLASSES)
+                          : undefined,
+                        itemClassName,
+                        item.className,
+                        item.disabled && "tw-cursor-not-allowed tw-opacity-60 tw-text-iron-500",
+                      )}
+                    >
+                      {item.icon && (
+                        <span className="tw-flex tw-shrink-0 tw-items-center tw-justify-center">
+                          {item.icon}
+                        </span>
+                      )}
+                      <span className="tw-flex-1 tw-text-left">{item.label}</span>
+                    </MenuItem>
-                      }}
-                    </MenuItem>
                   ))}
services/groups/groupMutations.ts (2)

15-31: Canonicalize empty arrays to null in sanitiseGroupPayload.

To align read/write shapes and minimize payload, coerce [] to null for identity arrays (you already do this elsewhere).

 export const sanitiseGroupPayload = (
   payload: ApiCreateGroup,
   name: string
 ): ApiCreateGroup => ({
   ...payload,
   name,
   group: {
     ...payload.group,
     owns_nfts: [...payload.group.owns_nfts],
-    identity_addresses: payload.group.identity_addresses
-      ? [...payload.group.identity_addresses]
-      : null,
-    excluded_identity_addresses: payload.group.excluded_identity_addresses
-      ? [...payload.group.excluded_identity_addresses]
-      : null,
+    identity_addresses:
+      payload.group.identity_addresses && payload.group.identity_addresses.length > 0
+        ? [...payload.group.identity_addresses]
+        : null,
+    excluded_identity_addresses:
+      payload.group.excluded_identity_addresses && payload.group.excluded_identity_addresses.length > 0
+        ? [...payload.group.excluded_identity_addresses]
+        : null,
   },
 });

90-114: Optional: add AbortSignal propagation for API calls.

Allow callers to cancel publish/hide requests by accepting a signal and passing it to commonApiPost.

components/waves/specs/groups/group/edit/buttons/hooks/useWaveGroupEditButtonsController.ts (5)

26-33: Type WAVE_GROUP_LABELS against WaveGroupType for safety.

Prevents key drift and enables exhaustiveness checks.

-const WAVE_GROUP_LABELS: Record<string, string> = {
+const WAVE_GROUP_LABELS: Record<WaveGroupType, string> = {
   VIEW: "View",
   DROP: "Drop",
   VOTE: "Vote",
   CHAT: "Chat",
   ADMIN: "Admin",
 };

465-468: Prefer polling publish status over a fixed 2s sleep.

Replace fixed wait with a short, bounded poll to ensure visibility propagation before updating the wave.

-        await wait(2000);
+        // Poll publish status (max ~2s)
+        for (let i = 0; i < 10; i++) {
+          try {
+            const refreshed = await commonApiFetch<ApiGroupFull>({ endpoint: `groups/${createdGroup.id}` });
+            if (refreshed.visible) break;
+          } catch {}
+          await wait(200);
+        }

339-353: DRY: reuse updateWave instead of calling editWaveMutation directly.

Consolidates auth/toast/mutating handling in one place.

-        const updateBody = buildWaveUpdateBody(wave, type, createdGroup.id);
-        waveMutationTriggered = true;
-        await editWaveMutation.mutateAsync(updateBody);
+        const updateBody = buildWaveUpdateBody(wave, type, createdGroup.id);
+        waveMutationTriggered = true;
+        await updateWave(updateBody);

Also applies to: 465-468


305-318: Remove unused hasGroup variable.

It’s computed but never used.

-  const hasGroup = scopedGroup !== null;
   const isWaveAdmin =
     wave.wave.authenticated_user_eligible_for_admin ?? false;

406-425: Optional: fetch via React Query for consistency.

Consider queryClient.fetchQuery for group/identity fetches to leverage caching and unified error handling.

As per coding guidelines

__tests__/components/waves/specs/groups/group/edit/WaveGroupRemove.test.tsx (1)

35-47: Avoid brittle getAllByTestId by using rerender.

Render once, then rerender for ADMIN to keep a single modal in the tree.

Apply this diff:

-const onWaveUpdate = jest.fn(() => Promise.resolve());
-render(
-  <WaveGroupRemove wave={baseWave} type={WaveGroupType.VIEW} isEditOpen={true} setIsEditOpen={jest.fn()} onWaveUpdate={onWaveUpdate} />
-);
+const onWaveUpdate = jest.fn(() => Promise.resolve());
+const { rerender } = render(
+  <WaveGroupRemove wave={baseWave} type={WaveGroupType.VIEW} isEditOpen={true} setIsEditOpen={jest.fn()} onWaveUpdate={onWaveUpdate} />
+);
@@
-onWaveUpdate.mockClear();
-render(
-  <WaveGroupRemove wave={baseWave} type={WaveGroupType.ADMIN} isEditOpen={true} setIsEditOpen={jest.fn()} onWaveUpdate={onWaveUpdate} />
-);
-await user.click(screen.getAllByTestId('remove')[1]);
+onWaveUpdate.mockClear();
+rerender(
+  <WaveGroupRemove wave={baseWave} type={WaveGroupType.ADMIN} isEditOpen={true} setIsEditOpen={jest.fn()} onWaveUpdate={onWaveUpdate} />
+);
+await user.click(screen.getByTestId('remove'));
__tests__/components/waves/specs/groups/group/edit/WaveGroupRemoveModal.test.tsx (1)

1-1: Prefer userEvent over fireEvent for clicks.

Aligns with Testing Library best practices and project guidelines.

Apply this diff:

-import { render, screen, fireEvent } from '@testing-library/react';
+import { render, screen } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
@@
-render(<WaveGroupRemoveModal closeModal={close} removeGroup={remove} />);
-fireEvent.click(screen.getByText('Remove'));
+render(<WaveGroupRemoveModal closeModal={close} removeGroup={remove} />);
+await userEvent.click(screen.getByText('Remove'));
@@
-fireEvent.click(screen.getByText('Cancel'));
+await userEvent.click(screen.getByText('Cancel'));
@@
-fireEvent.click(screen.getByRole('button', { name: 'Close' }));
+await userEvent.click(screen.getByRole('button', { name: 'Close' }));

Also applies to: 12-19

components/waves/specs/groups/group/edit/WaveGroupRemove.tsx (1)

23-85: Reduce duplication in getBody.

Five near-identical branches. Consider mapping type → path and nulling group_id via a helper.

If desired, I can propose a typed helper to mutate the specific path safely.

__tests__/components/groups/page/create/actions/GroupCreateActions.test.tsx (1)

11-23: Good move to hook-level mocking; add an assertion for validate.

Assert that validate is called with the payload before submit to lock the flow.

Apply this diff:

@@ it('creates group and marks visible on save', async () => {
   await userEvent.click(screen.getByRole('button', { name: 'Create' }));
 
   await waitFor(() => expect(mockSubmit).toHaveBeenCalledTimes(1));
+  expect(mockValidate).toHaveBeenCalledWith(groupConfig);
   expect(mockSubmit).toHaveBeenCalledWith({
     payload: groupConfig,
     previousGroup: originalGroup,
     currentHandle: 'alice',
   });

Based on learnings.

Also applies to: 61-66, 80-82, 87-92

components/common/TabToggleWithOverflow.tsx (1)

48-63: Optional: Consider Headless UI Tabs for full tab semantics.

If keyboard navigation and aria-controls/tabpanel linkage are required, migrating to Headless UI Tabs will handle roving tabindex and ARIA out of the box, while keeping your overflow menu approach.

components/waves/specs/groups/group/edit/buttons/subcomponents/WaveGroupManageIdentitiesModals.tsx (1)

20-37: Deduplicate modal rendering with a single mode map.

Removes repetition and keeps INCLUDE/EXCLUDE handling in one place.

   return (
-    <>
-      {activeModal === WaveGroupIdentitiesModal.INCLUDE ? (
-        <WaveGroupManageIdentitiesModal
-          mode={WaveGroupManageIdentitiesMode.INCLUDE}
-          onClose={onClose}
-          onConfirm={onConfirm}
-        />
-      ) : null}
-      {activeModal === WaveGroupIdentitiesModal.EXCLUDE ? (
-        <WaveGroupManageIdentitiesModal
-          mode={WaveGroupManageIdentitiesMode.EXCLUDE}
-          onClose={onClose}
-          onConfirm={onConfirm}
-        />
-      ) : null}
-    </>
+    <>
+      {(() => {
+        const mode =
+          activeModal === WaveGroupIdentitiesModal.INCLUDE
+            ? WaveGroupManageIdentitiesMode.INCLUDE
+            : activeModal === WaveGroupIdentitiesModal.EXCLUDE
+            ? WaveGroupManageIdentitiesMode.EXCLUDE
+            : null;
+        return mode ? (
+          <WaveGroupManageIdentitiesModal
+            mode={mode}
+            onClose={onClose}
+            onConfirm={onConfirm}
+          />
+        ) : null;
+      })()}
+    </>
   );
components/waves/specs/groups/group/edit/WaveGroupEditButtons.tsx (1)

3-3: Stabilize callbacks to avoid needless re-renders in menu.

Wrap the identity modal openers with useCallback so WaveGroupEditMenu’s memoized items don’t churn every render.

-import { useContext } from "react";
+import { useCallback, useContext } from "react";
@@
-      <WaveGroupEditMenu
+      <WaveGroupEditMenu
         wave={wave}
         type={type}
         onWaveUpdate={updateWave}
         canIncludeIdentity={canIncludeIdentity}
         canExcludeIdentity={canExcludeIdentity}
         canRemoveGroup={canRemoveGroup}
-        onIncludeIdentity={() =>
-          openIdentitiesModal(WaveGroupIdentitiesModal.INCLUDE)
-        }
-        onExcludeIdentity={() =>
-          openIdentitiesModal(WaveGroupIdentitiesModal.EXCLUDE)
-        }
+        onIncludeIdentity={useCallback(
+          () => openIdentitiesModal(WaveGroupIdentitiesModal.INCLUDE),
+          [openIdentitiesModal],
+        )}
+        onExcludeIdentity={useCallback(
+          () => openIdentitiesModal(WaveGroupIdentitiesModal.EXCLUDE),
+          [openIdentitiesModal],
+        )}
       />

Small a11y nit: CircleLoader uses role="status" with aria-hidden in its SVG; consider removing aria-hidden or adding an aria-live/text alternative when shown.

Also applies to: 65-71

__tests__/components/waves/specs/groups/group/edit/WaveGroupEditButton.test.tsx (1)

30-37: Rename test title to match new prop name.

Keeps tests self-explanatory after onEdit → onWaveUpdate rename.

-  it('calls onEdit and closes', async () => {
+  it('calls onWaveUpdate and closes', async () => {
components/waves/specs/groups/group/edit/buttons/utils/waveGroupEdit.ts (1)

10-24: Defensive access for nested API fields.

Optional chain the nested scope paths to avoid runtime crashes if backend responses omit sub-objects. Low risk, improves resilience.

   switch (type) {
     case WaveGroupType.VIEW:
-      return wave.visibility.scope.group ?? null;
+      return wave.visibility?.scope?.group ?? null;
     case WaveGroupType.DROP:
-      return wave.participation.scope.group ?? null;
+      return wave.participation?.scope?.group ?? null;
     case WaveGroupType.VOTE:
-      return wave.voting.scope.group ?? null;
+      return wave.voting?.scope?.group ?? null;
     case WaveGroupType.CHAT:
-      return wave.chat.scope.group ?? null;
+      return wave.chat?.scope?.group ?? null;
     case WaveGroupType.ADMIN:
-      return wave.wave.admin_group.group ?? null;
+      return wave.wave?.admin_group?.group ?? null;
     default:
       return null;
   }

If the API guarantees these paths are always present, feel free to skip.

components/groups/page/create/actions/GroupCreateTest.tsx (2)

92-94: Compute loading without state/effect.

Removes an unnecessary state + effect pair.

-  const [loading, setLoading] = useState<boolean>(false);
-  useEffect(() => setLoading(isFetching || isTesting), [isFetching, isTesting]);
+  const loading = isFetching || isTesting;

113-126: Use FontAwesome for icons (codebase guideline) and simplify markup.

Replaces inline SVG with FontAwesome, keeping classes.

+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { faUsers } from "@fortawesome/free-solid-svg-icons";
@@
-            <svg
-              className="tw-size-5 tw-flex-shrink-0 tw-text-iron-300"
-              xmlns="http://www.w3.org/2000/svg"
-              fill="none"
-              aria-hidden="true"
-              viewBox="0 0 24 24"
-              strokeWidth="1.5"
-              stroke="currentColor">
-              <path
-                strokeLinecap="round"
-                strokeLinejoin="round"
-                d="M18 18.72a9.094 9.094 0 0 0 3.741-.479 3 3 0 0 0-4.682-2.72m.94 3.198.001.031c0 .225-.012.447-.037.666A11.944 11.944 0 0 1 12 21c-2.17 0-4.207-.576-5.963-1.584A6.062 6.062 0 0 1 6 18.719m12 0a5.971 5.971 0 0 0-.941-3.197m0 0A5.995 5.995 0 0 0 12 12.75a5.995 5.995 0 0 0-5.058 2.772m0 0a3 3 0 0 0-4.681 2.72 8.986 8.986 0 0 0 3.74.477m.94-3.197a5.971 5.971 0 0 0-.94 3.197M15 6.75a3 3 0 1 1-6 0 3 3 0 0 1 6 0Zm6 3a2.25 2.25 0 1 1-4.5 0 2.25 2.25 0 0 1 4.5 0Zm-13.5 0a2.25 2.25 0 1 1-4.5 0 2.25 2.25 0 0 1 4.5 0Z"
-              />
-            </svg>
+            <FontAwesomeIcon
+              icon={faUsers}
+              className="tw-size-5 tw-flex-shrink-0 tw-text-iron-300"
+              aria-hidden="true"
+            />

Confirm FontAwesome is already configured in this app; otherwise, I can add a minimal setup.

components/waves/specs/groups/group/edit/WaveGroupEditButton.tsx (1)

27-33: Set explicit button type and accessible name.

Prevents accidental form submits and ensures a stable accessible name (icon-only button).

-        <button
-          title="Edit"
+        <button
+          type="button"
+          aria-label="Edit"
+          title="Edit"
           onClick={() => setIsEditOpen(true)}
           className="tw-border-none tw-bg-transparent tw-p-0 tw-items-center tw-text-iron-300 hover:tw-text-iron-400 tw-duration-300 tw-ease-out tw-transition-all">
           <PencilIcon />
         </button>
components/waves/specs/groups/group/edit/WaveGroupRemoveButton.tsx (1)

26-45: Set explicit button type and accessible name; consider FontAwesome for consistency.

Prevents accidental submits; improves a11y. Optionally swap inline SVG for a FontAwesome icon per guidelines.

-        <button
-          title="Remove"
+        <button
+          type="button"
+          aria-label="Remove"
+          title="Remove"
           onClick={() => setIsEditOpen(true)}
           className="tw-border-none tw-bg-transparent tw-p-0 tw-items-center">
-          <svg
+          <svg
             className="tw-flex-shrink-0 tw-size-5 tw-text-red tw-transition tw-duration-300 tw-ease-out hover:tw-scale-110"
             viewBox="0 0 24 24"
             fill="none"
             aria-hidden="true"
             xmlns="http://www.w3.org/2000/svg">

If preferred, I can provide a FontAwesome replacement (e.g., faCircleXmark) in a follow-up diff.

hooks/groups/useGroupMutations.ts (2)

71-80: Centralize error message formatting.

toErrorMessage is good; consider exporting it from services/groups/groupMutations to keep a single source for API error shaping across hooks/services.


144-200: Visibility updates: minor ergonomics.

Return the published/hidden ApiGroup (when available) to enable downstream cache updates without extra fetch. Optionally include first-class toast hooks here to keep callers slimmer. Safe to defer.

components/groups/page/create/actions/GroupCreateActions.tsx (1)

27-34: Good consolidation via useGroupMutations; one UX nit on validation memoization.

validate also runs inside submit, so correctness is safe. If groupConfig can be mutated in place (same reference), the memoized validation may become stale and keep the button disabled/enabled incorrectly. If that's possible, derive disabled directly from validate in the dependency array or add a stable version/sha of groupConfig to deps.

__tests__/components/groups/page/create/actions/GroupCreateTest.test.tsx (1)

2-2: Prefer user-event over fireEvent for interaction fidelity.

Switch to userEvent for clicks; it’s closer to real user behavior and matches our testing guidelines.

As per coding guidelines.

-import { fireEvent, render, screen, waitFor } from '@testing-library/react';
+import { render, screen, waitFor } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
@@
-  await fireEvent.click(screen.getByRole('button', { name: 'Test' }));
+  await userEvent.click(screen.getByRole('button', { name: 'Test' }));
@@
-  await fireEvent.click(screen.getByRole('button', { name: 'Test' }));
+  await userEvent.click(screen.getByRole('button', { name: 'Test' }));

Also applies to: 85-91, 103-106

__tests__/components/waves/specs/groups/group/edit/WaveGroupEditButtons.test.tsx (1)

2-2: Use user-event for clicks in component tests.

Align with our test guidelines and improve interaction realism.

As per coding guidelines.

-import { render, screen, fireEvent, waitFor } from '@testing-library/react';
+import { render, screen, waitFor } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
@@
-    fireEvent.click(screen.getByRole('button', { name: /Group options/i }));
-    fireEvent.click(screen.getByText('Change group'));
+    await userEvent.click(screen.getByRole('button', { name: /Group options/i }));
+    await userEvent.click(screen.getByText('Change group'));
@@
-    fireEvent.click(screen.getByRole('button', { name: /Group options/i }));
-    fireEvent.click(screen.getByText('Change group'));
+    await userEvent.click(screen.getByRole('button', { name: /Group options/i }));
+    await userEvent.click(screen.getByText('Change group'));
@@
-    fireEvent.click(screen.getByRole('button', { name: /Group options/i }));
+    await userEvent.click(screen.getByRole('button', { name: /Group options/i }));

Also applies to: 99-101, 108-110, 114-117

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b2c4f3b and 4acfcb6.

📒 Files selected for processing (45)
  • __tests__/components/WaveGroupRemoveButton.test.tsx (2 hunks)
  • __tests__/components/common/TabToggleWithOverflow.test.tsx (4 hunks)
  • __tests__/components/groups/GroupCreateWallets.test.tsx (1 hunks)
  • __tests__/components/groups/page/create/actions/GroupCreateActions.test.tsx (3 hunks)
  • __tests__/components/groups/page/create/actions/GroupCreateTest.test.tsx (1 hunks)
  • __tests__/components/groups/page/list/card/actions/delete/GroupCardDeleteModal.test.tsx (2 hunks)
  • __tests__/components/waves/specs/groups/group/edit/WaveGroupEdit.test.tsx (1 hunks)
  • __tests__/components/waves/specs/groups/group/edit/WaveGroupEditButton.test.tsx (2 hunks)
  • __tests__/components/waves/specs/groups/group/edit/WaveGroupEditButtons.test.tsx (3 hunks)
  • __tests__/components/waves/specs/groups/group/edit/WaveGroupRemove.test.tsx (1 hunks)
  • __tests__/components/waves/specs/groups/group/edit/WaveGroupRemoveModal.test.tsx (1 hunks)
  • __tests__/components/waves/specs/groups/group/edit/buttons/useWaveGroupEditButtonsController.test.tsx (1 hunks)
  • __tests__/services/groups/groupMutations.test.ts (1 hunks)
  • codex/STATE.md (1 hunks)
  • codex/tickets/TKT-0010.md (1 hunks)
  • codex/tickets/TKT-0011.md (1 hunks)
  • codex/tickets/TKT-0012.md (1 hunks)
  • components/common/CompactMenu.tsx (1 hunks)
  • components/common/TabToggleWithOverflow.tsx (2 hunks)
  • components/groups/page/create/GroupCreate.tsx (2 hunks)
  • components/groups/page/create/actions/GroupCreateActions.tsx (3 hunks)
  • components/groups/page/create/actions/GroupCreateTest.tsx (3 hunks)
  • components/groups/page/create/config/wallets/GroupCreateWallets.tsx (2 hunks)
  • components/groups/page/list/card/actions/delete/GroupCardDeleteModal.tsx (5 hunks)
  • components/utils/input/identity/IdentitySearch.tsx (4 hunks)
  • components/utils/input/profile-search/CommonProfileSearchItem.tsx (2 hunks)
  • components/utils/input/profile-search/CommonProfileSearchItems.tsx (2 hunks)
  • components/utils/select/dropdown/CommonDropdownItem.tsx (2 hunks)
  • components/utils/select/dropdown/CommonDropdownItemsDefaultWrapper.tsx (1 hunks)
  • components/waves/create-wave/services/waveGroupService.ts (2 hunks)
  • components/waves/groups/WaveGroups.tsx (1 hunks)
  • components/waves/specs/groups/group/WaveGroup.tsx (1 hunks)
  • components/waves/specs/groups/group/edit/WaveGroupEdit.tsx (2 hunks)
  • components/waves/specs/groups/group/edit/WaveGroupEditButton.tsx (2 hunks)
  • components/waves/specs/groups/group/edit/WaveGroupEditButtons.tsx (1 hunks)
  • components/waves/specs/groups/group/edit/WaveGroupManageIdentitiesModal.tsx (1 hunks)
  • components/waves/specs/groups/group/edit/WaveGroupRemove.tsx (2 hunks)
  • components/waves/specs/groups/group/edit/WaveGroupRemoveButton.tsx (2 hunks)
  • components/waves/specs/groups/group/edit/WaveGroupRemoveModal.tsx (1 hunks)
  • components/waves/specs/groups/group/edit/buttons/hooks/useWaveGroupEditButtonsController.ts (1 hunks)
  • components/waves/specs/groups/group/edit/buttons/subcomponents/WaveGroupEditMenu.tsx (1 hunks)
  • components/waves/specs/groups/group/edit/buttons/subcomponents/WaveGroupManageIdentitiesModals.tsx (1 hunks)
  • components/waves/specs/groups/group/edit/buttons/utils/waveGroupEdit.ts (1 hunks)
  • hooks/groups/useGroupMutations.ts (1 hunks)
  • services/groups/groupMutations.ts (1 hunks)
✅ Files skipped from review due to trivial changes (2)
  • codex/tickets/TKT-0011.md
  • codex/tickets/TKT-0012.md
🧰 Additional context used
📓 Path-based instructions (9)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{ts,tsx}: Do not include any comments in the code
Use react-query for data fetching
Always add readonly before props

**/*.{ts,tsx}: Use TypeScript for source code and follow existing code style and naming conventions
Adhere to clean code standards as measured by SonarQube

Files:

  • components/utils/input/profile-search/CommonProfileSearchItem.tsx
  • __tests__/components/waves/specs/groups/group/edit/WaveGroupRemoveModal.test.tsx
  • components/waves/groups/WaveGroups.tsx
  • components/common/TabToggleWithOverflow.tsx
  • __tests__/components/groups/GroupCreateWallets.test.tsx
  • components/utils/select/dropdown/CommonDropdownItemsDefaultWrapper.tsx
  • components/waves/specs/groups/group/edit/WaveGroupRemove.tsx
  • components/waves/specs/groups/group/edit/buttons/hooks/useWaveGroupEditButtonsController.ts
  • components/groups/page/create/actions/GroupCreateActions.tsx
  • components/groups/page/list/card/actions/delete/GroupCardDeleteModal.tsx
  • components/utils/select/dropdown/CommonDropdownItem.tsx
  • components/utils/input/identity/IdentitySearch.tsx
  • components/groups/page/create/actions/GroupCreateTest.tsx
  • components/utils/input/profile-search/CommonProfileSearchItems.tsx
  • components/waves/specs/groups/group/edit/WaveGroupEdit.tsx
  • components/waves/specs/groups/group/edit/WaveGroupManageIdentitiesModal.tsx
  • __tests__/services/groups/groupMutations.test.ts
  • __tests__/components/waves/specs/groups/group/edit/WaveGroupRemove.test.tsx
  • hooks/groups/useGroupMutations.ts
  • __tests__/components/common/TabToggleWithOverflow.test.tsx
  • components/groups/page/create/GroupCreate.tsx
  • components/waves/specs/groups/group/edit/buttons/utils/waveGroupEdit.ts
  • __tests__/components/WaveGroupRemoveButton.test.tsx
  • components/groups/page/create/config/wallets/GroupCreateWallets.tsx
  • __tests__/components/groups/page/create/actions/GroupCreateActions.test.tsx
  • components/waves/specs/groups/group/edit/WaveGroupEditButtons.tsx
  • components/waves/specs/groups/group/edit/WaveGroupEditButton.tsx
  • __tests__/components/groups/page/list/card/actions/delete/GroupCardDeleteModal.test.tsx
  • components/waves/specs/groups/group/edit/buttons/subcomponents/WaveGroupManageIdentitiesModals.tsx
  • components/waves/create-wave/services/waveGroupService.ts
  • services/groups/groupMutations.ts
  • components/waves/specs/groups/group/edit/WaveGroupRemoveModal.tsx
  • __tests__/components/waves/specs/groups/group/edit/WaveGroupEdit.test.tsx
  • __tests__/components/waves/specs/groups/group/edit/WaveGroupEditButton.test.tsx
  • components/common/CompactMenu.tsx
  • __tests__/components/groups/page/create/actions/GroupCreateTest.test.tsx
  • components/waves/specs/groups/group/edit/WaveGroupRemoveButton.tsx
  • __tests__/components/waves/specs/groups/group/edit/WaveGroupEditButtons.test.tsx
  • components/waves/specs/groups/group/WaveGroup.tsx
  • components/waves/specs/groups/group/edit/buttons/subcomponents/WaveGroupEditMenu.tsx
  • __tests__/components/waves/specs/groups/group/edit/buttons/useWaveGroupEditButtonsController.test.tsx
**/*.tsx

📄 CodeRabbit inference engine (.cursorrules)

**/*.tsx: Use FontAwesome for icons
Use TailwindCSS for styling

Implement React components as functional components using hooks (no class components)

Files:

  • components/utils/input/profile-search/CommonProfileSearchItem.tsx
  • __tests__/components/waves/specs/groups/group/edit/WaveGroupRemoveModal.test.tsx
  • components/waves/groups/WaveGroups.tsx
  • components/common/TabToggleWithOverflow.tsx
  • __tests__/components/groups/GroupCreateWallets.test.tsx
  • components/utils/select/dropdown/CommonDropdownItemsDefaultWrapper.tsx
  • components/waves/specs/groups/group/edit/WaveGroupRemove.tsx
  • components/groups/page/create/actions/GroupCreateActions.tsx
  • components/groups/page/list/card/actions/delete/GroupCardDeleteModal.tsx
  • components/utils/select/dropdown/CommonDropdownItem.tsx
  • components/utils/input/identity/IdentitySearch.tsx
  • components/groups/page/create/actions/GroupCreateTest.tsx
  • components/utils/input/profile-search/CommonProfileSearchItems.tsx
  • components/waves/specs/groups/group/edit/WaveGroupEdit.tsx
  • components/waves/specs/groups/group/edit/WaveGroupManageIdentitiesModal.tsx
  • __tests__/components/waves/specs/groups/group/edit/WaveGroupRemove.test.tsx
  • __tests__/components/common/TabToggleWithOverflow.test.tsx
  • components/groups/page/create/GroupCreate.tsx
  • __tests__/components/WaveGroupRemoveButton.test.tsx
  • components/groups/page/create/config/wallets/GroupCreateWallets.tsx
  • __tests__/components/groups/page/create/actions/GroupCreateActions.test.tsx
  • components/waves/specs/groups/group/edit/WaveGroupEditButtons.tsx
  • components/waves/specs/groups/group/edit/WaveGroupEditButton.tsx
  • __tests__/components/groups/page/list/card/actions/delete/GroupCardDeleteModal.test.tsx
  • components/waves/specs/groups/group/edit/buttons/subcomponents/WaveGroupManageIdentitiesModals.tsx
  • components/waves/specs/groups/group/edit/WaveGroupRemoveModal.tsx
  • __tests__/components/waves/specs/groups/group/edit/WaveGroupEdit.test.tsx
  • __tests__/components/waves/specs/groups/group/edit/WaveGroupEditButton.test.tsx
  • components/common/CompactMenu.tsx
  • __tests__/components/groups/page/create/actions/GroupCreateTest.test.tsx
  • components/waves/specs/groups/group/edit/WaveGroupRemoveButton.tsx
  • __tests__/components/waves/specs/groups/group/edit/WaveGroupEditButtons.test.tsx
  • components/waves/specs/groups/group/WaveGroup.tsx
  • components/waves/specs/groups/group/edit/buttons/subcomponents/WaveGroupEditMenu.tsx
  • __tests__/components/waves/specs/groups/group/edit/buttons/useWaveGroupEditButtonsController.test.tsx
__tests__/**

📄 CodeRabbit inference engine (tests/AGENTS.md)

Place Jest test suites under the __tests__ directory mirroring source folders (e.g., components, contexts, hooks, utils)

Files:

  • __tests__/components/waves/specs/groups/group/edit/WaveGroupRemoveModal.test.tsx
  • __tests__/components/groups/GroupCreateWallets.test.tsx
  • __tests__/services/groups/groupMutations.test.ts
  • __tests__/components/waves/specs/groups/group/edit/WaveGroupRemove.test.tsx
  • __tests__/components/common/TabToggleWithOverflow.test.tsx
  • __tests__/components/WaveGroupRemoveButton.test.tsx
  • __tests__/components/groups/page/create/actions/GroupCreateActions.test.tsx
  • __tests__/components/groups/page/list/card/actions/delete/GroupCardDeleteModal.test.tsx
  • __tests__/components/waves/specs/groups/group/edit/WaveGroupEdit.test.tsx
  • __tests__/components/waves/specs/groups/group/edit/WaveGroupEditButton.test.tsx
  • __tests__/components/groups/page/create/actions/GroupCreateTest.test.tsx
  • __tests__/components/waves/specs/groups/group/edit/WaveGroupEditButtons.test.tsx
  • __tests__/components/waves/specs/groups/group/edit/buttons/useWaveGroupEditButtonsController.test.tsx
__tests__/components/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (tests/AGENTS.md)

Use @testing-library/react and @testing-library/user-event for React component tests

Files:

  • __tests__/components/waves/specs/groups/group/edit/WaveGroupRemoveModal.test.tsx
  • __tests__/components/groups/GroupCreateWallets.test.tsx
  • __tests__/components/waves/specs/groups/group/edit/WaveGroupRemove.test.tsx
  • __tests__/components/common/TabToggleWithOverflow.test.tsx
  • __tests__/components/WaveGroupRemoveButton.test.tsx
  • __tests__/components/groups/page/create/actions/GroupCreateActions.test.tsx
  • __tests__/components/groups/page/list/card/actions/delete/GroupCardDeleteModal.test.tsx
  • __tests__/components/waves/specs/groups/group/edit/WaveGroupEdit.test.tsx
  • __tests__/components/waves/specs/groups/group/edit/WaveGroupEditButton.test.tsx
  • __tests__/components/groups/page/create/actions/GroupCreateTest.test.tsx
  • __tests__/components/waves/specs/groups/group/edit/WaveGroupEditButtons.test.tsx
  • __tests__/components/waves/specs/groups/group/edit/buttons/useWaveGroupEditButtonsController.test.tsx
{**/__tests__/**,**/*.test.tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Place tests in tests directories or alongside components as ComponentName.test.tsx

Files:

  • __tests__/components/waves/specs/groups/group/edit/WaveGroupRemoveModal.test.tsx
  • __tests__/components/groups/GroupCreateWallets.test.tsx
  • __tests__/services/groups/groupMutations.test.ts
  • __tests__/components/waves/specs/groups/group/edit/WaveGroupRemove.test.tsx
  • __tests__/components/common/TabToggleWithOverflow.test.tsx
  • __tests__/components/WaveGroupRemoveButton.test.tsx
  • __tests__/components/groups/page/create/actions/GroupCreateActions.test.tsx
  • __tests__/components/groups/page/list/card/actions/delete/GroupCardDeleteModal.test.tsx
  • __tests__/components/waves/specs/groups/group/edit/WaveGroupEdit.test.tsx
  • __tests__/components/waves/specs/groups/group/edit/WaveGroupEditButton.test.tsx
  • __tests__/components/groups/page/create/actions/GroupCreateTest.test.tsx
  • __tests__/components/waves/specs/groups/group/edit/WaveGroupEditButtons.test.tsx
  • __tests__/components/waves/specs/groups/group/edit/buttons/useWaveGroupEditButtonsController.test.tsx
{**/__tests__/**,**/*.test.{ts,tsx}}

📄 CodeRabbit inference engine (AGENTS.md)

Mock external dependencies and APIs in tests

Files:

  • __tests__/components/waves/specs/groups/group/edit/WaveGroupRemoveModal.test.tsx
  • __tests__/components/groups/GroupCreateWallets.test.tsx
  • __tests__/services/groups/groupMutations.test.ts
  • __tests__/components/waves/specs/groups/group/edit/WaveGroupRemove.test.tsx
  • __tests__/components/common/TabToggleWithOverflow.test.tsx
  • __tests__/components/WaveGroupRemoveButton.test.tsx
  • __tests__/components/groups/page/create/actions/GroupCreateActions.test.tsx
  • __tests__/components/groups/page/list/card/actions/delete/GroupCardDeleteModal.test.tsx
  • __tests__/components/waves/specs/groups/group/edit/WaveGroupEdit.test.tsx
  • __tests__/components/waves/specs/groups/group/edit/WaveGroupEditButton.test.tsx
  • __tests__/components/groups/page/create/actions/GroupCreateTest.test.tsx
  • __tests__/components/waves/specs/groups/group/edit/WaveGroupEditButtons.test.tsx
  • __tests__/components/waves/specs/groups/group/edit/buttons/useWaveGroupEditButtonsController.test.tsx
codex/STATE.md

📄 CodeRabbit inference engine (AGENTS.md)

Keep codex/STATE.md in sync with tickets under codex/tickets/ so the board remains auditable

Files:

  • codex/STATE.md
codex/tickets/**/*.md

📄 CodeRabbit inference engine (AGENTS.md)

Author new tickets with the provided template, keep YAML front matter alphabetical, and log timestamped updates as work progresses

Files:

  • codex/tickets/TKT-0010.md
codex/tickets/**

📄 CodeRabbit inference engine (AGENTS.md)

codex/tickets/**: Never edit tickets marked Done; open a new ticket if new scope emerges
Link pull requests back to their tickets and mirror merged PR references in both the ticket log and STATE.md

Files:

  • codex/tickets/TKT-0010.md
🧠 Learnings (3)
📚 Learning: 2025-09-28T12:33:30.950Z
Learnt from: CR
PR: 6529-Collections/6529seize-frontend#0
File: __tests__/AGENTS.md:0-0
Timestamp: 2025-09-28T12:33:30.950Z
Learning: Applies to __tests__/components/**/*.{ts,tsx,js,jsx} : Use `testing-library/react` and `testing-library/user-event` for React component tests

Applied to files:

  • __tests__/components/common/TabToggleWithOverflow.test.tsx
📚 Learning: 2025-10-14T05:39:48.871Z
Learnt from: CR
PR: 6529-Collections/6529seize-frontend#0
File: AGENTS.md:0-0
Timestamp: 2025-10-14T05:39:48.871Z
Learning: Applies to codex/STATE.md : Keep codex/STATE.md in sync with tickets under codex/tickets/ so the board remains auditable

Applied to files:

  • codex/STATE.md
📚 Learning: 2025-10-14T05:39:48.871Z
Learnt from: CR
PR: 6529-Collections/6529seize-frontend#0
File: AGENTS.md:0-0
Timestamp: 2025-10-14T05:39:48.871Z
Learning: Applies to {**/__tests__/**,**/*.test.{ts,tsx}} : Mock external dependencies and APIs in tests

Applied to files:

  • __tests__/components/groups/page/create/actions/GroupCreateActions.test.tsx
🧬 Code graph analysis (30)
components/utils/input/profile-search/CommonProfileSearchItem.tsx (1)
entities/IProfile.ts (1)
  • CommunityMemberMinimal (301-312)
components/waves/groups/WaveGroups.tsx (1)
components/waves/specs/groups/group/WaveGroup.tsx (1)
  • WaveGroup (20-64)
components/common/TabToggleWithOverflow.tsx (1)
components/common/CompactMenu.tsx (1)
  • CompactMenu (68-231)
components/waves/specs/groups/group/edit/WaveGroupRemove.tsx (2)
generated/models/ApiWave.ts (1)
  • ApiWave (27-177)
generated/models/ApiUpdateWaveRequest.ts (1)
  • ApiUpdateWaveRequest (21-95)
components/waves/specs/groups/group/edit/buttons/hooks/useWaveGroupEditButtonsController.ts (10)
services/api/common-api.ts (2)
  • commonApiFetch (18-45)
  • commonApiPost (166-191)
generated/models/ApiGroupFull.ts (1)
  • ApiGroupFull (17-85)
generated/models/ApiCreateGroup.ts (1)
  • ApiCreateGroup (16-49)
generated/models/ApiWave.ts (1)
  • ApiWave (27-177)
generated/models/ApiUpdateWaveRequest.ts (1)
  • ApiUpdateWaveRequest (21-95)
helpers/waves/waves.helpers.ts (1)
  • convertWaveToUpdateWave (24-78)
helpers/AllowlistToolHelpers.ts (1)
  • assertUnreachable (53-58)
services/groups/groupMutations.ts (4)
  • ValidationIssue (8-8)
  • validateGroupPayload (33-72)
  • createGroup (74-88)
  • publishGroup (90-103)
components/waves/specs/groups/group/edit/buttons/utils/waveGroupEdit.ts (2)
  • getScopedGroup (6-24)
  • isGroupAuthor (26-56)
helpers/Helpers.ts (1)
  • wait (787-789)
components/groups/page/create/actions/GroupCreateActions.tsx (1)
hooks/groups/useGroupMutations.ts (2)
  • useGroupMutations (106-355)
  • SubmitArgs (14-19)
components/groups/page/list/card/actions/delete/GroupCardDeleteModal.tsx (1)
hooks/groups/useGroupMutations.ts (1)
  • useGroupMutations (106-355)
components/groups/page/create/actions/GroupCreateTest.tsx (1)
hooks/groups/useGroupMutations.ts (1)
  • useGroupMutations (106-355)
components/utils/input/profile-search/CommonProfileSearchItems.tsx (2)
entities/IProfile.ts (1)
  • CommunityMemberMinimal (301-312)
components/utils/input/profile-search/CommonProfileSearchItem.tsx (1)
  • CommonProfileSearchItem (7-81)
components/waves/specs/groups/group/edit/WaveGroupEdit.tsx (2)
generated/models/ApiWave.ts (1)
  • ApiWave (27-177)
generated/models/ApiUpdateWaveRequest.ts (1)
  • ApiUpdateWaveRequest (21-95)
components/waves/specs/groups/group/edit/WaveGroupManageIdentitiesModal.tsx (1)
components/utils/input/identity/IdentitySearch.tsx (1)
  • IdentitySearch (18-281)
__tests__/services/groups/groupMutations.test.ts (2)
generated/models/ApiCreateGroup.ts (1)
  • ApiCreateGroup (16-49)
services/groups/groupMutations.ts (4)
  • ValidationIssue (8-8)
  • validateGroupPayload (33-72)
  • GROUP_INCLUDE_LIMIT (5-5)
  • GROUP_EXCLUDE_LIMIT (6-6)
__tests__/components/waves/specs/groups/group/edit/WaveGroupRemove.test.tsx (1)
components/waves/specs/groups/group/edit/WaveGroupRemove.tsx (1)
  • WaveGroupRemove (10-109)
hooks/groups/useGroupMutations.ts (3)
generated/models/ApiCreateGroup.ts (1)
  • ApiCreateGroup (16-49)
generated/models/ApiGroupFull.ts (1)
  • ApiGroupFull (17-85)
services/groups/groupMutations.ts (5)
  • ValidationResult (10-13)
  • createGroup (74-88)
  • publishGroup (90-103)
  • hideGroup (105-114)
  • validateGroupPayload (33-72)
components/groups/page/create/GroupCreate.tsx (1)
services/api/common-api.ts (1)
  • commonApiFetch (18-45)
components/waves/specs/groups/group/edit/buttons/utils/waveGroupEdit.ts (3)
generated/models/ApiWave.ts (1)
  • ApiWave (27-177)
generated/models/ApiGroup.ts (1)
  • ApiGroup (16-70)
generated/models/ApiIdentity.ts (1)
  • ApiIdentity (17-169)
__tests__/components/WaveGroupRemoveButton.test.tsx (1)
components/waves/specs/groups/group/edit/WaveGroupRemoveButton.tsx (1)
  • WaveGroupRemoveButton (9-58)
components/waves/specs/groups/group/edit/WaveGroupEditButtons.tsx (7)
generated/models/ApiWave.ts (1)
  • ApiWave (27-177)
components/auth/Auth.tsx (1)
  • AuthContext (83-93)
components/react-query-wrapper/ReactQueryWrapper.tsx (1)
  • ReactQueryWrapperContext (184-212)
components/waves/specs/groups/group/edit/buttons/hooks/useWaveGroupEditButtonsController.ts (1)
  • useWaveGroupEditButtonsController (288-512)
components/distribution-plan-tool/common/CircleLoader.tsx (1)
  • CircleLoader (9-42)
components/waves/specs/groups/group/edit/buttons/subcomponents/WaveGroupEditMenu.tsx (1)
  • WaveGroupEditMenu (28-148)
components/waves/specs/groups/group/edit/buttons/subcomponents/WaveGroupManageIdentitiesModals.tsx (1)
  • WaveGroupManageIdentitiesModals (15-38)
components/waves/specs/groups/group/edit/WaveGroupEditButton.tsx (4)
generated/models/ApiWave.ts (1)
  • ApiWave (27-177)
generated/models/ApiUpdateWaveRequest.ts (1)
  • ApiUpdateWaveRequest (21-95)
components/utils/icons/PencilIcon.tsx (1)
  • PencilIcon (6-33)
components/waves/specs/groups/group/edit/WaveGroupEdit.tsx (1)
  • WaveGroupEdit (9-102)
components/waves/specs/groups/group/edit/buttons/subcomponents/WaveGroupManageIdentitiesModals.tsx (1)
components/waves/specs/groups/group/edit/WaveGroupManageIdentitiesModal.tsx (2)
  • WaveGroupManageIdentitiesConfirmEvent (15-18)
  • WaveGroupManageIdentitiesModal (20-131)
components/waves/create-wave/services/waveGroupService.ts (1)
services/groups/groupMutations.ts (2)
  • createGroup (74-88)
  • publishGroup (90-103)
services/groups/groupMutations.ts (3)
generated/models/ApiCreateGroup.ts (1)
  • ApiCreateGroup (16-49)
generated/models/ApiGroupFull.ts (1)
  • ApiGroupFull (17-85)
services/api/common-api.ts (1)
  • commonApiPost (166-191)
__tests__/components/waves/specs/groups/group/edit/WaveGroupEdit.test.tsx (1)
components/waves/specs/groups/group/edit/WaveGroupEdit.tsx (1)
  • WaveGroupEdit (9-102)
__tests__/components/waves/specs/groups/group/edit/WaveGroupEditButton.test.tsx (1)
components/waves/specs/groups/group/edit/WaveGroupEditButton.tsx (1)
  • WaveGroupEditButton (10-46)
__tests__/components/groups/page/create/actions/GroupCreateTest.test.tsx (3)
services/api/common-api.ts (1)
  • commonApiFetch (18-45)
components/groups/page/create/actions/GroupCreateTest.tsx (1)
  • GroupCreateTest (17-140)
components/auth/Auth.tsx (1)
  • AuthContext (83-93)
components/waves/specs/groups/group/edit/WaveGroupRemoveButton.tsx (2)
generated/models/ApiUpdateWaveRequest.ts (1)
  • ApiUpdateWaveRequest (21-95)
components/waves/specs/groups/group/edit/WaveGroupRemove.tsx (1)
  • WaveGroupRemove (10-109)
__tests__/components/waves/specs/groups/group/edit/WaveGroupEditButtons.test.tsx (1)
components/waves/specs/groups/group/edit/WaveGroupEditButtons.tsx (1)
  • WaveGroupEditButtons (23-85)
components/waves/specs/groups/group/WaveGroup.tsx (1)
components/waves/specs/groups/group/edit/WaveGroupEditButtons.tsx (1)
  • WaveGroupEditButtons (23-85)
components/waves/specs/groups/group/edit/buttons/subcomponents/WaveGroupEditMenu.tsx (5)
generated/models/ApiWave.ts (1)
  • ApiWave (27-177)
generated/models/ApiUpdateWaveRequest.ts (1)
  • ApiUpdateWaveRequest (21-95)
components/common/CompactMenu.tsx (2)
  • CompactMenuItem (13-25)
  • CompactMenu (68-231)
components/waves/specs/groups/group/edit/WaveGroupEditButton.tsx (1)
  • WaveGroupEditButton (10-46)
components/waves/specs/groups/group/edit/WaveGroupRemoveButton.tsx (1)
  • WaveGroupRemoveButton (9-58)
__tests__/components/waves/specs/groups/group/edit/buttons/useWaveGroupEditButtonsController.test.tsx (1)
components/waves/specs/groups/group/edit/buttons/hooks/useWaveGroupEditButtonsController.ts (1)
  • useWaveGroupEditButtonsController (288-512)
🪛 LanguageTool
codex/tickets/TKT-0010.md

[style] ~39-~39: This is not the usual sequence for adjectives that have no special emphasis.
Context: ...opdown wrapper/item styles to match the new compact menu look across the app. - 2025-10-15T...

(EN_ADJ_ORDER)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Analyze (javascript-typescript)

Comment thread components/common/TabToggleWithOverflow.tsx Outdated
Comment thread components/waves/specs/groups/group/edit/WaveGroupManageIdentitiesModal.tsx Outdated
Signed-off-by: Simo <simo@6529.io>
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (3)
components/groups/page/list/card/actions/delete/GroupCardDeleteModal.tsx (1)

32-60: Missing onClose() call after successful deletion.

The onDelete function handles the deletion flow correctly but does not close the modal on success. After showing the toast and updating state (lines 52-59), the function should invoke onClose() to dismiss the modal.

Apply this diff to close the modal after successful deletion:

     setToast({
       message: "Group deleted.",
       type: "warning",
     });
     onGroupRemoved({ groupId: group.id });
     if (activeGroupId === group.id) {
       dispatch(setActiveGroupId(null));
     }
+    onClose();
   };
components/common/TabToggleWithOverflow.tsx (2)

90-100: Use FontAwesome for the chevron icon.

Replace the inline SVG with faChevronDown to align with project guidelines.

Apply this diff:

-import React from "react";
+import React from "react";
 import { CompactMenu } from "./CompactMenu";
 import clsx from "clsx";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { faChevronDown } from "@fortawesome/free-solid-svg-icons";

And replace the SVG:

               <span
                 className={clsx(
                   "tw-ml-0.5 tw-inline-flex tw-transition-transform tw-duration-200",
                   isOpen ? "tw-rotate-180" : "",
                 )}
               >
-                <svg
-                  aria-hidden="true"
-                  width="10"
-                  height="6"
-                  viewBox="0 0 8 4"
-                  fill="none"
-                  xmlns="http://www.w3.org/2000/svg"
-                  className="tw-opacity-70"
-                >
-                  <path d="M4 4L0 0H8L4 4Z" fill="currentColor" />
-                </svg>
+                <FontAwesomeIcon 
+                  icon={faChevronDown} 
+                  className="tw-h-3 tw-w-3 tw-opacity-70" 
+                  aria-hidden="true"
+                />
               </span>

Based on coding guidelines


109-110: Remove tab semantics from menu items.

Menu items should not use role="tab" or ariaSelected. These ARIA attributes are semantically incorrect for menu items and violate accessibility guidelines.

Apply this diff:

 items={overflowTabs.map((option) => ({
   id: option.key,
   label: option.label,
   onSelect: () => handleSelect(option.key),
   active: activeKey === option.key,
-  role: "tab",
-  ariaSelected: activeKey === option.key,
 }))}
🧹 Nitpick comments (1)
components/common/CompactMenu.tsx (1)

14-26: Consider restricting role and ariaSelected to prevent semantic misuse.

Allowing arbitrary role and ariaSelected enables consumers to apply incorrect ARIA semantics (e.g., role="tab" on menu items). Consider either removing these props or constraining them to valid menu-related values.

For safer semantics, you could:

  1. Remove role and ariaSelected entirely (recommended — Headless UI Menu already provides correct semantics)
  2. Or constrain role to menu-appropriate values: role?: "menuitem" | "menuitemradio" | "menuitemcheckbox"
 export interface CompactMenuItem {
   readonly id: string;
   readonly label: ReactNode;
   readonly icon?: ReactNode;
   readonly onSelect?: () => void;
   readonly disabled?: boolean;
   readonly className?: string;
   readonly active?: boolean;
   readonly "data-testid"?: string;
-  readonly role?: string;
-  readonly ariaSelected?: boolean;
   readonly ariaLabel?: string;
 }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4acfcb6 and af9af8a.

📒 Files selected for processing (3)
  • components/common/CompactMenu.tsx (1 hunks)
  • components/common/TabToggleWithOverflow.tsx (2 hunks)
  • components/groups/page/list/card/actions/delete/GroupCardDeleteModal.tsx (4 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{ts,tsx}: Do not include any comments in the code
Use react-query for data fetching
Always add readonly before props

**/*.{ts,tsx}: Use TypeScript for source code and follow existing code style and naming conventions
Adhere to clean code standards as measured by SonarQube

Files:

  • components/common/TabToggleWithOverflow.tsx
  • components/common/CompactMenu.tsx
  • components/groups/page/list/card/actions/delete/GroupCardDeleteModal.tsx
**/*.tsx

📄 CodeRabbit inference engine (.cursorrules)

**/*.tsx: Use FontAwesome for icons
Use TailwindCSS for styling

Implement React components as functional components using hooks (no class components)

Files:

  • components/common/TabToggleWithOverflow.tsx
  • components/common/CompactMenu.tsx
  • components/groups/page/list/card/actions/delete/GroupCardDeleteModal.tsx
🧠 Learnings (1)
📚 Learning: 2025-09-28T12:29:11.651Z
Learnt from: CR
PR: 6529-Collections/6529seize-frontend#0
File: .cursorrules:0-0
Timestamp: 2025-09-28T12:29:11.651Z
Learning: Applies to **/*.tsx : Use FontAwesome for icons

Applied to files:

  • components/common/TabToggleWithOverflow.tsx
🧬 Code graph analysis (2)
components/common/TabToggleWithOverflow.tsx (1)
components/common/CompactMenu.tsx (1)
  • CompactMenu (71-234)
components/groups/page/list/card/actions/delete/GroupCardDeleteModal.tsx (1)
hooks/groups/useGroupMutations.ts (1)
  • useGroupMutations (106-355)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (9)
components/groups/page/list/card/actions/delete/GroupCardDeleteModal.tsx (3)

28-30: LGTM! Centralized mutation hook correctly integrated.

The useGroupMutations hook is properly configured with requestAuth, and the returned updateVisibility function and isUpdatingVisibility state are correctly destructured for use in the component.


124-153: LGTM! Delete button loading state properly implemented.

The button correctly:

  • Disables during isUpdatingVisibility
  • Applies appropriate styling for disabled/active states
  • Hides button text and displays a spinner during the operation

154-164: LGTM! Cancel button correctly disabled during operation.

The Cancel button is properly disabled during isUpdatingVisibility to prevent the user from closing the modal mid-operation.

components/common/TabToggleWithOverflow.tsx (1)

3-5: LGTM!

The imports are appropriate for the refactored implementation using CompactMenu.

components/common/CompactMenu.tsx (5)

1-12: LGTM!

Imports are appropriate and include necessary Headless UI primitives.


53-69: LGTM!

Default styling constants are well-organized and use TailwindCSS appropriately.


95-118: Excellent focus management implementation.

The focusInitialMenuItem callback uses a robust pattern:

  • Prioritizes active items for initial focus
  • Uses requestAnimationFrame to ensure DOM readiness
  • Checks isConnected to avoid focusing removed elements

This prevents common focus-related edge cases.


183-220: LGTM with caveat.

The button implementation correctly handles disabled state, click events, and styling composition. However, the passthrough of item.role and item.ariaSelected (lines 189-190) enables semantic misuse, as flagged in the interface definition.


236-253: LGTM!

The FocusManager helper cleanly isolates focus management side effects and correctly tracks state transitions to focus only when the menu opens.

Signed-off-by: Simo <simo@6529.io>
Signed-off-by: Simo <simo@6529.io>
Signed-off-by: Simo <simo@6529.io>
Signed-off-by: Simo <simo@6529.io>
Signed-off-by: Simo <simo@6529.io>
Signed-off-by: Simo <simo@6529.io>
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
components/utils/input/profile-search/CommonProfileSearchItem.tsx (1)

63-79: Use FontAwesome for the selected checkmark icon (repo standard).

Replace inline SVG with FontAwesome.

As per coding guidelines

+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { faCheck } from "@fortawesome/free-solid-svg-icons";
@@
-          {isSelected && (
-            <svg
-              className="tw-flex-shrink-0 tw-h-5 tw-w-5 tw-text-primary-300 tw-transition tw-duration-300 tw-ease-out"
-              viewBox="0 0 24 24"
-              fill="none"
-              aria-hidden="true"
-              xmlns="http://www.w3.org/2000/svg"
-            >
-              <path
-                d="M20 6L9 17L4 12"
-                stroke="currentColor"
-                strokeWidth="2"
-                strokeLinecap="round"
-                strokeLinejoin="round"
-              />
-            </svg>
-          )}
+          {isSelected && (
+            <FontAwesomeIcon
+              icon={faCheck}
+              className="tw-flex-shrink-0 tw-h-5 tw-w-5 tw-text-primary-300 tw-transition tw-duration-300 tw-ease-out"
+            />
+          )}
♻️ Duplicate comments (1)
components/waves/specs/groups/group/edit/buttons/subcomponents/WaveGroupEditMenu.tsx (1)

4-4: Replace Heroicons with FontAwesome to meet TSX icon guideline.

Swap Cog6ToothIcon with FontAwesomeIcon/faGear.

As per coding guidelines

-import { Cog6ToothIcon } from "@heroicons/react/24/outline";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { faGear } from "@fortawesome/free-solid-svg-icons";
@@
-            <Cog6ToothIcon className="tw-size-5 tw-flex-shrink-0" />
+            <FontAwesomeIcon icon={faGear} className="tw-size-5 tw-flex-shrink-0" />

Also applies to: 139-141

🧹 Nitpick comments (5)
codex/tickets/TKT-0012.md (1)

27-31: Link this ticket to the open PR.

Add the PR URL per ticket guidelines and mirror later in STATE.md after merge.

As per coding guidelines

-## Links
-
-- Primary PR: _(add when available)_
-- Follow-ups: _(reference additional tickets or TODO items)_
+## Links
+
+- Primary PR: https://github.com/6529-Collections/6529seize-frontend/pull/1544
+- Follow-ups: _(reference additional tickets or TODO items)_
__tests__/components/waves/specs/groups/group/edit/buttons/useWaveGroupEditButtonsController.test.tsx (1)

131-298: Good coverage of include/exclude flows; add failure-path tests.

Add tests for:

  • requestAuth() returning { success: false } → shows "Failed to authenticate", no mutations.
  • validateGroupPayload invalid → shows mapped error, no create/publish.
  • API failures (createGroup/publishGroup) → error toast when wave mutation not triggered.

I can draft these if helpful.

components/waves/specs/groups/group/edit/WaveGroupEditButtons.tsx (1)

76-83: Memoize onConfirm mapping to avoid re-renders.

Small optimization: wrap the mode-mapping handler in useCallback.

-import { useContext } from "react";
+import { useContext, useCallback } from "react";
@@
-      <WaveGroupManageIdentitiesModals
+      <WaveGroupManageIdentitiesModals
         activeModal={activeIdentitiesModal}
         onClose={closeIdentitiesModal}
-        onConfirm={({ identity, mode }) => {
-          const action =
-            mode === WaveGroupManageIdentitiesMode.INCLUDE
-              ? WaveGroupIdentitiesModal.INCLUDE
-              : WaveGroupIdentitiesModal.EXCLUDE;
-          onIdentityConfirm({ identity, mode: action });
-        }}
+        onConfirm={useCallback(
+          ({ identity, mode }) => {
+            const action =
+              mode === WaveGroupManageIdentitiesMode.INCLUDE
+                ? WaveGroupIdentitiesModal.INCLUDE
+                : WaveGroupIdentitiesModal.EXCLUDE;
+            onIdentityConfirm({ identity, mode: action });
+          },
+          [onIdentityConfirm],
+        )}
       />
components/waves/specs/groups/group/edit/buttons/hooks/useWaveGroupEditButtonsController.ts (1)

408-548: Include editWaveMutation in the onIdentityConfirm dependency array.

The callback uses editWaveMutation.mutateAsync at line 515 but doesn't list editWaveMutation in the dependency array at lines 540-547. While useMutation returns a stable reference, React's exhaustive-deps rule expects it to be included for completeness.

Apply this diff:

   [
     requestAuth,
     scopedGroup,
     setToast,
     type,
     wave,
     onWaveCreated,
+    editWaveMutation,
   ],
__tests__/components/waves/specs/groups/group/edit/WaveGroupEditButtons.test.tsx (1)

140-157: Move the @headlessui/react mock to the top of the file.

The mock is defined after the test suite ends (line 139). While jest.mock is hoisted and will work, convention is to place all mocks near the top with other jest.mock calls (lines 9-43) for clarity.

Move this block to line 44 (before the mutateAsync setup).

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between af9af8a and 0fdbc12.

📒 Files selected for processing (13)
  • __tests__/components/utils/input/identity/IdentitySearch.test.tsx (1 hunks)
  • __tests__/components/waves/specs/groups/group/edit/WaveGroupEditButtons.test.tsx (3 hunks)
  • __tests__/components/waves/specs/groups/group/edit/buttons/useWaveGroupEditButtonsController.test.tsx (1 hunks)
  • __tests__/services/groups/groupMutations.test.ts (1 hunks)
  • codex/STATE.md (1 hunks)
  • codex/tickets/TKT-0011.md (1 hunks)
  • codex/tickets/TKT-0012.md (1 hunks)
  • components/utils/input/identity/IdentitySearch.tsx (4 hunks)
  • components/utils/input/profile-search/CommonProfileSearchItem.tsx (2 hunks)
  • components/waves/specs/groups/group/edit/WaveGroupEditButtons.tsx (1 hunks)
  • components/waves/specs/groups/group/edit/buttons/hooks/useWaveGroupEditButtonsController.ts (1 hunks)
  • components/waves/specs/groups/group/edit/buttons/subcomponents/WaveGroupEditMenu.tsx (1 hunks)
  • services/groups/groupMutations.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
  • tests/services/groups/groupMutations.test.ts
  • services/groups/groupMutations.ts
  • codex/STATE.md
  • codex/tickets/TKT-0011.md
🧰 Additional context used
📓 Path-based instructions (8)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{ts,tsx}: Do not include any comments in the code
Use react-query for data fetching
Always add readonly before props

**/*.{ts,tsx}: Use TypeScript for source code and follow existing code style and naming conventions
Adhere to clean code standards as measured by SonarQube

Files:

  • __tests__/components/utils/input/identity/IdentitySearch.test.tsx
  • components/utils/input/profile-search/CommonProfileSearchItem.tsx
  • __tests__/components/waves/specs/groups/group/edit/WaveGroupEditButtons.test.tsx
  • components/utils/input/identity/IdentitySearch.tsx
  • __tests__/components/waves/specs/groups/group/edit/buttons/useWaveGroupEditButtonsController.test.tsx
  • components/waves/specs/groups/group/edit/buttons/subcomponents/WaveGroupEditMenu.tsx
  • components/waves/specs/groups/group/edit/buttons/hooks/useWaveGroupEditButtonsController.ts
  • components/waves/specs/groups/group/edit/WaveGroupEditButtons.tsx
**/*.tsx

📄 CodeRabbit inference engine (.cursorrules)

**/*.tsx: Use FontAwesome for icons
Use TailwindCSS for styling

Implement React components as functional components using hooks (no class components)

Files:

  • __tests__/components/utils/input/identity/IdentitySearch.test.tsx
  • components/utils/input/profile-search/CommonProfileSearchItem.tsx
  • __tests__/components/waves/specs/groups/group/edit/WaveGroupEditButtons.test.tsx
  • components/utils/input/identity/IdentitySearch.tsx
  • __tests__/components/waves/specs/groups/group/edit/buttons/useWaveGroupEditButtonsController.test.tsx
  • components/waves/specs/groups/group/edit/buttons/subcomponents/WaveGroupEditMenu.tsx
  • components/waves/specs/groups/group/edit/WaveGroupEditButtons.tsx
__tests__/**

📄 CodeRabbit inference engine (tests/AGENTS.md)

Place Jest test suites under the __tests__ directory mirroring source folders (e.g., components, contexts, hooks, utils)

Files:

  • __tests__/components/utils/input/identity/IdentitySearch.test.tsx
  • __tests__/components/waves/specs/groups/group/edit/WaveGroupEditButtons.test.tsx
  • __tests__/components/waves/specs/groups/group/edit/buttons/useWaveGroupEditButtonsController.test.tsx
__tests__/components/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (tests/AGENTS.md)

Use @testing-library/react and @testing-library/user-event for React component tests

Files:

  • __tests__/components/utils/input/identity/IdentitySearch.test.tsx
  • __tests__/components/waves/specs/groups/group/edit/WaveGroupEditButtons.test.tsx
  • __tests__/components/waves/specs/groups/group/edit/buttons/useWaveGroupEditButtonsController.test.tsx
{**/__tests__/**,**/*.test.tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Place tests in tests directories or alongside components as ComponentName.test.tsx

Files:

  • __tests__/components/utils/input/identity/IdentitySearch.test.tsx
  • __tests__/components/waves/specs/groups/group/edit/WaveGroupEditButtons.test.tsx
  • __tests__/components/waves/specs/groups/group/edit/buttons/useWaveGroupEditButtonsController.test.tsx
{**/__tests__/**,**/*.test.{ts,tsx}}

📄 CodeRabbit inference engine (AGENTS.md)

Mock external dependencies and APIs in tests

Files:

  • __tests__/components/utils/input/identity/IdentitySearch.test.tsx
  • __tests__/components/waves/specs/groups/group/edit/WaveGroupEditButtons.test.tsx
  • __tests__/components/waves/specs/groups/group/edit/buttons/useWaveGroupEditButtonsController.test.tsx
codex/tickets/**/*.md

📄 CodeRabbit inference engine (AGENTS.md)

Author new tickets with the provided template, keep YAML front matter alphabetical, and log timestamped updates as work progresses

Files:

  • codex/tickets/TKT-0012.md
codex/tickets/**

📄 CodeRabbit inference engine (AGENTS.md)

codex/tickets/**: Never edit tickets marked Done; open a new ticket if new scope emerges
Link pull requests back to their tickets and mirror merged PR references in both the ticket log and STATE.md

Files:

  • codex/tickets/TKT-0012.md
🧠 Learnings (1)
📚 Learning: 2025-09-28T12:29:11.651Z
Learnt from: CR
PR: 6529-Collections/6529seize-frontend#0
File: .cursorrules:0-0
Timestamp: 2025-09-28T12:29:11.651Z
Learning: Applies to **/*.tsx : Use FontAwesome for icons

Applied to files:

  • components/waves/specs/groups/group/edit/buttons/subcomponents/WaveGroupEditMenu.tsx
🧬 Code graph analysis (8)
__tests__/components/utils/input/identity/IdentitySearch.test.tsx (1)
components/utils/input/identity/IdentitySearch.tsx (1)
  • IdentitySearch (32-329)
components/utils/input/profile-search/CommonProfileSearchItem.tsx (1)
entities/IProfile.ts (1)
  • CommunityMemberMinimal (301-312)
__tests__/components/waves/specs/groups/group/edit/WaveGroupEditButtons.test.tsx (1)
components/waves/specs/groups/group/edit/WaveGroupEditButtons.tsx (1)
  • WaveGroupEditButtons (23-86)
components/utils/input/identity/IdentitySearch.tsx (1)
entities/IProfile.ts (1)
  • CommunityMemberMinimal (301-312)
__tests__/components/waves/specs/groups/group/edit/buttons/useWaveGroupEditButtonsController.test.tsx (1)
components/waves/specs/groups/group/edit/buttons/hooks/useWaveGroupEditButtonsController.ts (1)
  • useWaveGroupEditButtonsController (308-561)
components/waves/specs/groups/group/edit/buttons/subcomponents/WaveGroupEditMenu.tsx (5)
generated/models/ApiWave.ts (1)
  • ApiWave (27-177)
generated/models/ApiUpdateWaveRequest.ts (1)
  • ApiUpdateWaveRequest (21-95)
components/common/CompactMenu.tsx (2)
  • CompactMenuItem (14-26)
  • CompactMenu (71-234)
components/waves/specs/groups/group/edit/WaveGroupEditButton.tsx (1)
  • WaveGroupEditButton (10-46)
components/waves/specs/groups/group/edit/WaveGroupRemoveButton.tsx (1)
  • WaveGroupRemoveButton (9-58)
components/waves/specs/groups/group/edit/buttons/hooks/useWaveGroupEditButtonsController.ts (4)
services/api/common-api.ts (2)
  • commonApiFetch (18-45)
  • commonApiPost (166-191)
helpers/waves/waves.helpers.ts (1)
  • convertWaveToUpdateWave (24-78)
services/groups/groupMutations.ts (4)
  • ValidationIssue (8-8)
  • validateGroupPayload (33-83)
  • createGroup (85-99)
  • publishGroup (101-114)
components/waves/specs/groups/group/edit/buttons/utils/waveGroupEdit.ts (2)
  • getScopedGroup (6-24)
  • isGroupAuthor (26-56)
components/waves/specs/groups/group/edit/WaveGroupEditButtons.tsx (7)
generated/models/ApiWave.ts (1)
  • ApiWave (27-177)
components/auth/Auth.tsx (1)
  • AuthContext (83-93)
components/react-query-wrapper/ReactQueryWrapper.tsx (1)
  • ReactQueryWrapperContext (184-212)
components/waves/specs/groups/group/edit/buttons/hooks/useWaveGroupEditButtonsController.ts (1)
  • useWaveGroupEditButtonsController (308-561)
components/distribution-plan-tool/common/CircleLoader.tsx (1)
  • CircleLoader (9-42)
components/waves/specs/groups/group/edit/buttons/subcomponents/WaveGroupEditMenu.tsx (1)
  • WaveGroupEditMenu (29-152)
components/waves/specs/groups/group/edit/buttons/subcomponents/WaveGroupManageIdentitiesModals.tsx (1)
  • WaveGroupManageIdentitiesModals (15-38)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (7)
codex/tickets/TKT-0012.md (1)

1-8: Front matter looks correct.

Keys are alphabetical; template respected.

components/waves/specs/groups/group/edit/buttons/hooks/useWaveGroupEditButtonsController.ts (1)

76-113: Well-structured identity management flow.

The helper functions cleanly separate concerns (payload cloning, identity normalization, validation mapping), and the main onIdentityConfirm callback orchestrates the create-publish-update sequence correctly with proper error handling and state management.

Also applies to: 219-251, 408-548

__tests__/components/waves/specs/groups/group/edit/WaveGroupEditButtons.test.tsx (1)

62-90: Test coverage aligns well with the new menu-driven UI.

The fixtures properly model group relationships across visibility, participation, voting, chat, and admin scopes, and the test cases correctly verify menu interactions, permission-based rendering (ADMIN type hiding remove option), and conditional labels (Add vs. Change group).

Also applies to: 97-138

__tests__/components/utils/input/identity/IdentitySearch.test.tsx (1)

16-16: Test correctly validates typing-driven dropdown behavior.

The updated test verifies that the dropdown remains closed on focus (line 24) and only opens after typing begins (lines 25-26), matching the new interaction model.

Also applies to: 20-20, 24-25

components/utils/input/identity/IdentitySearch.tsx (3)

185-190: Form submission pattern is correct.

Using setTimeout with zero delay to defer formElement.requestSubmit() ensures state updates complete before submission. This is a standard pattern for programmatic form submission after state changes.


141-193: Keyboard navigation implementation is solid.

The arrow key handlers correctly manage index wrapping (lines 151-156, 163-168), and the Enter key handler (lines 172-192) properly prevents default, derives the selectable identity, updates state, and triggers form submission. The logic handles edge cases (no data, invalid highlight) appropriately.


18-30: Well-designed identity selection flow.

The getSelectableIdentity helper prioritizes primary_wallet over wallet over handle, the selectionUpdateRef pattern prevents race conditions between controlled prop updates and user selections, and the highlightedIndex synchronization effect (lines 201-227) handles data changes, identity matching, and index clamping correctly.

Also applies to: 89-97, 201-227

Signed-off-by: Simo <simo@6529.io>
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (2)
components/waves/specs/groups/group/edit/buttons/hooks/useWaveGroupEditButtonsController.ts (2)

540-547: Consider including editWaveMutation for consistency.

The updateWave callback's dependency array (line 383) includes editWaveMutation, but this callback doesn't. While useMutation returns a stable reference making both approaches correct, adding it here would maintain consistency across the hook.

Apply this diff:

   [
     requestAuth,
     scopedGroup,
     setToast,
     type,
     wave,
     onWaveCreated,
+    editWaveMutation,
   ],

416-419: Consider client-side validation for identity format.

The identity is normalized but not validated for wallet address or handle format. While the API will reject invalid inputs, client-side validation could provide earlier feedback and better UX.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0fdbc12 and 7fadaee.

📒 Files selected for processing (2)
  • components/utils/input/identity/IdentitySearch.tsx (4 hunks)
  • components/waves/specs/groups/group/edit/buttons/hooks/useWaveGroupEditButtonsController.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • components/utils/input/identity/IdentitySearch.tsx
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{ts,tsx}: Do not include any comments in the code
Use react-query for data fetching
Always add readonly before props

**/*.{ts,tsx}: Use TypeScript for source code and follow existing code style and naming conventions
Adhere to clean code standards as measured by SonarQube

Files:

  • components/waves/specs/groups/group/edit/buttons/hooks/useWaveGroupEditButtonsController.ts
🧬 Code graph analysis (1)
components/waves/specs/groups/group/edit/buttons/hooks/useWaveGroupEditButtonsController.ts (4)
services/api/common-api.ts (2)
  • commonApiFetch (18-45)
  • commonApiPost (166-191)
helpers/waves/waves.helpers.ts (1)
  • convertWaveToUpdateWave (24-78)
services/groups/groupMutations.ts (4)
  • ValidationIssue (8-8)
  • validateGroupPayload (33-83)
  • createGroup (85-99)
  • publishGroup (101-114)
components/waves/specs/groups/group/edit/buttons/utils/waveGroupEdit.ts (2)
  • getScopedGroup (6-24)
  • isGroupAuthor (26-56)
🔇 Additional comments (6)
components/waves/specs/groups/group/edit/buttons/hooks/useWaveGroupEditButtonsController.ts (6)

334-337: LGTM: Permission logic is sound.

The permission checks correctly require an existing group for inclusion while allowing exclusion without one. The runtime safety check at lines 421-431 provides an additional guard.


369-384: LGTM: Authentication gating and state management.

The callback properly gates the mutation behind authentication and manages the mutating state correctly, including cleanup on auth failure.


386-395: LGTM: Modal cleanup on permission changes.

This effect correctly closes the modal when the user loses the required permissions, preventing invalid UI states.


408-548: LGTM: Comprehensive identity management flow.

The logic correctly handles both existing and new group scenarios, applies identity changes with proper normalization, validates payloads, and coordinates group creation/publishing with wave updates. The waveMutationTriggered flag prevents duplicate error toasts when the wave mutation has its own error handler.


271-306: LGTM: Well-defined type system.

The type definitions are comprehensive and properly use readonly on all interface properties per coding guidelines. The enum and interfaces provide clear contracts for the hook's API surface.


115-202: LGTM: Exhaustive type switching with proper defaults.

Both getGroupIdFromUpdateBody and buildWaveUpdateBody use assertUnreachable in their default cases, ensuring compile-time exhaustiveness checking when WaveGroupType is extended.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (1)
components/utils/input/profile-search/CommonProfileSearchItems.tsx (1)

63-68: Fix ID mismatch breaking aria-activedescendant.

The highlightedOptionId uses the base optionId (line 67), but the actual DOM elements rendered at line 161 have IDs with a -visual suffix (line 156). When this mismatched ID is passed to the parent via onHighlightedOptionIdChange (line 78) and used in aria-activedescendant, it won't reference any existing DOM element, breaking screen reader navigation.

Apply this diff:

   const highlightedOptionId =
     highlightedIndex !== null &&
     highlightedIndex >= 0 &&
     highlightedIndex < optionMetadata.length
-      ? optionMetadata[highlightedIndex].optionId
+      ? `${optionMetadata[highlightedIndex].optionId}-visual`
       : undefined;
🧹 Nitpick comments (2)
components/common/TabToggleWithOverflow.tsx (1)

181-190: Memoize activeOption to avoid redundant lookups.

The options.find() call on line 182 executes inside the trigger render function, which runs every time the menu opens or closes. This linear search is unnecessarily repeated on each state change.

Apply this diff to compute activeOption once:

+  const activeOption = React.useMemo(
+    () => options.find((option) => option.key === activeKey),
+    [options, activeKey],
+  );
+
   {overflowTabs.length > 0 && (
     <CompactMenu
       ...
       trigger={({ isOpen }) => {
-        const activeOption = options.find((option) => option.key === activeKey);
         return (
           <OverflowTrigger
             isOpen={isOpen}
components/waves/specs/groups/group/edit/buttons/hooks/useWaveGroupEditButtonsController.ts (1)

608-753: Consider reducing redundant mutating state updates.

The identity confirmation flow is well-structured with proper error handling and the waveMutationTriggered flag correctly prevents duplicate error toasts. However, when updateWave is called (line 706), the mutating state is managed by both:

  • editWaveMutation.onSettled (line 562) → sets mutating = false
  • finally block (line 736) → also sets mutating = false

This results in redundant state updates in both success and error paths when wave updates are triggered. While idempotent and functionally correct, you could refactor to avoid the redundancy by either:

  • Conditionally setting mutating in the finally block only when !waveMutationTriggered
  • Removing setMutating(false) from onSettled and managing it entirely in the caller
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 069b1cb and 3f1cee1.

📒 Files selected for processing (5)
  • codex/tickets/TKT-0012.md (1 hunks)
  • components/common/TabToggleWithOverflow.tsx (3 hunks)
  • components/utils/input/profile-search/CommonProfileSearchItem.tsx (1 hunks)
  • components/utils/input/profile-search/CommonProfileSearchItems.tsx (1 hunks)
  • components/waves/specs/groups/group/edit/buttons/hooks/useWaveGroupEditButtonsController.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{ts,tsx}: Do not include any comments in the code
Use react-query for data fetching
Always add readonly before props

Use TypeScript across the codebase

Files:

  • components/waves/specs/groups/group/edit/buttons/hooks/useWaveGroupEditButtonsController.ts
  • components/common/TabToggleWithOverflow.tsx
  • components/utils/input/profile-search/CommonProfileSearchItems.tsx
  • components/utils/input/profile-search/CommonProfileSearchItem.tsx
**/*.tsx

📄 CodeRabbit inference engine (.cursorrules)

**/*.tsx: Use FontAwesome for icons
Use TailwindCSS for styling

Use React functional components with hooks for UI components

Files:

  • components/common/TabToggleWithOverflow.tsx
  • components/utils/input/profile-search/CommonProfileSearchItems.tsx
  • components/utils/input/profile-search/CommonProfileSearchItem.tsx
🧠 Learnings (2)
📚 Learning: 2025-09-28T12:29:11.651Z
Learnt from: CR
PR: 6529-Collections/6529seize-frontend#0
File: .cursorrules:0-0
Timestamp: 2025-09-28T12:29:11.651Z
Learning: Applies to **/*.{ts,tsx} : Always add readonly before props

Applied to files:

  • components/waves/specs/groups/group/edit/buttons/hooks/useWaveGroupEditButtonsController.ts
📚 Learning: 2025-09-28T12:29:11.651Z
Learnt from: CR
PR: 6529-Collections/6529seize-frontend#0
File: .cursorrules:0-0
Timestamp: 2025-09-28T12:29:11.651Z
Learning: Applies to **/*.tsx : Use FontAwesome for icons

Applied to files:

  • components/common/TabToggleWithOverflow.tsx
🧬 Code graph analysis (4)
components/waves/specs/groups/group/edit/buttons/hooks/useWaveGroupEditButtonsController.ts (8)
services/api/common-api.ts (2)
  • commonApiFetch (18-45)
  • commonApiPost (166-191)
generated/models/ApiGroupFull.ts (1)
  • ApiGroupFull (17-85)
generated/models/ApiCreateGroup.ts (1)
  • ApiCreateGroup (16-49)
generated/models/ApiWave.ts (1)
  • ApiWave (27-177)
services/groups/groupMutations.ts (5)
  • ValidationIssue (8-15)
  • validateGroupPayload (52-134)
  • toErrorMessage (22-30)
  • createGroup (136-153)
  • publishGroup (155-171)
components/waves/specs/groups/group/edit/buttons/utils/waveGroupEdit.ts (4)
  • getScopedGroup (31-49)
  • isGroupAuthor (51-81)
  • getGroupIdFromUpdateBody (26-29)
  • buildWaveUpdateBody (17-24)
generated/models/ApiIdentity.ts (1)
  • ApiIdentity (17-169)
generated/models/ApiUpdateWaveRequest.ts (1)
  • ApiUpdateWaveRequest (21-95)
components/common/TabToggleWithOverflow.tsx (2)
components/compact-menu/index.tsx (1)
  • CompactMenu (12-63)
i18n/messages.ts (1)
  • TAB_TOGGLE_WITH_OVERFLOW_MESSAGES (1-4)
components/utils/input/profile-search/CommonProfileSearchItems.tsx (3)
entities/IProfile.ts (1)
  • CommunityMemberMinimal (301-312)
components/utils/input/profile-search/getSelectableIdentity.ts (1)
  • getSelectableIdentity (3-16)
components/utils/input/profile-search/CommonProfileSearchItem.tsx (1)
  • CommonProfileSearchItem (8-92)
components/utils/input/profile-search/CommonProfileSearchItem.tsx (3)
entities/IProfile.ts (1)
  • CommunityMemberMinimal (301-312)
components/utils/input/profile-search/getSelectableIdentity.ts (1)
  • getSelectableIdentity (3-16)
helpers/image.helpers.ts (1)
  • getScaledImageUri (14-42)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (9)
codex/tickets/TKT-0012.md (1)

1-55: Document is well-structured and properly maintained.

The ticket follows the established format with clear metadata, contextual sections, and a detailed audit log. All timestamps are in chronological order and use appropriate past tense. Previous review findings (future-dated entries, "Planned:" prefix anomalies) have been resolved. The "In-Progress" status correctly reflects that implementation tasks are largely complete while acceptance validation remains pending.

components/utils/input/profile-search/CommonProfileSearchItems.tsx (1)

121-136: Remove this review comment—the architectural concern is based on incorrect assumptions.

The file does not use aria-activedescendant. The native select and visual list are cleanly separated with no cross-linking: the native select provides screen reader access with base IDs, and the visual list (aria-hidden="true") serves sighted users with -visual suffix IDs. This is proper progressive enhancement, not a conflicting hybrid pattern. Previous datalist removal is confirmed (no datalist found in directory).

Likely an incorrect or invalid review comment.

components/waves/specs/groups/group/edit/buttons/hooks/useWaveGroupEditButtonsController.ts (7)

1-77: LGTM! Clean utility functions and imports.

The import structure, constants, and utility functions are well-organized. The deduplication logic is sound, and fetchIdentityGroupWallets appropriately handles errors by returning an empty array rather than throwing.


79-190: LGTM! Correct payload normalization and abort handling.

The waitWithAbort implementation properly handles abort signals with cleanup. The payload builders correctly initialize identity_addresses and excluded_identity_addresses to null (matching the past review fix), and applyIdentityChangeToPayload consistently normalizes empty arrays to null for API compatibility.


192-335: LGTM! Well-structured validation and helper functions.

The validation helpers provide clear, actionable error messages. The permission checks are thorough, and buildIdentityPayload correctly handles both empty group creation and cloning existing groups with parallel wallet loading.


337-392: LGTM! Robust polling with exponential backoff.

The polling implementation uses appropriate timeouts (10s max) and exponential backoff. Swallowing non-abort errors during polling is correct given potential propagation delays. The timeout warning is logged, and cleanup is properly handled.


394-432: LGTM! Type definitions follow coding guidelines.

All type and interface properties correctly use readonly modifiers per repository guidelines. The type structure is clear and well-organized.


434-595: LGTM! Solid React Query integration and state management.

The hook setup is clean with proper cleanup, complete dependency arrays, and correct React Query usage. The error handling uses toErrorMessage consistently (line 557), and the modal state properly reacts to permission changes.


755-765: LGTM! Complete controller return.

The return object correctly exposes all controller methods and state, matching the WaveGroupEditButtonsController interface.

Comment thread components/utils/input/profile-search/CommonProfileSearchItem.tsx
Comment thread components/utils/input/profile-search/CommonProfileSearchItems.tsx Outdated
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (3)
components/utils/input/profile-search/CommonProfileSearchItems.tsx (1)

63-68: Critical regression: aria-activedescendant mismatch reintroduced.

The highlightedOptionId returns the base option ID (e.g., profile-search-item-foo-0), but the visual list items rendered at line 161 receive id={visualOptionId} with a -visual suffix (e.g., profile-search-item-foo-0-visual). When the parent component uses this value for aria-activedescendant, it will reference an ID that doesn't exist in the DOM, breaking screen reader navigation.

This is the same issue flagged in a previous review (marked as addressed in commit ebbe291), suggesting a regression.

Apply this diff to append the -visual suffix:

   const highlightedOptionId =
     highlightedIndex !== null &&
     highlightedIndex >= 0 &&
     highlightedIndex < optionMetadata.length
-      ? optionMetadata[highlightedIndex].optionId
+      ? `${optionMetadata[highlightedIndex].optionId}-visual`
       : undefined;
components/common/TabToggleWithOverflow.tsx (2)

3-8: Good: FontAwesome + Tailwind usage matches guidelines.

Imports look correct and align with the “Use FontAwesome for icons” and Tailwind styling rules.


185-191: Chevron rotation likely broken — pass open state into OverflowTrigger.

isOpen isn’t provided to OverflowTrigger, so the chevron never rotates.

If CompactMenu supports a render-function trigger, switch to:

-          trigger={
-            <OverflowTrigger
-              isActiveInOverflow={isActiveInOverflow}
-              activeLabel={activeOption?.label}
-              fallbackLabel={TAB_TOGGLE_WITH_OVERFLOW_MESSAGES.overflowFallbackLabel}
-            />
-          }
+          trigger={(isOpen: boolean) => (
+            <OverflowTrigger
+              isOpen={isOpen}
+              isActiveInOverflow={isActiveInOverflow}
+              activeLabel={activeOption?.label}
+              fallbackLabel={TAB_TOGGLE_WITH_OVERFLOW_MESSAGES.overflowFallbackLabel}
+            />
+          )}

Please confirm CompactMenuProps["trigger"] supports (isOpen: boolean) => React.ReactNode; if not, I can suggest an alternative patch.

🧹 Nitpick comments (3)
components/utils/input/profile-search/CommonProfileSearchItems.tsx (1)

70-79: Consider documenting callback stability expectations.

The useEffect includes onHighlightedOptionIdChange in its dependencies. If the parent doesn't memoize this callback, the effect will re-run on every render. While this is functionally correct, consider adding a comment documenting that callers should memoize the callback (e.g., with useCallback) for optimal performance, or omit the callback from dependencies if calling it multiple times with the same value is safe.

components/common/TabToggleWithOverflow.tsx (2)

149-169: Verify ARIA tab semantics or adjust.

If these “tabs” control corresponding tabpanels, ensure each button has aria-controls pointing to a role="tabpanel" with matching id; otherwise consider using role="button" with aria-pressed/current to avoid incomplete tab semantics.


179-184: Add a visible keyboard focus style to the overflow trigger.

Consider a focus ring for accessibility.

Apply this class tweak:

-          triggerClassName={clsx(
-            "tw-flex tw-items-center tw-gap-0.5 tw-border-0 tw-bg-transparent tw-text-sm tw-font-medium tw-transition-all tw-duration-200 tw-whitespace-nowrap",
+          triggerClassName={clsx(
+            "tw-flex tw-items-center tw-gap-0.5 tw-border-0 tw-bg-transparent tw-text-sm tw-font-medium tw-transition-all tw-duration-200 tw-whitespace-nowrap focus-visible:tw-ring-2 focus-visible:tw-ring-primary-400/50 focus-visible:tw-rounded-sm",
             isActiveInOverflow
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3f1cee1 and 96aaaf0.

📒 Files selected for processing (4)
  • components/common/TabToggleWithOverflow.tsx (3 hunks)
  • components/utils/input/profile-search/CommonProfileSearchItem.tsx (1 hunks)
  • components/utils/input/profile-search/CommonProfileSearchItems.tsx (1 hunks)
  • components/waves/specs/groups/group/edit/buttons/hooks/useWaveGroupEditButtonsController.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{ts,tsx}: Do not include any comments in the code
Use react-query for data fetching
Always add readonly before props

Use TypeScript across the codebase

Files:

  • components/utils/input/profile-search/CommonProfileSearchItem.tsx
  • components/waves/specs/groups/group/edit/buttons/hooks/useWaveGroupEditButtonsController.ts
  • components/utils/input/profile-search/CommonProfileSearchItems.tsx
  • components/common/TabToggleWithOverflow.tsx
**/*.tsx

📄 CodeRabbit inference engine (.cursorrules)

**/*.tsx: Use FontAwesome for icons
Use TailwindCSS for styling

Use React functional components with hooks for UI components

Files:

  • components/utils/input/profile-search/CommonProfileSearchItem.tsx
  • components/utils/input/profile-search/CommonProfileSearchItems.tsx
  • components/common/TabToggleWithOverflow.tsx
🧠 Learnings (2)
📚 Learning: 2025-09-28T12:29:11.651Z
Learnt from: CR
PR: 6529-Collections/6529seize-frontend#0
File: .cursorrules:0-0
Timestamp: 2025-09-28T12:29:11.651Z
Learning: Applies to **/*.{ts,tsx} : Always add readonly before props

Applied to files:

  • components/waves/specs/groups/group/edit/buttons/hooks/useWaveGroupEditButtonsController.ts
📚 Learning: 2025-09-28T12:29:11.651Z
Learnt from: CR
PR: 6529-Collections/6529seize-frontend#0
File: .cursorrules:0-0
Timestamp: 2025-09-28T12:29:11.651Z
Learning: Applies to **/*.tsx : Use FontAwesome for icons

Applied to files:

  • components/common/TabToggleWithOverflow.tsx
🧬 Code graph analysis (4)
components/utils/input/profile-search/CommonProfileSearchItem.tsx (3)
entities/IProfile.ts (1)
  • CommunityMemberMinimal (301-312)
components/utils/input/profile-search/getSelectableIdentity.ts (1)
  • getSelectableIdentity (3-16)
helpers/image.helpers.ts (1)
  • getScaledImageUri (14-42)
components/waves/specs/groups/group/edit/buttons/hooks/useWaveGroupEditButtonsController.ts (5)
services/api/common-api.ts (2)
  • commonApiFetch (18-45)
  • commonApiPost (166-191)
generated/models/ApiGroupFull.ts (1)
  • ApiGroupFull (17-85)
generated/models/ApiCreateGroup.ts (1)
  • ApiCreateGroup (16-49)
services/groups/groupMutations.ts (5)
  • ValidationIssue (8-15)
  • validateGroupPayload (52-134)
  • toErrorMessage (22-30)
  • createGroup (136-153)
  • publishGroup (155-171)
components/waves/specs/groups/group/edit/buttons/utils/waveGroupEdit.ts (4)
  • getScopedGroup (31-49)
  • isGroupAuthor (51-81)
  • getGroupIdFromUpdateBody (26-29)
  • buildWaveUpdateBody (17-24)
components/utils/input/profile-search/CommonProfileSearchItems.tsx (3)
entities/IProfile.ts (1)
  • CommunityMemberMinimal (301-312)
components/utils/input/profile-search/getSelectableIdentity.ts (1)
  • getSelectableIdentity (3-16)
components/utils/input/profile-search/CommonProfileSearchItem.tsx (1)
  • CommonProfileSearchItem (8-93)
components/common/TabToggleWithOverflow.tsx (2)
components/compact-menu/index.tsx (1)
  • CompactMenu (12-63)
i18n/messages.ts (1)
  • TAB_TOGGLE_WITH_OVERFLOW_MESSAGES (1-4)
🔇 Additional comments (9)
components/waves/specs/groups/group/edit/buttons/hooks/useWaveGroupEditButtonsController.ts (1)

1-766: Excellent implementation of the wave group identity management hook!

This hook is well-architected with:

  • ✅ All coding guidelines followed (no comments, react-query usage, readonly modifiers, TypeScript)
  • ✅ All past review issues properly addressed
  • ✅ Comprehensive error handling with the toErrorMessage helper
  • ✅ Proper resource cleanup using AbortController with useEffect cleanup
  • ✅ Correct empty array to null normalization pattern (lines 112-113, 137-138, 186-187)
  • ✅ Well-structured async orchestration in onIdentityConfirm with proper permission checks, authentication, payload construction, group creation/publishing, wave updates, and error recovery
  • ✅ Exponential backoff polling with abort support in pollGroupVisibility (lines 344-392)
  • ✅ Complete and correct useCallback dependency arrays throughout

The separation of concerns between helper functions, type definitions, and the main hook logic makes this code maintainable and testable.

components/utils/input/profile-search/CommonProfileSearchItem.tsx (4)

1-20: LGTM! Clean prop and import additions.

The new props (isHighlighted and id) properly support the enhanced accessibility pattern, and all imports align with their usage. Prop types follow the readonly requirement from coding guidelines.


21-31: LGTM! Robust identity and display logic.

The selection logic correctly uses getSelectableIdentity with case-insensitive matching. The secondaryText computation elegantly avoids displaying duplicate information by filtering out the value already shown as title.


33-39: LGTM! Proper keyboard accessibility.

The keyboard handler correctly responds to Enter and Space keys per ARIA best practices, and properly prevents default behavior to avoid unwanted page scrolling.


42-92: LGTM! Complete and correct ARIA option implementation.

The component now properly implements the ARIA option pattern:

  • role="option" and aria-selected={isSelected} provide correct semantics
  • tabIndex={-1} ensures options remain unfocusable per the aria-activedescendant combobox pattern
  • The decorative checkmark is correctly hidden from assistive technologies with aria-hidden="true" and focusable={false}

The separation of concerns is excellent: isHighlighted drives only visual styling, while aria-selected reflects the actual selection state as per ARIA specifications.

components/common/TabToggleWithOverflow.tsx (4)

23-51: OverflowTrigger is tidy and accessible.

Clear label fallback and chevron with aria-hidden. Component typing uses readonly props.


60-68: Logic for clamping and overflow partitioning is solid.

Memoized clamp and slices are correct and safe for edge cases (≤0, short arrays).

Also applies to: 69-72


76-91: Roving tabindex + arrow/Home/End handling looks correct.

State and refs are bounded to visibleTabs; keyboard behavior matches horizontal tabs.

Also applies to: 92-107, 108-140


192-197: Menu items mapping looks correct (active state, no ARIA misuse).

Good switch from invalid ariaSelected to a simple active flag.

Comment thread components/utils/input/profile-search/CommonProfileSearchItems.tsx Outdated
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
components/common/TabToggleWithOverflow.tsx (1)

145-166: Adopt ARIA tab semantics (role="tablist"/"tab" with aria-selected).

You implemented tab keyboard patterns; reflect that in roles/attributes for screen readers. Replace aria-pressed with aria-selected and add proper roles.

-      <div className={clsx("tw-flex tw-gap-x-1", fullWidth && "tw-flex-1")}>
+      <div
+        role="tablist"
+        aria-orientation="horizontal"
+        className={clsx("tw-flex tw-gap-x-1", fullWidth && "tw-flex-1")}
+      >
         {visibleTabs.map((option, index) => (
           <button
-            aria-pressed={activeKey === option.key}
+            role="tab"
+            aria-selected={activeKey === option.key}
             key={option.key}
             type="button"
             tabIndex={index === focusedTabIndex ? 0 : -1}
             onClick={() => handleSelect(option.key)}
             onKeyDown={(event) => handleVisibleTabKeyDown(event, index)}
             ref={(element) => {
               tabRefs.current[index] = element;
             }}
♻️ Duplicate comments (2)
components/common/TabToggleWithOverflow.tsx (1)

181-187: Chevron never rotates — isOpen isn’t passed to OverflowTrigger.

trigger is a pre-rendered node, so isOpen stays false. Pass a render function so CompactMenu can provide open state.

Apply this diff:

-          trigger={
-            <OverflowTrigger
-              isActiveInOverflow={isActiveInOverflow}
-              activeLabel={activeOption?.label}
-              fallbackLabel={TAB_TOGGLE_WITH_OVERFLOW_MESSAGES.overflowFallbackLabel}
-            />
-          }
+          trigger={(isOpen) => (
+            <OverflowTrigger
+              isOpen={isOpen}
+              isActiveInOverflow={isActiveInOverflow}
+              activeLabel={activeOption?.label}
+              fallbackLabel={TAB_TOGGLE_WITH_OVERFLOW_MESSAGES.overflowFallbackLabel}
+            />
+          )}

CompactMenu exposes open state to its content (see components/compact-menu/index.tsx), so this wiring is supported. Based on relevant code snippet

components/utils/input/profile-search/CommonProfileSearchItems.tsx (1)

110-114: Regression: add role="listbox" to the UL.

Required for proper ARIA structure with role="option" children. Previously addressed, now missing.

-                <ul
+                <ul
                   id={visualListboxId}
                   tabIndex={-1}
+                  role="listbox"
                   className="tw-flex tw-flex-col tw-gap-y-1 tw-px-2 tw-mx-0 tw-mb-0 tw-list-none"
                 >
🧹 Nitpick comments (6)
components/common/TabToggleWithOverflow.tsx (1)

92-95: Focus the active tab when selection changes.

Improves keyboard UX with roving tabindex by moving focus to the newly active tab.

   React.useEffect(() => {
     if (activeVisibleIndex >= 0) {
       setFocusedTabIndex(activeVisibleIndex);
+      tabRefs.current[activeVisibleIndex]?.focus();
       return;
     }
components/utils/input/profile-search/CommonProfileSearchItem.tsx (1)

24-30: Prevent empty title; improve fallback order.

Title can be empty if both handle and display are null. Fall back to wallet or a generic label.

-  const title = profile.handle ?? profile.display;
+  const title =
+    profile.display ??
+    profile.handle ??
+    profile.wallet ??
+    "Profile";
-  const secondaryText = [profile.display, profile.handle, profile.wallet].find(
+  const secondaryText = [profile.handle, profile.wallet, profile.display].find(
     (value) => value && value !== title
   );
components/utils/input/profile-search/CommonProfileSearchItems.tsx (4)

70-73: Coding guideline: remove inline comments from TSX.

Project rule forbids comments in .ts/.tsx files.

-  // Keep the callback in the dependency list so highlighted state stays in sync.
-  // Callers should memoize `onHighlightedOptionIdChange` (e.g., with useCallback)
-  // if they want to avoid rerunning the effect when the reference changes.
   useEffect(() => {

90-95: Remove unused state derivation.

normalizedSelected and selectedOptionId are computed but unused.

-  const normalizedSelected = selected?.toLowerCase() ?? null;
-  const selectedOptionId = normalizedSelected
-    ? optionMetadata.find(
-        (meta) => meta.identity?.toLowerCase() === normalizedSelected
-      )?.optionId
-    : undefined;

41-45: Optional: prefer String.prototype.replace for broader runtime compat.

replaceAll with regex is fine in modern targets; replace with replace+global regex to be safer.

-        .replaceAll(/[^a-zA-Z0-9_-]/g, "-")
-        .replaceAll(/-+/g, "-")
-        .replaceAll(/(^-|-$)/g, "")
+        .replace(/[^a-zA-Z0-9_-]+/g, "-")
+        .replace(/-+/g, "-")
+        .replace(/(^-|-$)/g, "")

131-133: Clarify “no results” semantics.

Mark the placeholder as presentational so it’s not treated as an option.

-                    <li className="tw-py-2 tw-w-full tw-h-full tw-flex tw-items-center tw-justify-between tw-text-sm tw-font-medium tw-text-white tw-rounded-lg tw-relative tw-select-none tw-px-2">
+                    <li role="presentation" className="tw-py-2 tw-w-full tw-h-full tw-flex tw-items-center tw-justify-between tw-text-sm tw-font-medium tw-text-white tw-rounded-lg tw-relative tw-select-none tw-px-2">
                       {noResultsText}
                     </li>
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 96aaaf0 and 38cb4be.

📒 Files selected for processing (7)
  • codex/STATE.md (1 hunks)
  • codex/tickets/TKT-0011.md (1 hunks)
  • codex/tickets/TKT-0012.md (1 hunks)
  • components/common/TabToggleWithOverflow.tsx (3 hunks)
  • components/utils/input/profile-search/CommonProfileSearchItem.tsx (1 hunks)
  • components/utils/input/profile-search/CommonProfileSearchItems.tsx (1 hunks)
  • components/waves/specs/groups/group/edit/buttons/hooks/useWaveGroupEditButtonsController.ts (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • codex/tickets/TKT-0011.md
🚧 Files skipped from review as they are similar to previous changes (1)
  • codex/tickets/TKT-0012.md
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{ts,tsx}: Do not include any comments in the code
Use react-query for data fetching
Always add readonly before props

Use TypeScript across the codebase

Files:

  • components/utils/input/profile-search/CommonProfileSearchItem.tsx
  • components/waves/specs/groups/group/edit/buttons/hooks/useWaveGroupEditButtonsController.ts
  • components/common/TabToggleWithOverflow.tsx
  • components/utils/input/profile-search/CommonProfileSearchItems.tsx
**/*.tsx

📄 CodeRabbit inference engine (.cursorrules)

**/*.tsx: Use FontAwesome for icons
Use TailwindCSS for styling

Use React functional components with hooks for UI components

Files:

  • components/utils/input/profile-search/CommonProfileSearchItem.tsx
  • components/common/TabToggleWithOverflow.tsx
  • components/utils/input/profile-search/CommonProfileSearchItems.tsx
🧠 Learnings (2)
📚 Learning: 2025-09-28T12:29:11.651Z
Learnt from: CR
PR: 6529-Collections/6529seize-frontend#0
File: .cursorrules:0-0
Timestamp: 2025-09-28T12:29:11.651Z
Learning: Applies to **/*.{ts,tsx} : Always add readonly before props

Applied to files:

  • components/waves/specs/groups/group/edit/buttons/hooks/useWaveGroupEditButtonsController.ts
📚 Learning: 2025-09-28T12:29:11.651Z
Learnt from: CR
PR: 6529-Collections/6529seize-frontend#0
File: .cursorrules:0-0
Timestamp: 2025-09-28T12:29:11.651Z
Learning: Applies to **/*.tsx : Use FontAwesome for icons

Applied to files:

  • components/common/TabToggleWithOverflow.tsx
🧬 Code graph analysis (4)
components/utils/input/profile-search/CommonProfileSearchItem.tsx (3)
entities/IProfile.ts (1)
  • CommunityMemberMinimal (301-312)
components/utils/input/profile-search/getSelectableIdentity.ts (1)
  • getSelectableIdentity (3-16)
helpers/image.helpers.ts (1)
  • getScaledImageUri (14-42)
components/waves/specs/groups/group/edit/buttons/hooks/useWaveGroupEditButtonsController.ts (3)
services/api/common-api.ts (2)
  • commonApiFetch (18-45)
  • commonApiPost (166-191)
services/groups/groupMutations.ts (5)
  • ValidationIssue (8-15)
  • validateGroupPayload (52-134)
  • createGroup (136-153)
  • publishGroup (155-171)
  • toErrorMessage (22-30)
components/waves/specs/groups/group/edit/buttons/utils/waveGroupEdit.ts (4)
  • getScopedGroup (31-49)
  • isGroupAuthor (51-81)
  • getGroupIdFromUpdateBody (26-29)
  • buildWaveUpdateBody (17-24)
components/common/TabToggleWithOverflow.tsx (2)
components/compact-menu/index.tsx (1)
  • CompactMenu (12-63)
i18n/messages.ts (1)
  • TAB_TOGGLE_WITH_OVERFLOW_MESSAGES (1-4)
components/utils/input/profile-search/CommonProfileSearchItems.tsx (3)
entities/IProfile.ts (1)
  • CommunityMemberMinimal (301-312)
components/utils/input/profile-search/getSelectableIdentity.ts (1)
  • getSelectableIdentity (3-16)
components/utils/input/profile-search/CommonProfileSearchItem.tsx (1)
  • CommonProfileSearchItem (7-88)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (8)
codex/STATE.md (2)

17-17: Discrepancy between summary and actual PR link for TKT-0011.

The enhanced summary states that TKT-0011 has "PRs link present," but the code shows (no PR link). Clarify whether a PR link should be added to TKT-0011 or if the summary description should be updated to align with the actual table state.


17-20: Table format, ordering, and metadata validation.

All four new ticket entries are correctly formatted, maintain alphabetical ordering by ticket ID, and use valid statuses (In-Progress/Backlog), priorities (P1), and owners (openai-assistant). TKT-0012 correctly references the current PR (#1544). Last updated dates are reasonable and fall within the recent development window (2025-10-23 to 2025-10-26).

components/waves/specs/groups/group/edit/buttons/hooks/useWaveGroupEditButtonsController.ts (1)

1-795: LGTM! Well-architected controller hook with proper orchestration.

The implementation is comprehensive and correct:

  • Consistent null-normalization for identity addresses (lines 112-113, 137-138, 186-187) aligns with the project standard
  • Proper abort controller lifecycle management with cleanup (lines 509-514, 388-391, 428-431)
  • Error handling uses toErrorMessage helper throughout (lines 611, 757)
  • Include/exclude logic correctly maintains set semantics and prevents duplicates (lines 164-177)
  • Wave mutation coordination with waveMutationTriggered flag prevents double error handling (lines 688, 732, 749, 763)
  • Exponential backoff polling with configurable limits (lines 344-392)
  • Permission-based guards and authentication gating (lines 575-588, 642-649, 675-684, 690-696)
  • All interfaces use readonly per coding guidelines
  • No inline comments per coding guidelines

The complexity is appropriate for orchestrating create/publish/poll/update workflows with abortable operations and comprehensive error handling.

components/common/TabToggleWithOverflow.tsx (3)

6-7: Icons: Correct use of FontAwesome.

Imports and usage align with project guidelines for TSX iconography. Based on learnings


10-21: Props are readonly — good.

Interfaces consistently mark props as readonly as required. As per coding guidelines

Also applies to: 23-28


170-205: No action needed – button type is already correctly set.

The concern is invalid. CompactMenuTrigger wraps the trigger prop in <MenuButton type="button">, and OverflowTrigger is a presentational component that renders only text and an icon—no button elements. The form submission risk is already prevented at the wrapper level.

Likely an incorrect or invalid review comment.

components/utils/input/profile-search/CommonProfileSearchItem.tsx (1)

76-82: Decorative checkmark a11y looks good.

Icon uses FontAwesome, aria-hidden, and focusable={false}. Conforms to guidelines.

components/utils/input/profile-search/CommonProfileSearchItems.tsx (1)

29-48: ID/metadata construction and ARIA wiring verified as correct.

The upstream combobox integration is properly implemented:

  • listboxId is correctly passed through and used as the ul id
  • Generated option IDs (profile-search-item-${sanitized}-${index}) are stable and match the aria-activedescendant values in the parent combobox
  • onHighlightedOptionIdChange callback properly propagates the correct option ID to IdentitySearch, which uses it in aria-activedescendant
  • getSelectableIdentity is used as intended for label construction

Comment thread components/utils/input/profile-search/CommonProfileSearchItem.tsx Outdated
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (1)
components/common/TabToggleWithOverflow.tsx (1)

187-193: Pass isOpen to OverflowTrigger via render function.

The trigger is passed as a static ReactNode, so OverflowTrigger never receives the isOpen prop from CompactMenu. This means the chevron icon won't rotate when the menu opens. Based on the relevant code snippets, CompactMenu supports render functions for the trigger prop.

Apply this diff to fix the chevron rotation:

-          trigger={
-            <OverflowTrigger
-              isActiveInOverflow={isActiveInOverflow}
-              activeLabel={activeOption?.label}
-              fallbackLabel={TAB_TOGGLE_WITH_OVERFLOW_MESSAGES.overflowFallbackLabel}
-            />
-          }
+          trigger={(isOpen) => (
+            <OverflowTrigger
+              isOpen={isOpen}
+              isActiveInOverflow={isActiveInOverflow}
+              activeLabel={activeOption?.label}
+              fallbackLabel={TAB_TOGGLE_WITH_OVERFLOW_MESSAGES.overflowFallbackLabel}
+            />
+          )}
🧹 Nitpick comments (3)
codex/tickets/TKT-0011.md (1)

20-24: Minor: Consider updating acceptance criteria status and metadata.

The acceptance criteria are all unchecked despite the extensive log through 2025-10-26. If the work has been verified, consider marking them complete. Also, the owner (openai-assistant) and primary PR link appear to be placeholders—consider updating these when available for full traceability.

Also applies to: 28-28

components/utils/input/profile-search/CommonProfileSearchItem.tsx (1)

35-40: Remove unnecessary keyboard handler from option element.

In the aria-activedescendant combobox pattern, the input element maintains focus and handles all keyboard events (arrow keys, Enter, Space). Options have tabIndex={-1} and are never focused during normal interaction, so this onKeyDown handler will not trigger. The input should be responsible for handling Enter/Space to select the active descendant.

Apply this diff to remove the redundant handler:

   const onProfileClick = () => onProfileSelect(profile);
-  const onProfileKeyDown = (event: KeyboardEvent<HTMLLIElement>) => {
-    if (event.key === "Enter" || event.key === " " || event.key === "Spacebar") {
-      event.preventDefault();
-      onProfileClick();
-    }
-  };

   return (
     <li
       id={id}
       role="option"
       aria-selected={isSelected}
       tabIndex={-1}
       onClick={onProfileClick}
-      onKeyDown={onProfileKeyDown}
       className={...}

Also applies to: 49-49

components/waves/specs/groups/group/edit/buttons/hooks/useWaveGroupEditButtonsController.ts (1)

296-318: Normalize empty wallet arrays to null for consistency.

Lines 313-314 assign includeWallets and excludeWallets directly without normalizing empty arrays to null. While applyIdentityChangeToPayload (line 704) does normalize these later, it's cleaner to follow the project standard immediately upon loading.

Apply this diff:

   const basePayload = cloneGroupPayload(groupFull);
   return {
     payload: {
       ...basePayload,
       group: {
         ...basePayload.group,
-        identity_addresses: includeWallets,
-        excluded_identity_addresses: excludeWallets,
+        identity_addresses: includeWallets.length ? includeWallets : null,
+        excluded_identity_addresses: excludeWallets.length ? excludeWallets : null,
       },
     },
     previousGroupId: groupFull.id,
   };
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 38cb4be and 9201f35.

📒 Files selected for processing (6)
  • codex/tickets/TKT-0011.md (1 hunks)
  • codex/tickets/TKT-0012.md (1 hunks)
  • components/common/TabToggleWithOverflow.tsx (3 hunks)
  • components/utils/input/profile-search/CommonProfileSearchItem.tsx (1 hunks)
  • components/utils/input/profile-search/CommonProfileSearchItems.tsx (2 hunks)
  • components/waves/specs/groups/group/edit/buttons/hooks/useWaveGroupEditButtonsController.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • codex/tickets/TKT-0012.md
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{ts,tsx}: Do not include any comments in the code
Use react-query for data fetching
Always add readonly before props

Use TypeScript across the codebase

Files:

  • components/common/TabToggleWithOverflow.tsx
  • components/utils/input/profile-search/CommonProfileSearchItem.tsx
  • components/utils/input/profile-search/CommonProfileSearchItems.tsx
  • components/waves/specs/groups/group/edit/buttons/hooks/useWaveGroupEditButtonsController.ts
**/*.tsx

📄 CodeRabbit inference engine (.cursorrules)

**/*.tsx: Use FontAwesome for icons
Use TailwindCSS for styling

Use React functional components with hooks for UI components

Files:

  • components/common/TabToggleWithOverflow.tsx
  • components/utils/input/profile-search/CommonProfileSearchItem.tsx
  • components/utils/input/profile-search/CommonProfileSearchItems.tsx
🧠 Learnings (1)
📚 Learning: 2025-09-28T12:29:11.651Z
Learnt from: CR
PR: 6529-Collections/6529seize-frontend#0
File: .cursorrules:0-0
Timestamp: 2025-09-28T12:29:11.651Z
Learning: Applies to **/*.tsx : Use FontAwesome for icons

Applied to files:

  • components/common/TabToggleWithOverflow.tsx
🧬 Code graph analysis (4)
components/common/TabToggleWithOverflow.tsx (2)
components/compact-menu/index.tsx (1)
  • CompactMenu (12-63)
i18n/messages.ts (1)
  • TAB_TOGGLE_WITH_OVERFLOW_MESSAGES (1-4)
components/utils/input/profile-search/CommonProfileSearchItem.tsx (3)
entities/IProfile.ts (1)
  • CommunityMemberMinimal (301-312)
components/utils/input/profile-search/getSelectableIdentity.ts (1)
  • getSelectableIdentity (3-16)
helpers/image.helpers.ts (1)
  • getScaledImageUri (14-42)
components/utils/input/profile-search/CommonProfileSearchItems.tsx (3)
entities/IProfile.ts (1)
  • CommunityMemberMinimal (301-312)
components/utils/input/profile-search/getSelectableIdentity.ts (1)
  • getSelectableIdentity (3-16)
components/utils/input/profile-search/CommonProfileSearchItem.tsx (1)
  • CommonProfileSearchItem (8-91)
components/waves/specs/groups/group/edit/buttons/hooks/useWaveGroupEditButtonsController.ts (7)
services/api/common-api.ts (2)
  • commonApiFetch (18-45)
  • commonApiPost (166-191)
generated/models/ApiGroupFull.ts (1)
  • ApiGroupFull (17-85)
generated/models/ApiCreateGroup.ts (1)
  • ApiCreateGroup (16-49)
generated/models/ApiWave.ts (1)
  • ApiWave (27-177)
services/groups/groupMutations.ts (5)
  • ValidationIssue (8-15)
  • validateGroupPayload (52-134)
  • createGroup (136-153)
  • publishGroup (155-171)
  • toErrorMessage (22-30)
components/waves/specs/groups/group/edit/buttons/utils/waveGroupEdit.ts (4)
  • getScopedGroup (31-49)
  • isGroupAuthor (51-81)
  • getGroupIdFromUpdateBody (26-29)
  • buildWaveUpdateBody (17-24)
generated/models/ApiUpdateWaveRequest.ts (1)
  • ApiUpdateWaveRequest (21-95)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (10)
codex/tickets/TKT-0011.md (1)

1-46: Excellent tracking and documentation for accessibility work.

The ticket structure is clear, context is well-defined, and the log provides detailed chronological tracking of iterative fixes to keyboard navigation and ARIA semantics. This level of detail is valuable for understanding the accessibility improvements in this PR.

components/utils/input/profile-search/CommonProfileSearchItem.tsx (1)

8-32: LGTM! Clean implementation with proper ARIA semantics.

The component correctly implements the option role with appropriate accessibility attributes. Props use readonly as required, the selection indicator icon is properly marked decorative, and the display logic handles missing profile fields gracefully.

Also applies to: 42-90

components/utils/input/profile-search/CommonProfileSearchItems.tsx (1)

1-134: LGTM! Well-structured listbox implementation.

The component properly implements the listbox container with correct ARIA semantics. Key strengths:

  • ID generation uses useId() with sanitization and index suffix to prevent collisions
  • optionMetadata pattern cleanly separates data preparation from rendering
  • useEffect correctly notifies parent of highlighted option changes
  • ARIA attributes (role="listbox", aria-label, aria-multiselectable) complete the accessible tree
  • Props use readonly as required by coding guidelines
components/common/TabToggleWithOverflow.tsx (7)

1-21: LGTM! Imports and interfaces are well-defined.

The imports correctly use FontAwesome for icons, all interface properties have the readonly modifier, and TypeScript is used throughout. This aligns with the coding guidelines.


23-51: LGTM! OverflowTrigger component is well-implemented.

The component properly uses FontAwesome for the chevron icon, includes appropriate ARIA attributes (aria-hidden="true"), and implements smooth rotation transitions using TailwindCSS classes.


53-86: LGTM! Component setup and memoizations are correct.

The clamping of maxVisibleTabs to a non-negative integer prevents edge cases, and the memoizations for visibleTabs, overflowTabs, and activeOption have correct dependency arrays to optimize performance.


76-107: LGTM! Focus management is correctly implemented.

The roving tabindex pattern is properly implemented with refs to track visible tabs, and the focus management effects correctly handle both auto-focusing the active tab when visible and clamping the focused index when tabs change.


109-141: LGTM! Keyboard navigation is correctly implemented.

The handler properly supports Arrow keys, Home, and End with appropriate wrapping behavior. The dependency array is correctly minimal, including only visibleTabs.length, and the use of useCallback optimizes performance.


143-173: LGTM! Visible tabs rendering follows accessibility best practices.

The implementation correctly uses ARIA roles (tablist, tab), attributes (aria-selected, aria-orientation), and the roving tabindex pattern for keyboard navigation. The ref callbacks properly populate the refs array for focus management.


194-212: LGTM! Overflow menu configuration is correct.

The items mapping correctly omits the ariaSelected property that was flagged in past reviews, ensuring ARIA compliance. The styling uses TailwindCSS throughout, and the conditional activeItemId logic properly reflects when the active tab is in the overflow menu.

Signed-off-by: Simo <simo@6529.io>
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (2)
components/utils/input/profile-search/CommonProfileSearchItems.tsx (1)

100-132: Critical: restore a single ARIA surface (listbox + options); remove hidden select and expose the visual list.

Mixing a hidden native with a custom list and hiding the list from AT breaks the combobox/aria-activedescendant pattern. Use role="listbox" with role="option" items and wire ids directly. Apply: @@ - const selectSize = Math.max(Math.min(optionMetadata.length || 1, 10), 1); + // (removed) no native select; visual list is the accessible surface @@ - <select - id={resolvedListboxId} - aria-label="Profile suggestions" - tabIndex={-1} - size={selectSize} - className="tw-sr-only" - > - {optionMetadata.length ? ( - optionMetadata.map((meta) => { - const isOptionSelected = - typeof meta.identity === "string" && - normalizedSelected === meta.identity.toLowerCase(); - return ( - <option - key={`sr-${meta.optionId}`} - id={meta.optionId} - value={meta.identity ?? meta.optionId} - aria-selected={isOptionSelected} - > - {meta.label} - </option> - ); - }) - ) : ( - <option id={`${resolvedListboxId}-empty`} value=""> - {noResultsText} - </option> - )} - </select> - <ul - aria-hidden="true" - className="tw-flex tw-flex-col tw-gap-y-1 tw-px-2 tw-mx-0 tw-mb-0 tw-list-none" - > + <ul + id={resolvedListboxId} + role="listbox" + className="tw-flex tw-flex-col tw-gap-y-1 tw-px-2 tw-mx-0 tw-mb-0 tw-list-none" + > Follow‑up: ensure the parent combobox’s aria-activedescendant points to item ids rendered here (see next comment for item ids). #!/bin/bash # Verify combobox wiring uses the listbox id and option ids (no "-visual"/hidden select ids). rg -n -C3 'aria-activedescendant|aria-controls' -g '*.tsx' \ | sed -n '1,160p' Also applies to: 86-86 components/utils/input/profile-search/CommonProfileSearchItem.tsx (1) 34-42: Expose options to AT and align ids with aria-activedescendant (remove “-visual”, remove aria-hidden). Current li is hidden from screen readers and uses a different id than the one sent upstream. Make the li the option with the base id and selection state. Apply: - const visualItemId = `${id}-visual`; @@ - <li - id={visualItemId} - data-option-id={id} - aria-hidden="true" - className="tw-list-none" - > + <li + id={id} + data-option-id={id} + role="option" + aria-selected={isSelected} + className="tw-list-none" + > Also applies to: 20-23 🧹 Nitpick comments (4) components/utils/input/profile-search/CommonProfileSearchItem.tsx (1) 43-50: Optional: remove nested button; make the li the clickable option. Avoid interactive inside role="option". Move onClick to the li and render a non‑interactive wrapper for styling. Keeps semantics clean. - <button - type="button" - tabIndex={-1} - onClick={onProfileClick} - className={`tw-h-full hover:tw-bg-iron-700 tw-py-2 tw-w-full tw-border-none tw-text-left tw-flex tw-items-center tw-justify-between tw-text-white tw-rounded-lg tw-relative tw-cursor-pointer tw-select-none tw-px-2 focus:tw-outline-none focus:tw-ring-1 focus:tw-ring-primary-400 tw-transition tw-duration-300 tw-ease-out ${ - isHighlighted ? "tw-bg-iron-700" : "tw-bg-transparent" - }`} - > + <div + onClick={onProfileClick} + className={`tw-h-full hover:tw-bg-iron-700 tw-py-2 tw-w-full tw-text-left tw-flex tw-items-center tw-justify-between tw-text-white tw-rounded-lg tw-relative tw-cursor-pointer tw-select-none tw-px-2 focus:tw-outline-none focus:tw-ring-1 focus:tw-ring-primary-400 tw-transition tw-duration-300 tw-ease-out ${ + isHighlighted ? "tw-bg-iron-700" : "tw-bg-transparent" + }`} + > @@ - </button> + </div> components/waves/specs/groups/group/edit/buttons/hooks/useWaveGroupEditButtonsController.ts (3) 577-583: Permission gating blocks quick include/exclude when no group exists, but the code supports on‑the‑fly creation. Decide and align. Currently: canIncludeIdentity/canExcludeIdentity require scopedGroup !== null. isIdentityActionAllowed blocks INCLUDE when !scopedGroup?.id. Yet buildIdentityPayload handles missing groups and createAndPublishGroupWithVisibility updates the wave. Choose one: Option A (likely intent): allow quick include/exclude without an existing group if user is admin/author. Then remove the include+no‑group block and relax the can* gates. Option B: disallow operations without an existing group; then remove the no‑group creation path to avoid dead code. Suggested change for Option A: - const canIncludeIdentity = - scopedGroup !== null && (isWaveAdmin || isAuthor); - const canExcludeIdentity = - scopedGroup !== null && (isWaveAdmin || isAuthor); + const canIncludeIdentity = isWaveAdmin || isAuthor; + const canExcludeIdentity = isWaveAdmin || isAuthor; - if ( - mode === WaveGroupIdentitiesModal.INCLUDE && - !scopedGroup?.id - ) { - setToast({ - type: "error", - message: - "You need to define group filters before including specific identities.", - }); - return false; - } + // If needed, rely on validatePayloadOrNotify(NO_FILTERS) to guard invalid states. Also applies to: 215-244, 272-321 43-55: Normalize naming (US vs UK) for consistency. Function is normaliseIdentity but variable is normalisedIdentity; elsewhere you use “normalized”. Pick one (recommend “normalize/normalized”) to reduce cognitive load. -const normaliseIdentity = (identity: string): string => +const normalizeIdentity = (identity: string): string => identity.trim().toLowerCase(); @@ - const normalisedIdentity = normaliseIdentity(identity); + const normalizedIdentity = normalizeIdentity(identity); @@ - normalisedIdentity, + normalizedIdentity, Also applies to: 672-676 622-642: DRY: reuse ensureAuthenticated in updateWave. Avoid duplicating auth flow and keep toast behavior consistent. - if (!opts?.skipAuth) { - const { success } = await requestAuth(); - if (!success) { - setToast({ - type: "error", - message: "Failed to authenticate", - }); - setMutating(false); - return; - } - } + if (!opts?.skipAuth) { + const authed = await ensureAuthenticated(requestAuth, setToast); + if (!authed) { + setMutating(false); + return; + } + } 📜 Review details Configuration used: CodeRabbit UI Review profile: CHILL Plan: Pro 📥 Commits Reviewing files that changed from the base of the PR and between 9201f35 and 2fcda90. 📒 Files selected for processing (6) codex/STATE.md (1 hunks) codex/tickets/TKT-0011.md (1 hunks) codex/tickets/TKT-0012.md (1 hunks) components/utils/input/profile-search/CommonProfileSearchItem.tsx (3 hunks) components/utils/input/profile-search/CommonProfileSearchItems.tsx (2 hunks) components/waves/specs/groups/group/edit/buttons/hooks/useWaveGroupEditButtonsController.ts (1 hunks) 🚧 Files skipped from review as they are similar to previous changes (3) codex/tickets/TKT-0012.md codex/tickets/TKT-0011.md codex/STATE.md 🧰 Additional context used 📓 Path-based instructions (2) **/*.{ts,tsx} 📄 CodeRabbit inference engine (.cursorrules) **/*.{ts,tsx}: Do not include any comments in the code Use react-query for data fetching Always add readonly before props Use TypeScript across the codebase Files: components/utils/input/profile-search/CommonProfileSearchItems.tsx components/utils/input/profile-search/CommonProfileSearchItem.tsx components/waves/specs/groups/group/edit/buttons/hooks/useWaveGroupEditButtonsController.ts **/*.tsx 📄 CodeRabbit inference engine (.cursorrules) **/*.tsx: Use FontAwesome for icons Use TailwindCSS for styling Use React functional components with hooks for UI components Files: components/utils/input/profile-search/CommonProfileSearchItems.tsx components/utils/input/profile-search/CommonProfileSearchItem.tsx 🧠 Learnings (1) 📚 Learning: 2025-09-28T12:29:11.651Z Learnt from: CR PR: 6529-Collections/6529seize-frontend#0 File: .cursorrules:0-0 Timestamp: 2025-09-28T12:29:11.651Z Learning: Applies to **/*.{ts,tsx} : Always add readonly before props Applied to files: components/waves/specs/groups/group/edit/buttons/hooks/useWaveGroupEditButtonsController.ts 🧬 Code graph analysis (3) components/utils/input/profile-search/CommonProfileSearchItems.tsx (3) entities/IProfile.ts (1) CommunityMemberMinimal (301-312) components/utils/input/profile-search/getSelectableIdentity.ts (1) getSelectableIdentity (3-16) components/utils/input/profile-search/CommonProfileSearchItem.tsx (1) CommonProfileSearchItem (7-89) components/utils/input/profile-search/CommonProfileSearchItem.tsx (3) entities/IProfile.ts (1) CommunityMemberMinimal (301-312) components/utils/input/profile-search/getSelectableIdentity.ts (1) getSelectableIdentity (3-16) helpers/image.helpers.ts (1) getScaledImageUri (14-42) components/waves/specs/groups/group/edit/buttons/hooks/useWaveGroupEditButtonsController.ts (3) services/api/common-api.ts (2) commonApiFetch (18-45) commonApiPost (166-191) services/groups/groupMutations.ts (5) ValidationIssue (8-15) validateGroupPayload (52-134) createGroup (136-153) publishGroup (155-171) toErrorMessage (22-30) components/waves/specs/groups/group/edit/buttons/utils/waveGroupEdit.ts (4) getScopedGroup (31-49) isGroupAuthor (51-81) getGroupIdFromUpdateBody (26-29) buildWaveUpdateBody (17-24) ⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1) GitHub Check: Analyze (javascript-typescript) 🔇 Additional comments (5) components/utils/input/profile-search/CommonProfileSearchItems.tsx (2) 30-49: ID generation is sound; keep index disambiguator. Stable, sanitised ids with an index suffix prevent collisions and enable predictable aria-activedescendant targets. LGTM. 64-81: Highlight callback timing is correct. Open‑gate and bounds checks are good; avoids spurious notifications when the popup closes. Confirm parent resets aria-activedescendant to undefined on close. If not, add it where the popup closes. components/utils/input/profile-search/CommonProfileSearchItem.tsx (1) 77-83: Selection visuals and media a11y look good. Checkmark is decorative (aria-hidden, non-focusable). Avatar alt text derived from label is appropriate. Also applies to: 57-61, 70-74 components/waves/specs/groups/group/edit/buttons/hooks/useWaveGroupEditButtonsController.ts (2) 104-117: Standardizing identity fields to null looks good. cloneGroupPayload/createEmptyGroupPayload now set identity_addresses and excluded_identity_addresses to null for empty state. Consistent with downstream normalization. Also applies to: 118-141 346-394: Publish polling with aborts is solid. Exponential backoff, max timeout, cache update, and cleanup via AbortController are correct and resilient.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (3)
components/waves/specs/groups/group/edit/buttons/hooks/useWaveGroupEditButtonsController.ts (2)

163-163: Remove inline comment per coding guidelines.

This comment violates the repository rule: "Do not include any comments in the code" for TS/TSX files.

As per coding guidelines

-  // identity is already normalized by the caller
   const includeSet = new Set(

474-477: Type mismatch: async implementation vs void interface declaration.

The interface declares onIdentityConfirm with return type void (line 477), but the implementation at lines 649-765 is an async function that returns Promise<void>. This creates a type inconsistency.

Update the interface to match the async implementation:

   readonly onIdentityConfirm: (event: Readonly<{
     identity: string;
     mode: WaveGroupIdentitiesModal;
-  }>) => void;
+  }>) => Promise<void>;
 }
components/utils/input/profile-search/CommonProfileSearchItem.tsx (1)

37-44: Critical: Restore ARIA semantics required for combobox pattern.

The list item has aria-hidden="true" (line 40) which completely hides the option from screen readers, and is missing role="option" and aria-selected. This breaks the combobox pattern and violates WCAG Level A requirements. Additionally, the id should be the value passed as a prop (used for aria-activedescendant), not visualItemId.

Apply this diff:

     <li
-      id={visualItemId}
-      data-option-id={id}
-      aria-hidden="true"
+      id={id}
+      role="option"
+      aria-selected={isSelected}
       className="tw-list-none"
       onClick={onProfileClick}
       tabIndex={-1}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2fcda90 and 5af05fb.

📒 Files selected for processing (3)
  • codex/tickets/TKT-0012.md (1 hunks)
  • components/utils/input/profile-search/CommonProfileSearchItem.tsx (3 hunks)
  • components/waves/specs/groups/group/edit/buttons/hooks/useWaveGroupEditButtonsController.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • codex/tickets/TKT-0012.md
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{ts,tsx}: Do not include any comments in the code
Use react-query for data fetching
Always add readonly before props

Use TypeScript across the codebase

Files:

  • components/waves/specs/groups/group/edit/buttons/hooks/useWaveGroupEditButtonsController.ts
  • components/utils/input/profile-search/CommonProfileSearchItem.tsx
**/*.tsx

📄 CodeRabbit inference engine (.cursorrules)

**/*.tsx: Use FontAwesome for icons
Use TailwindCSS for styling

Use React functional components with hooks for UI components

Files:

  • components/utils/input/profile-search/CommonProfileSearchItem.tsx
🧠 Learnings (1)
📚 Learning: 2025-09-28T12:29:11.651Z
Learnt from: CR
PR: 6529-Collections/6529seize-frontend#0
File: .cursorrules:0-0
Timestamp: 2025-09-28T12:29:11.651Z
Learning: Applies to **/*.{ts,tsx} : Always add readonly before props

Applied to files:

  • components/waves/specs/groups/group/edit/buttons/hooks/useWaveGroupEditButtonsController.ts
🧬 Code graph analysis (2)
components/waves/specs/groups/group/edit/buttons/hooks/useWaveGroupEditButtonsController.ts (8)
services/api/common-api.ts (2)
  • commonApiFetch (18-45)
  • commonApiPost (166-191)
generated/models/ApiGroupFull.ts (1)
  • ApiGroupFull (17-85)
generated/models/ApiCreateGroup.ts (1)
  • ApiCreateGroup (16-49)
generated/models/ApiWave.ts (1)
  • ApiWave (27-177)
services/groups/groupMutations.ts (5)
  • ValidationIssue (8-15)
  • validateGroupPayload (52-134)
  • createGroup (136-153)
  • publishGroup (155-171)
  • toErrorMessage (22-30)
components/waves/specs/groups/group/edit/buttons/utils/waveGroupEdit.ts (4)
  • getScopedGroup (31-49)
  • isGroupAuthor (51-81)
  • getGroupIdFromUpdateBody (26-29)
  • buildWaveUpdateBody (17-24)
generated/models/ApiIdentity.ts (1)
  • ApiIdentity (17-169)
generated/models/ApiUpdateWaveRequest.ts (1)
  • ApiUpdateWaveRequest (21-95)
components/utils/input/profile-search/CommonProfileSearchItem.tsx (3)
entities/IProfile.ts (1)
  • CommunityMemberMinimal (301-312)
components/utils/input/profile-search/getSelectableIdentity.ts (1)
  • getSelectableIdentity (3-16)
helpers/image.helpers.ts (1)
  • getScaledImageUri (14-42)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (5)
components/utils/input/profile-search/CommonProfileSearchItem.tsx (5)

1-5: LGTM! Clean imports for the new features.

The imports correctly bring in FontAwesome icons (per guidelines), image scaling utilities, and the identity helper for consistent selection logic.


20-31: LGTM! Robust selection and display logic.

The use of getSelectableIdentity ensures consistent identity extraction across components. The fallback chains for title and secondary text handle missing data gracefully, and avatarAltText follows accessibility best practices.


57-58: LGTM! Proper image optimization and accessibility.

The use of getScaledImageUri optimizes image loading, and avatarAltText provides descriptive alternative text for screen readers.


69-73: LGTM! Clean conditional rendering.

The secondary text is only displayed when it exists and differs from the title, avoiding redundant information.


77-82: LGTM! Properly marked decorative icon.

The checkmark icon correctly uses aria-hidden="true" and focusable={false} to mark it as decorative, since aria-selected on the option already conveys selection state to assistive technologies.

Signed-off-by: Simo <simo@6529.io>
@sonarqubecloud
Copy link
Copy Markdown

@simo6529 simo6529 merged commit 1b9f984 into main Oct 27, 2025
8 checks passed
@simo6529 simo6529 deleted the block-add-identity-to-wave branch October 27, 2025 10:07
@coderabbitai coderabbitai Bot mentioned this pull request Nov 6, 2025
This was referenced Nov 25, 2025
@coderabbitai coderabbitai Bot mentioned this pull request Feb 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants