Skip to content

feat(onboarding): SCM connect step UI polish and analytics#111478

Merged
jaydgoss merged 37 commits intomasterfrom
jaygoss/vdy-22-scm-connect-step-ui-polish-and-analytics-pass
Mar 26, 2026
Merged

feat(onboarding): SCM connect step UI polish and analytics#111478
jaydgoss merged 37 commits intomasterfrom
jaygoss/vdy-22-scm-connect-step-ui-polish-and-analytics-pass

Conversation

@jaydgoss
Copy link
Copy Markdown
Member

Add analytics instrumentation, UI polish, and state management improvements for the SCM connect onboarding step.

Addresses VDY-22. The SCM connect step was implemented in PR #110883 with analytics and UI polish explicitly deferred.

Stacked on PR #111160 (VDY-30: sync feature selections from context).

Analytics

All 6 events from the ticket are tracked:

Event Mechanism
Step viewed trackAnalytics on mount
Provider selected Existing integrations.installation_start with view: 'onboarding'
Integration connected Existing integrations.installation_complete with view: 'onboarding'
Repo selected trackAnalytics in Select onChange
Skip clicked Button analyticsEventKey prop
Continue clicked Button analyticsEventKey prop

UI polish

  • Step indicator ("Step 1 of 3" + Optional tag) with audited font size/weight/spacing
  • "Why connect your repository?" benefits card with 4 items (icons, titles, descriptions)
  • Connected state uses <Tag variant="success"> with checkmark
  • Benefits card visibility: shown with title in initial state, without title when repo selected, hidden when searching
  • "Skip for now" hidden when a repo is selected
  • Repo search dropdown shows error message on API failure
  • Provider pills filtered to design-specified 4 (GitHub, GitLab, Bitbucket, Azure DevOps), ordered per Figma

Repo selector overhaul

Replaced CompactSelect with Select from scraps to get a full-width combo box input matching the Figma design. Added a custom SearchControl component that prepends a search icon inside the select input.

Downstream state reset

When a user goes back to repo selection and picks a different repo, clearDerivedState() on the onboarding context now resets selectedPlatform, selectedFeatures, and createdProjectSlug without wiping the entire session (which setSelectedPlatform(undefined) would do).

Shared components extracted

  • ScmStepHeader -- step indicator + heading + subtitle (reusable across SCM steps)
  • ScmStepContent -- 506px max-width content wrapper
  • ScmStepFooter -- right-aligned 506px footer with top padding

Refs VDY-22

@linear-code
Copy link
Copy Markdown

linear-code bot commented Mar 24, 2026

