Skip to content

PersonMenu + redesign of the SidePanel's 'Identified People' section - #22

Merged
jansaldo merged 9 commits into
mainfrom
feature/person-menu
Aug 7, 2026
Merged

PersonMenu + redesign of the SidePanel's 'Identified People' section#22
jansaldo merged 9 commits into
mainfrom
feature/person-menu

Conversation

@jansaldo

@jansaldo jansaldo commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Adds PersonMenu, a presentational surface (card + AvatarPill rows + footer action) with no anchoring/popover logic of its own, exported from the public barrel.
  • Renames the SidePanel section from "Personas sugeridas" to "Personas identificadas" and splits its layout into two rows: pills on top, "Nuevo" always below.
  • Turns "Nuevo" into a Popover trigger that opens PersonMenu with newPersonOptions (e.g. judicial roles) when provided; falls back to calling onNewPerson directly when omitted — fully backwards compatible.
  • Bumps package version to 0.6.0 and tags v0.6.0.

Unrelated minor patch

  • ArchiveRow.stories.tsx: standardized the example description strings to use · instead of a mix of -/· (three of the four examples used a hyphen). Doesn't touch ArchiveRow itself — it only renders whatever description string it's given; this just fixes the reference examples in Storybook to reflect the separator convention consumers (e.g. desktop-app) should follow.

Test plan

  • vitest run — 72 tests passing (incl. new PersonMenu.test.tsx and SidePanel.test.tsx)
  • tsc -p tsconfig.build.json --noEmit — clean
  • biome check — clean (no non-token CSS values; strictTokens: true enforced)
  • vite build && panda cssgenPersonMenu present in dist/, no arbitrary-value violations
  • storybook build — smoke-tested, all new stories compile
  • Visual comparison against Figma screenshots (nodes 40002701:44844, 40002701:44829) — not done in this non-interactive environment; recommend a manual pass in storybook dev before merging.

Spec: docs/superpowers/specs/2026-08-06-person-menu-personas-identificadas-design.md
Plan: docs/superpowers/plans/2026-08-06-person-menu-personas-identificadas.md

🤖 Generated with Claude Code

@sourcery-ai

sourcery-ai Bot commented Aug 6, 2026

Copy link
Copy Markdown

Reviewer's Guide

Introduces a reusable PersonMenu surface for selecting people/roles and wires it into the SidePanel’s people section, redesigning the “Personas sugeridas” area into “Personas identificadas” with a two-row layout and an optional popover-based menu for the “Nuevo” action, plus updated stories, tests, public exports, and a version bump to 0.6.0.

Sequence diagram for SidePanel Nuevo flow with optional PersonMenu

sequenceDiagram
  actor User
  participant SidePanel
  participant Popover
  participant PersonMenu
  participant Parent

  User->>SidePanel: click Nuevo button
  alt newPersonOptions provided
    SidePanel->>Popover: setMenuOpen(true)
    Popover->>PersonMenu: render(options=newPersonOptions)
    alt user chooses role option
      User->>PersonMenu: click role button
      PersonMenu->>SidePanel: onSelectOption(index)
      SidePanel->>Parent: onSelectNewPersonOption(index)
      SidePanel->>Popover: setMenuOpen(false)
    else user chooses Nueva persona
      User->>PersonMenu: click footer button
      PersonMenu->>SidePanel: onFooterAction()
      SidePanel->>Parent: onNewPerson()
      SidePanel->>Popover: setMenuOpen(false)
    end
  else no newPersonOptions
    SidePanel->>Parent: onNewPerson()
  end
Loading

File-Level Changes

Change Details Files
Add reusable PersonMenu presentational component for person/role selection.
  • Implement PersonMenu component as a purely presentational surface (list of AvatarPill options plus optional footer) without popover/anchoring logic.
  • Define PersonMenuOption and PersonMenuProps types, including support for a selectable option list, optional selectedIndex, footerLabel/onFooterAction, and a footerSlot override.
  • Ensure PersonMenu is keyboard-accessible by wrapping each AvatarPill row in a focusable with aria-label and aria-current for the selected option.
  • Apply Panda CSS styling for the menu surface, option buttons, and a custom 40px-high footer button matching the Figma spec.
  • Export PersonMenu, PersonMenuOption, and PersonMenuProps from a local index and from the package’s public barrel.
src/components/person-menu/PersonMenu.tsx
src/components/person-menu/index.ts
src/index.ts
Integrate PersonMenu into SidePanel and redesign the ‘Personas’ section layout and behavior.
  • Rename the SidePanel section heading and docs from “Personas sugeridas” to “Personas identificadas”.
  • Add new SidePanel props newPersonOptions and onSelectNewPersonOption, documenting backward-compatible behavior with onNewPerson.
  • Refactor the people pills area into a two-row layout (pills in a wrap row, with the Nuevo button always in a dedicated row below) using new peopleGroup and newButton styles.
  • Replace the original inline Nuevo button with a conditional Popover+PersonMenu when newPersonOptions are provided, passing options, aria-label, footerLabel, and footer action wiring.
  • Maintain backward compatibility by falling back to the original direct onNewPerson click behavior when newPersonOptions is empty or undefined, and manage popover open state with internal menuOpen state.
src/components/side-panel/SidePanel.tsx
Update SidePanel stories to cover the new menu behavior and keep examples aligned with the new design.
  • Introduce ROLE_OPTIONS in stories to preview the menu options (judicial roles) used by SidePanel.
  • Wire newPersonOptions and onSelectNewPersonOption into existing stories (Default, Sizes, NarrowContainer, MergeConfirmation, InvalidTimestamp) so Storybook reflects the new behavior.
  • Adjust sample people and merge confirmation labels to be more generic person labels instead of role-specific names.
  • Add two new stories: WithNewPersonMenu to demonstrate SidePanel with the dropdown available, and WithoutNewPersonMenu to show the backward-compatible direct Nuevo behavior.
src/components/side-panel/SidePanel.stories.tsx
Add unit tests for PersonMenu and SidePanel’s new-person menu behavior.
  • Create PersonMenu tests covering rendering of options as buttons, index reporting on selection, aria-current marking for selected option, footer button behavior, footerSlot overriding, footer-only rendering when options are empty, rendering nothing when both options and footer are absent, and group-level aria-labeling.
  • Create SidePanel tests for the people section ensuring the new “Personas identificadas” heading and absence of the old title, plus tests that cover direct onNewPerson calls, menu opening with options, role selection reporting via onSelectNewPersonOption, and footer routing to onNewPerson with menu close behavior.
  • Stub ResizeObserver and pointer-related Element methods in SidePanel tests to make Radix-based popovers work under jsdom.
src/components/person-menu/PersonMenu.test.tsx
src/components/side-panel/SidePanel.test.tsx
Add Storybook documentation for PersonMenu and bump package version.
  • Create PersonMenu stories that mirror the Figma spec, including Default, WithSelection, Empty, LongNames, and WithFooterSlot (using a controlled input as footerSlot to mimic SpeakerPicker usage).
  • Configure Storybook metadata for PersonMenu, including layout and Figma URL parameters.
  • Bump the package version from 0.5.0 to 0.6.0 in package.json to reflect the new public component and behavior.
src/components/person-menu/PersonMenu.stories.tsx
package.json

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Hey - I've found 2 issues, and left some high level feedback:

  • In WithoutNewPersonMenu the story description says "Nuevo" calls onNewPerson directly, but the args don’t pass onNewPerson, so the button ends up inert; consider wiring a no-op or example handler so the Storybook behavior matches the description.
  • The ResizeObserver stub and Element prototype patches introduced in SidePanel.test.tsx are effectively global (via beforeAll + vi.stubGlobal); if other tests rely on real behavior or different stubs, it might be safer to move this into a shared test setup or make it more scoped/resettable.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `WithoutNewPersonMenu` the story description says "Nuevo" calls `onNewPerson` directly, but the args don’t pass `onNewPerson`, so the button ends up inert; consider wiring a no-op or example handler so the Storybook behavior matches the description.
- The `ResizeObserver` stub and Element prototype patches introduced in `SidePanel.test.tsx` are effectively global (via `beforeAll` + `vi.stubGlobal`); if other tests rely on real behavior or different stubs, it might be safer to move this into a shared test setup or make it more scoped/resettable.

## Individual Comments

### Comment 1
<location path="src/components/person-menu/PersonMenu.tsx" line_range="98-100" />
<code_context>
+  className,
+  "aria-label": ariaLabel = "Personas",
+}: PersonMenuProps) {
+  const footer =
+    footerSlot ??
+    (footerLabel ? (
+      <Button
+        variant="tertiary"
</code_context>
<issue_to_address>
**suggestion (bug_risk):** Avoid rendering a clickable footer button when there is no `onFooterAction` handler.

Currently the footer button renders whenever `footerLabel` is set, even if `onFooterAction` is `undefined`, resulting in a clickable control that does nothing. Either gate rendering on `footerLabel && onFooterAction` or require `onFooterAction` whenever `footerLabel` is provided so the footer button always triggers an action.

Suggested implementation:

```typescript
  const footer =
    footerSlot ??
    (footerLabel && onFooterAction ? (
      <Button
        variant="tertiary"
        size="sm"
        onClick={onFooterAction}
        className={footerButton}
      >
        <PlusIcon size={16} />
        {footerLabel}
      </Button>
    ) : null);

```

You may also want to update the `PersonMenuProps` type so that `footerLabel` and `onFooterAction` are modeled as a pair (e.g., via a discriminated union or by making them both required in a specific variant), ensuring at the type level that a label cannot be provided without a handler.
</issue_to_address>

### Comment 2
<location path="src/components/side-panel/SidePanel.stories.tsx" line_range="261-266" />
<code_context>
+};
+
+/** Sin `newPersonOptions`: "Nuevo" llama a onNewPerson directo (retrocompatible). */
+export const WithoutNewPersonMenu: Story = {
+  args: {
+    people: [{ id: "s1", initials: "P1", name: "Persona 1", renamable: true }],
+    timestamp: "01:15",
+    turn: { initials: "P1", name: "Persona 1", time: "01:15", color: "violet" },
+    newPersonOptions: undefined,
+  },
+};
</code_context>
<issue_to_address>
**nitpick (bug_risk):** `WithoutNewPersonMenu` story leaves the 'Nuevo' button without an action.

The description says this variant should call `onNewPerson` directly, but the story doesn’t pass `onNewPerson` in `args`, so the "Nuevo" button is inert. Either wire a no-op `onNewPerson` to show the intended behavior, or remove the button when no handler is provided.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread src/components/person-menu/PersonMenu.tsx Outdated
Comment thread src/components/side-panel/SidePanel.stories.tsx
- Gate PersonMenu's footer button on both footerLabel and onFooterAction
  so a future consumer that passes only footerLabel doesn't get a dead
  button.
- Wire onNewPerson in the WithoutNewPersonMenu story so it actually
  demonstrates the backwards-compatible fallback it documents.
- Standardize the demo role options in PersonMenu and SidePanel stories
  to Juez/a, Fiscal, Defensor/a, Denunciante, Acusado/a.
- Translate all Spanish code comments introduced by this feature to
  English. User-facing strings stay in Spanish per the library's
  existing i18n debt.
@jansaldo jansaldo changed the title PersonMenu + rediseño 'Personas identificadas' del SidePanel PersonMenu + redesign of the SidePanel's 'Identified People' section Aug 6, 2026
Three of the four example descriptions used a plain hyphen while one used
the middle dot; standardize on '·' so Storybook's reference examples
match the separator convention consumers should follow.
@jansaldo
jansaldo merged commit 5125acf into main Aug 7, 2026
2 checks passed
@jansaldo
jansaldo deleted the feature/person-menu branch August 7, 2026 00:01
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.

1 participant