Skip to content

feat(groups): add active group switcher - #273

Open
chiptus wants to merge 15 commits into
mainfrom
claude/implement-124-f9vl65
Open

feat(groups): add active group switcher#273
chiptus wants to merge 15 commits into
mainfrom
claude/implement-124-f9vl65

Conversation

@chiptus

@chiptus chiptus commented Jul 31, 2026

Copy link
Copy Markdown
Owner

Adds a persistent header switcher (Everyone + your groups) that sets the Active Group everywhere, and retires the Artists-tab-only group filter in favor of a two-state Vote Perspective toggle built on a shared, tested vote-scope resolver.
Selecting a group in the header re-scores and re-sorts the Artists tab immediately, without a reload.

Closes #124

Verification

  • Sign in as a user in 2+ groups; open the header switcher, pick a group, and confirm the button updates to that group's name immediately (no reload).
  • On the Artists tab, with an Active Group set, confirm the Vote Perspective toggle defaults to the group and toggling to Everyone re-sorts/re-scores sets without hiding any.
  • Sign in as a user in zero groups; confirm the header still shows the "Create/Join a Group" CTA and the Artists tab has no Vote Perspective toggle (always Everyone).
  • pnpm test passes, including new src/lib/voteScope.test.ts covering Everyone/Me/Group scopes, a member vs. non-member group, no votes, and votes only from outside the group.
  • pnpm run typecheck and pnpm run lint pass.

Generated by Claude Code

Copilot AI review requested due to automatic review settings July 31, 2026 08:27
@qodo-code-review

Copy link
Copy Markdown

Qodo reviews are paused for this user.

Troubleshooting steps vary by plan Learn more →

On a Teams plan?
Reviews resume once this user has a paid seat and their Git account is linked in Qodo.
Link Git account →

Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center?
These require an Enterprise plan - Contact us
Contact us →

@vercel

vercel Bot commented Jul 31, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
upline Ready Ready Preview Aug 14, 2026 8:42am

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot reviewed 12 out of 12 changed files in this pull request and generated no comments.

@github-actions

github-actions Bot commented Aug 1, 2026

Copy link
Copy Markdown

Playwright test results

passed  50 passed

Details

stats  50 tests across 14 suites
duration  1 minute, 6 seconds
commit  3baa05c

Comment thread src/components/layout/AppHeader/GroupsIndicator.tsx Outdated
Comment thread src/lib/voteScope.test.ts
Comment thread src/lib/voteScope.test.ts Outdated
@github-actions

github-actions Bot commented Aug 1, 2026

Copy link
Copy Markdown

Deploy → stagingworkflow run
Last updated: 2026-08-14 08:42:33 UTC

  • DB migrations succeeded
  • ⏭️ Edge functions skipped (no changes)

chiptus commented Aug 1, 2026

Copy link
Copy Markdown
Owner Author

The migrate / Push migrations (staging) failure isn't caused by the new migration in this PR (20260801140000_add_active_group_selected_to_profiles.sql) — its timestamp is correctly ordered after everything else. The actual error from the job log:

Found local migration files to be inserted before the last migration on remote database.
Rerun the command with --include-all flag to apply these migrations:
supabase/migrations/20260728000000_add_votes_user_id_profiles_fkey.sql

This means staging is missing 20260728000000_add_votes_user_id_profiles_fkey.sql even though later migrations were already applied — pre-existing history drift unrelated to this branch's changes. This is the first commit on this PR to touch supabase/migrations/, so it's the first time the migrate check has run here.

The auto-generated deploy-status comment on this thread suggests supabase db reset --linked to recover, but that wipes staging data — I'm not running that from this session per repo policy (CLAUDE.md: never run supabase db push / db reset). Resolving the drift needs a human decision on the staging project. Happy to push --include-all myself if that's the intended fix and someone confirms it's safe for staging's current state.


Generated by Claude Code

Comment thread src/components/layout/AppHeader/ActiveGroupSwitcher.tsx Outdated
claude added 10 commits August 14, 2026 07:29
… toggle

Turns the header's Groups indicator into a switcher listing Everyone plus
every group the user belongs to; selecting an entry persists it as the
profile's active_group_id and takes effect everywhere without a reload.

Retires the Artists-tab-only groupId URL param and its dropdown, replacing
it with a Vote Perspective toggle (Everyone vs Active Group) that re-scores
and re-sorts sets without hiding any. Extracts the ad hoc group-membership
vote filter into a shared, pure resolveVotesForScope function (Everyone/Me/
Group) so the next issue's Schedule tab scope can reuse it.