@github-actions github-actions bot added the Scope: Frontend Automatically applied to PRs that change frontend components label Mar 24, 2026
@jaydgoss jaydgoss force-pushed the jaygoss/vdy-22-scm-connect-step-ui-polish-and-analytics-pass branch from 2210996 to 8df46bf Compare March 24, 2026 21:54
@jaydgoss jaydgoss marked this pull request as ready for review March 24, 2026 22:35
@jaydgoss jaydgoss requested review from a team as code owners March 24, 2026 22:35
)}
{BENEFITS.map(({icon: Icon, title, description}) => (
<Flex key={title} gap="lg" align="start">
<Icon legacySize="20px" variant="muted" />
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

possible to avoid legacy size here?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Unfortunately not really unless the design itself changes, available non-legacy icon sizes are

const ICON_SIZES: Record<IconSize, string> = {
  xs: '12px',
  sm: '14px',
  md: '16px',
  lg: '24px',
  xl: '32px',
  '2xl': '72px',
} as const;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Feels like lg isn't a huge difference here. Can we see a comparison?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This is a design system problem if the design is specifying icons like this. cc @natemoo-re @itsdangold

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Good callout. Legacy sizing is a known issue (hence "legacy") but we shouldn't have new designs using it. We'll be normalizing on 12px, 16px, 24px moving forward. The in-betweens aren't significantly different enough to provide value.

size="lg" would be the design systems recc here.

Comment on lines +28 to +31
<selectComponents.Control {...props}>
<IconSearch size="sm" variant="muted" style={{marginLeft: 12, flexShrink: 0}} />
{children}
</selectComponents.Control>
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We can't use a leadingItems type thign for this can we?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Looks to me like leadingItems is only possible to add for Select option items, not the Control component. There is an inFieldLabel prop, but that is is just a text prefix for the selected value.

{
value: selectedSlug,
label: selectedRepository.name,
textValue: selectedRepository.name,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I don't think we actually need this since label isn't a react node

);
}

const MotionStack = motion.create(Stack);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Oh damn I didn't know about motion.create very nice

Comment on lines +100 to +107
<motion.div
exit={{opacity: 0}}
initial={{opacity: 0}}
animate={{opacity: 1}}
key="benefits"
>
<ScmBenefitsCard />
</motion.div>
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

can we also use motion.create on ScmBenifitsCard?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

It is possible but it would require altering ScmBenefitsCard to accept a ref and forward it to the Container that it renders.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

that doesn't sound too bad I think

@jaydgoss jaydgoss force-pushed the jaygoss/vdy-30-scm-onboarding-sync-feature-selections-from-context-to branch from 65c2555 to 4703c93 Compare March 26, 2026 19:42
@jaydgoss jaydgoss force-pushed the jaygoss/vdy-22-scm-connect-step-ui-polish-and-analytics-pass branch from 7579eda to a9522c5 Compare March 26, 2026 19:42
jaydgoss added 19 commits March 26, 2026 14:50
When the POST to register a repo fails (e.g., 400 because repo
already exists as hidden, or any other error), keep the optimistic
selection rather than reverting to undefined. The user selected a
valid repo and clearing their choice is disruptive. The missing
Sentry ID only matters for cleanup on repo switch, which is
best-effort anyway.

Refs VDY-22
The Continue button checked selectedRepository.id, but the optimistic
repo has id='' (falsy) until the POST resolves. Since we now keep
the optimistic value on POST failure, just check for the existence
of selectedRepository instead.
Remove debug console.log from useScmRepoSelection. Re-apply the
Continue button fix: check for selectedRepository existence instead
of selectedRepository.id, since the optimistic repo has an empty
id until the POST resolves.
The Continue button must require a real Sentry repo ID before
proceeding. Downstream steps need the ID to associate the project
with the repo. The optimistic repo with empty id should not allow
continuation — the POST needs to succeed first.

Refs VDY-22
All Text elements in the SCM connect step need density="comfortable"
for line-height 1.4 to match the Figma specs. Without it, they
inherit the browser default (~1.2).

Also re-enables wrap on provider pills so they flow to a second row
when there are more providers than fit in one line.

Refs VDY-22
- Extract SCM_STEP_CONTENT_WIDTH constant for the 506px content width
  used by ScmStepContent and ScmStepFooter
- Extract SCM_STEP_FADE_IN and scmStepFadeIn() for repeated motion
  props in scmConnect.tsx
- Revert useScmRepoSelection error handling back to original behavior
  (revert on POST failure) — the 400 errors were caused by stale
  local dev state, not a production scenario

Refs VDY-22
Remove unnecessary useMemo for static components object in
ScmRepoSelector per PR review comment. Update onboarding test
to remove assertion for 'Manage in Settings' link which was
removed from the connected state in V1.

Refs VDY-22
Restore error message in error state, remove dead has_repo analytics
param from Skip button, consolidate duplicate import, fix any type on
SearchControl, remove unnecessary Flex wrappers and useCallback usage,
move SCM_STEP_CONTENT_WIDTH to consts.ts, and update stale comment.
Restore useMemo for the custom components object passed to Select to
prevent react-select from remounting on every render. Revert the
ComponentProps type annotation back to any since react-select generics
are contravariant and produce type errors with the narrowed option type.
Remove memoization wrapper and pass the components object inline,
matching the established pattern across the codebase.
Collapse two early returns in the options memo into a single guard,
document the any type on SearchControl props (react-select generic
contravariance), and note why client-side filtering is disabled.
Change "Connected to github.com/jaydgoss" to
"Connected to GitHub org jaydgoss" to match the Figma design.
Move trackAnalytics call from onSelect callback (which fires twice per
selection due to optimistic + resolved updates) to handleChange where
it represents a single user action. Also fix stale test assertion for
connected tag format.
…tion

Restructure scmConnect to make the content sections direct motion
siblings of the footer within a LayoutGroup. Split the connected state
into two keyed MotionStacks (with/without repo selected) so the footer
with layout="position" can animate when the section above swaps.

VDY-24
The LayoutGroup refactor made SCM_STEP_FADE_IN, scmStepFadeIn, and
scmStepContent.tsx dead code. Remove them to fix knip.

VDY-24
…ates

Move loading and error early returns into the main render so the step
header and footer remain visible in all states. Extract the content
branch into a variable to replace the nested ternary chain.

VDY-24
textValue is only needed when label is a React node so the Select
can filter on a plain string. Here label is already a string, making
textValue redundant.
@jaydgoss jaydgoss force-pushed the jaygoss/vdy-22-scm-connect-step-ui-polish-and-analytics-pass branch from a9522c5 to a1ace12 Compare March 26, 2026 19:51
The exit props on MotionStack are ineffective because they are children
of LayoutGroup, not AnimatePresence. MotionStack is still needed for
LayoutGroup coordination.
Replace the motion.div wrapper with motion.create(ScmBenefitsCard)
to avoid an extra DOM node. ScmBenefitsCard now accepts ref and
spreads extra props onto its root Container.
Replace legacySize="20px" with size="lg" (24px) per design system
guidance to avoid legacy sizing.
Copy link
Copy Markdown
Contributor

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

</Flex>
)}
</Stack>
}}
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.

Search state not reset when clearing selected repo

Low Severity

The onInputChange handler only calls setSearch for input-change actions. When the user clears the selected value via the clear button, react-select fires onInputChange('', {action: 'set-value'}), which is ignored. This leaves the internal search state (and debouncedSearch) at the old value, so the query remains enabled with the stale search term. As a result, when the dropdown reopens after clearing, the user sees old search results despite the input being visually empty. The set-value action needs to also reset the search state to avoid this inconsistency.

Fix in Cursor Fix in Web

@jaydgoss jaydgoss merged commit af3d784 into master Mar 26, 2026
70 checks passed
@jaydgoss jaydgoss deleted the jaygoss/vdy-22-scm-connect-step-ui-polish-and-analytics-pass branch March 26, 2026 20:21
jaydgoss added a commit that referenced this pull request Mar 26, 2026
)

When a user navigates back to repo selection and picks a different repo,
the previously selected platform, features, and created project slug are
stale.

Adds `clearDerivedState` to the onboarding context that clears these
fields in a single session storage update without wiping the entire
session -- which `setSelectedPlatform(undefined)` would do due to the
`removeOnboarding()` call in its undefined branch.

The repo selector's `onSelect` callback now calls `clearDerivedState()`
whenever the repository changes.

**Stacked on [PR
#111478](#111478 (VDY-22: UI
polish and analytics).

Refs VDY-22
@github-actions github-actions bot locked and limited conversation to collaborators Apr 11, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

Scope: Frontend Automatically applied to PRs that change frontend components

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants