Skip to content

Implement USER_SELECT, AUTH_FLOW_SELECT and USER_TYPE_SELECT inputs in the JavaScript core and React SDK - #106

Open
Dilusha-Madushan wants to merge 1 commit into
thunder-id:mainfrom
Dilusha-Madushan:feature/user-select-input
Open

Dilusha-Madushan wants to merge 1 commit into
thunder-id:mainfrom
Dilusha-Madushan:feature/user-select-input

Conversation

@Dilusha-Madushan

@Dilusha-Madushan Dilusha-Madushan commented Sep 28, 2026 •

Copy link
Copy Markdown
Contributor

Purpose

This PR adds three flow inputs for selecting users, authentication flows, and user types. Each picker loads options a page at a time as the user scrolls. It also adds masked text so a flow can hide a value, such as a client secret, until the user chooses to reveal it.

Input What the user selects Submitted value
USER_SELECT A user User ID by default
AUTH_FLOW_SELECT An authentication flow, shown by name Flow ID
USER_TYPE_SELECT A user type, shown by handle User type handle

ThunderID's agent onboarding flow uses USER_SELECT to choose an owner and masked COPYABLE_TEXT to display the generated client secret. Copying a masked value still copies the original value.

The built-in flow integration supports these pickers in InviteUser, where a signed-in user's access token is available for the management API calls. Sign-in, sign-up, recovery, and accept-invite skip these inputs with a warning. They do not replace them with free-text fields.

Approach

The implementation separates displaying options from loading them. Select handles the dropdown and user interaction. The React useInfiniteOptions hook and Vue composable manage loading and pagination. UserSelect, AuthFlowSelect, and UserTypeSelect connect that shared behavior to their management APIs, with Base variants for custom integrations.

  • One select for static and paginated lists. Select accepts ordinary options and an optional onLoadMore callback. When the end of the open list becomes visible, it requests more options. It avoids repeated requests for the same list length and does not request pages while closed or loading. A hidden input supports ordinary form submission, and the dropdown supports keyboard navigation and type-ahead.
  • Shared pagination behavior. The loading hook appends pages, removes duplicate values, and stops when there is no next page. A failed request keeps the existing options and leaves the cursor unchanged, so the next attempt requests the same page. It can continue through a limited number of empty or duplicate-only pages. Resource pickers request 30 options by default, with a configurable page size capped at 100.
  • Consistent error handling. Management page requests retry once for server errors or failures without a response; client errors are not retried. A final failure is logged once with request context, without including the server's response text. The picker reports errors through onError rather than displaying an inline error. Direct API errors are preserved, and server-backed integrations carry the status back to the browser.
  • Shared logic in the lower SDK layers. Core owns option mapping, pagination helpers, retry rules, query validation, and fallback messages. Browser owns the clipboard helper and end-of-list detection. This keeps React and Vue from maintaining separate copies of the same behavior. Browser requests also forward AbortSignal and report cancellation as ABORT_ERROR.
  • Management APIs across runtimes. Core adds public getFlows and getUserTypes operations alongside the existing user API, with models and endpoint overrides. Node and Next.js expose getUsers, getFlows, and getUserTypes using the session's access token. Express inherits the Node methods. React Router and TanStack Router inherit the React components without direct changes.
  • Server-backed loading in Next.js and Nuxt. Next.js uses server actions; Nuxt uses /api/auth/users, /api/auth/auth-flows, and /api/auth/user-types. These paths keep the access token on the server and return normalized options. Shared server-side validation checks paging values and limits user filters to 512 characters. Retry and failure logging happen on the server.
  • Flow integration. React and Vue factories render all three picker types through a shared helper. InviteUser supplies their loaders, validates required selections, and forwards loading failures to its existing onError. It also passes flow metadata and translations so templated headings resolve. The fetch hooks and composables are exported for apps composing BaseInviteUser themselves.
  • Masked values. React and Vue expose MaskedText, and CopyableText uses it when masked is true. Hidden values are omitted from the rendered DOM, and the placeholder does not reveal their length. An accessible Show/Hide button toggles visibility. The value remains available to the component for copying. The new picker and masking messages are included in all nine locales.

This also fixes required SELECT validation in React and Vue accept-invite flows, passes additionalData to Vue's invite and accept-invite factories for copyable values, and adds the missing React key to elements returned by custom flow renderers.

Breaking changes

The React and Vue Select primitives now render a custom dropdown instead of a native <select>. This allows the SDK to detect when the user reaches the end of the options and load another page. The closed control keeps its existing styling, and flow SELECT fields still submit the same value under the same reference.

Apps that use native select behavior need to account for these changes:

  • Native mobile pickers are no longer used. multiple, size, and autoComplete are ignored with a warning.
  • Tests using selectOption() or userEvent.selectOptions() must open the dropdown and click an option instead.
  • CSS targeting the <select> element must target the SDK's classes instead.
  • React onChange receives an event-shaped object with target and currentTarget, rather than a real DOM event. Existing ChangeEvent<HTMLSelectElement> handler types remain supported, and handlers can continue reading event.target.value.
  • In Vue, extra attributes and listeners are applied to the trigger button. Blur listeners now fire, but the native required attribute no longer provides browser form validation.

Existing flow definitions do not need to change. Apps using Select directly should review their native attributes, event handling, CSS, and tests.

Test coverage and validation