Closes #124
The header's active-group indicator is now a DropdownMenu-driven
switcher (a native <button>), not a plain link to /groups, so the
role assertion needs to match.
- Extract the group-switcher dropdown out of GroupsIndicator into its
  own ActiveGroupSwitcher component.
- Narrow resolveVotesForScope's groupMemberIds param to Set<string>
  only, since every real caller already passes a Set; drops the
  unused array-input overload and its dedicated test.
… users

The single-group auto-activation from #123 stores "no active group set
yet" as active_group_id = NULL, which is indistinguishable from an
explicit "Everyone" choice made through the new switcher — so choosing
Everyone was silently overridden back to the sole group. Add a
active_group_selected flag so an explicit selection is trusted as-is,
while auto-activation still applies for users who've never touched the
switcher.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VGSj8prZCB1qAtR2Wz3qKL
There was no way to reach /groups once a user already belonged to a
group, since the header CTA only shows for the zero-groups case.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VGSj8prZCB1qAtR2Wz3qKL
Epic #122 references CONTEXT.md vocabulary and docs/adr/0003-active-group-model.md
as settled, but they were only ever written on claude/group-feature-design-f0nhvn,
which never merged (and is now too stale to merge as-is, predating Festival
Phase and Retrospective Rating). Reapply just the Active Group / Vote
Perspective / Vote Scope vocabulary and the ADR (renumbered 0005, since 0003
and 0004 are now taken) onto current CONTEXT.md, updated to describe the
active_group_selected flag actually shipped and to flag that its
Everyone-persistence behavior is under active reconsideration.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VGSj8prZCB1qAtR2Wz3qKL
…ive scope

Following further design work (grill session + prototype, see docs/adr/0005),
the active_group_selected flag from the previous fix papered over a modeling
bug rather than fixing it: "which group is mine" and "which lens am I
viewing through" were conflated into one nullable column. Root-cause instead:

- profiles.active_group_id: which group is yours (unchanged meaning, now
  scope-independent).
- profiles.active_scope (new): group/everyone/me, the durable Settings-level
  pin. Both are set explicitly only from a new Settings page.
- The header switcher no longer writes either column — picking an entry
  there is a transient, in-memory override for the current session only,
  with a "back to X" affordance when it diverges from the pin. Pinned entry
  always sorts first and is starred.

This is asymmetric by design: real group picks stay sticky/default with no
friction (the epic's whole point), while Everyone/Me default to a temporary
lens, with friction pushed onto the explicit Settings pin instead of every
header click.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VGSj8prZCB1qAtR2Wz3qKL
Split the 189-line switcher into three focused files per review feedback:
scopeDisplay.ts (pure label/icon/key helpers), ScopeMenuBody.tsx (the
dropdown list), and the trimmed-down ActiveGroupSwitcher.tsx (trigger +
back-to-default pill).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VGSj8prZCB1qAtR2Wz3qKL
Per review feedback: the pinned entry already sorts first (starred) in
the dropdown, so a separate "back to X" affordance next to the trigger
was redundant and added an extra jumping element to the header.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VGSj8prZCB1qAtR2Wz3qKL
chiptus and others added 2 commits August 14, 2026 09:15
Deriving activeGroupId from the header's transient current scope hid
the toggle whenever a user with an Active Group switched away from
"group". Also seed the initial perspective from current per ADR 0005
instead of hardcoding "group".

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Replace hand-rolled <button> + manual state classes with the
shadcn ToggleGroup pattern already used by VotePerspectiveToggle
for the same single-select choice shape.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Decompose FilteredSetsPanel.tsx (154 lines, four mixed concerns) into
AuthedFilteredSetsPanel/GroupScopedSetsPanel/SetsPanelContent, matching
the earlier ActiveGroupSwitcher/ScopeMenuBody split. This also removes
the duplicated "everyone" SetsPanelContent call between the anonymous
and authed-non-group branches.

Also collapse useSetActiveGroupMutation/useSetActiveScopeMutation into
a shared useProfileFieldMutation, and dedupe the ToggleGroupItem
className repeated in ActiveGroupSetting/ActiveScopeSetting.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012hH41h18hKSvYxWAw17nhd
return useMutation({
mutationFn: async (variables: {
userId: string;
value: ProfileUpdate[K];

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Why should the mutation update not nly one value?

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.

Group switcher + Vote Perspective on Artists tab

3 participants