The added tests cover the behavior at each layer:

  • Core and browser: option mapping, duplicate handling, page boundaries, page-size limits, empty batches, retries, sanitized failure logs, query validation, cancellation, clipboard fallback, and end-of-list detection.
  • React and Vue: selection, keyboard behavior, loading more options, required fields, flow wiring, error callbacks, concurrent loads, resets, masking, and copying. Select tests also check the existing field contract and compare the closed control's computed styles with the released version. React includes a strict type check for existing onChange handlers. Layout-dependent component tests run in Chromium.
  • Node, Next.js, and Nuxt: session-backed management calls, server actions and routes, paging validation, retries, and error status propagation to the browser.

The latest recorded local test results are listed below. These are package suite totals, not counts of tests added by this PR. They were checked against the implementation notes during this description update; the suites were not rerun for this documentation-only edit.

Package Passing tests
JavaScript core 718
Browser 69
Node 47
Express 13
React 347
Vue 283, with 3 additional tests skipped
Next.js 101
Nuxt 193

Related Issues

Related PRs

  • N/A

Checklist

  • Followed the contribution guidelines.
  • Manual test round performed and verified.
  • Documentation provided. (Add links if there are any)
  • Tests provided. (Add links if there are any)
    • Unit Tests
    • Integration Tests
  • Breaking changes. (Fill if applicable)
    • Breaking changes section filled.
    • breaking change label added.
  • Cross-SDK parity. Exactly one of parity/prs-raised or parity/prs-not-needed added.
    • If parity/prs-raised, the port links are posted as a reply on the parity check's comment.

Security checks

  • Followed secure coding standards.
  • Confirmed that this PR doesn't commit any keys, passwords, tokens, usernames, or other secrets.

@coderabbitai

coderabbitai Bot commented Sep 28, 2026 •

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration
  • Configuration used: Repository: thunder-id/javascript-sdks/.coderabbit.yaml
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: e283ee05-5676-47e2-9725-e198913f62a0
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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.

@github-actions

github-actions Bot commented Sep 28, 2026 •

Copy link
Copy Markdown

🔀 Cross-SDK feature parity, ports raised

This pull request is labelled parity/prs-raised, so the capability it introduces is expected in the other ThunderID SDKs as well.

Ports (DO NOT EDIT)

  • thunder-id/ios-sdks, iOS => #30, #123
  • thunder-id/android-sdks, Android => #36
  • thunder-id/flutter-sdks, Flutter => #47

3 of 3 sibling SDKs accounted for.

Every sibling SDK is accounted for.

The contract is in the SDK development specification.

@Dilusha-Madushan

Copy link
Copy Markdown
Contributor Author

🔀 Cross-SDK feature parity

Does this change need to ship in the other ThunderID SDKs too? This check stays red until one of these labels answers that:

Label What it asserts
parity/prs-raised The ports exist in every other SDK this change reaches, or are recorded as not applying there.
parity/prs-not-needed Nothing to port: a fix, refactor, docs, CI, dependency bump, release, or a change specific to this platform.

Ports (DO NOT EDIT)

  • thunder-id/ios-sdks, iOS
  • thunder-id/android-sdks, Android
  • thunder-id/flutter-sdks, Flutter

No sibling SDKs accounted for on this thread yet.

Important

Reply to this comment with this block, filled in. The boxes above tick themselves when you post it.

- thunder-id/ios-sdks: <paste the port link here>
- thunder-id/android-sdks: <paste the port link here>
- thunder-id/flutter-sdks: <paste the port link here>

A link can be a pull request or a tracked issue, written in full or as thunder-id/ios-sdks#123. Where the change does not reach an SDK, replace the placeholder with N/A and a short reason, as in - thunder-id/ios-sdks: N/A, the capability has no equivalent there.

The contract is in the SDK development specification.

@Dilusha-Madushan
Dilusha-Madushan force-pushed the feature/user-select-input branch 5 times, most recently from ce68fb3 to d1fb5c2 Compare October 7, 2026 09:26
@Dilusha-Madushan
Dilusha-Madushan force-pushed the feature/user-select-input branch from d1fb5c2 to 313e70e Compare October 9, 2026 04:53
@DonOmalVindula DonOmalVindula added the parity/prs-raised Ports exist in the other SDKs, or are recorded as not applying there label Oct 12, 2026
@Dilusha-Madushan
Dilusha-Madushan force-pushed the feature/user-select-input branch from 313e70e to 65b9c06 Compare October 13, 2026 03:10
@Dilusha-Madushan Dilusha-Madushan changed the title Implement USER_SELECT flow input in the JavaScript core and React SDK Implement USER_SELECT, AUTH_FLOW_SELECT and USER_TYPE_SELECT inputs in the JavaScript core and React SDK Oct 13, 2026
Signed-off-by: Dilusha-Madushan <dilushamadushan9912@gmail.com>

# Conflicts:
#	packages/javascript/src/index.ts
#	packages/javascript/src/utils/resolveResourceEndpoint.ts

# Conflicts:
#	packages/node/src/ThunderIDNodeClient.ts

# Conflicts:
#	packages/react/src/components/presentation/auth/AuthOptionFactory.tsx
#	packages/react/src/components/presentation/auth/__tests__/AuthOptionFactory.test.tsx
#	packages/vue/src/components/auth/sign-in/AuthOptionFactoryCore.ts
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

parity/prs-raised Ports exist in the other SDKs, or are recorded as not applying there

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants