Skip to content

integrate users/me/meta endpoint into the Vue and Nuxt sdks - #60

Merged
brionmario merged 1 commit into
thunder-id:mainfrom
janithjay:feat/vue-profile-edit
Aug 7, 2026
Merged

integrate users/me/meta endpoint into the Vue and Nuxt sdks#60
brionmario merged 1 commit into
thunder-id:mainfrom
janithjay:feat/vue-profile-edit

Conversation

@janithjay

@janithjay janithjay commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Purpose

Integrates the /users/me/meta schema endpoint into the React (@thunderid/vue) and Next.js (@thunderid/nuxt) SDKs. This allows the component to dynamically load user profile schema metadata (attribute types, labels, required states, and order) directly from the ThunderID server

Approach

Vue SDK (@thunderid/vue):

  • Added getUsersMeMeta API client helper for fetching profile schema attributes from /users/me/meta.
  • Updated ThunderIDProvider and UserProvider to fetch schema metadata and supply userSchemaState via Vue's provide/inject context.
  • Updated <BaseUserProfile> to render schema driven form fields dynamically based on attribute metadata with regex validation.
  • Changed avatar sizing and styling in <BaseUserDropdown> and <BaseUserProfile>

Nuxt SDK (@thunderid/nuxt):

  • Refactored ThunderIDNuxtConfig to extend AuthClientConfig directly from @thunderid/node.
  • Added getUserSchema() to ThunderIDNuxtClient and integrated concurrent schema fetching into the Nitro SSR server plugin (thunderid-ssr.ts).
  • Hydrated userSchemaState from SSR payloads into client state via <ThunderIDRoot>.
  • Wired endpoint overrides (usersMe, usersMeMeta) from nuxt.config.ts to ThunderIDNuxtClient and Nitro SSR plugins.

Related Issues

Related PRs

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.

Security checks

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

Summary by CodeRabbit

  • New Features

    • Added user metadata schemas for displaying and editing profile fields.
    • Added required-field and regular-expression validation.
    • Added configurable service endpoints and improved Nuxt profile retrieval.
    • Added a Vue API for retrieving user metadata.
    • Added profile preferences support across providers and components.
    • Added profile images with initials as avatar fallbacks.
    • Improved profile modal layout, alignment, and accessibility.
  • Bug Fixes

    • Profile updates now use PUT requests.
    • Profile data gracefully falls back when service requests fail.

Copilot AI lite review requested due to automatic review settings August 7, 2026 09:43
@coderabbitai

coderabbitai Bot commented Aug 7, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@janithjay, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 6 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 7f6c8b82-6972-44ca-bd75-33463e6598e4

📥 Commits

Reviewing files that changed from the base of the PR and between b7060fe and 93d0cbb.

📒 Files selected for processing (5)
  • packages/nuxt/src/runtime/server/ThunderIDNuxtClient.ts
  • packages/vue/src/components/presentation/user-dropdown/BaseUserDropdown.ts
  • packages/vue/src/components/presentation/user-profile/BaseUserProfile.ts
  • packages/vue/src/models/contexts.ts
  • packages/vue/src/providers/UserProvider.ts
📝 Walkthrough

Walkthrough

Nuxt and Vue now support /users/me/meta schema retrieval, schema hydration, profile merging, client-side validation, preferences, profile images, and updated profile modal presentation.

Changes

User schema profile integration

Layer / File(s) Summary
Configuration and shared schema contracts
packages/nuxt/src/module.ts, packages/nuxt/src/runtime/types.ts, packages/nuxt/src/runtime/utils/*, packages/vue/src/models/contexts.ts
Nuxt configuration exposes endpoints and SSR schema data. Vendor-scoped state keys and cookie resolution are supported. Vue contexts expose preferences and reactive schema metadata.
Nuxt profile and schema hydration
packages/nuxt/src/runtime/server/*, packages/nuxt/src/runtime/plugins/thunderid.ts, packages/nuxt/src/runtime/components/ThunderIDRoot.ts, packages/nuxt/tests/unit/*
Nuxt fetches profile and schema data, includes schema metadata in SSR data, hydrates vendor-scoped state, and passes it to UserProvider.
Vue API and provider data flow
packages/vue/src/api/*, packages/vue/src/providers/*, packages/vue/src/index.ts
Vue adds the metadata API, changes profile updates to PUT, retrieves and merges /users/me data, stores schema metadata, and exposes it through provider context.
Schema-aware profile editing and presentation
packages/vue/src/components/presentation/user-profile/*
Profile fields use schema metadata. Required and regex validation runs before updates. Preferences, merged attributes, profile images, and revised profile styles are supported.
Profile modal and avatar presentation
packages/vue/src/components/presentation/user-dropdown/*
Profile modals render through Teleport. Dropdown avatars use profile images when available. Alignment, sizing, cropping, and modal layout styles are updated.

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related issues

  • thunder-id/thunderid#4625 — The PR integrates /users/me/meta and uses schema metadata for profile rendering and validation.

Possibly related PRs

Suggested reviewers: senthalan

Sequence Diagram(s)

sequenceDiagram
  participant ThunderIDProvider
  participant getUsersMeMeta
  participant UsersMeAPI
  participant UserProfile

  ThunderIDProvider->>getUsersMeMeta: Request user schema
  getUsersMeMeta->>UsersMeAPI: Request users-me metadata
  UsersMeAPI-->>getUsersMeMeta: Return schema metadata
  getUsersMeMeta-->>ThunderIDProvider: Provide AttributeSchema map
  ThunderIDProvider->>UserProfile: Pass profile and userSchema
  UserProfile->>UserProfile: Render fields and validate edits
Loading
🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning The pull request includes unrelated avatar styling and user-dropdown modal accessibility changes beyond the linked schema endpoint objective. Move avatar styling and user-dropdown modal changes to a separate pull request, or document their required relationship to the schema endpoint work.
Docstring Coverage ⚠️ Warning Docstring coverage is 64.29% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the integration of the users/me/meta endpoint into the Vue and Nuxt SDKs.
Description check ✅ Passed The description covers the purpose, approach, related issues, implementation scope, testing, manual testing, and security checks.
Linked Issues check ✅ Passed The changes implement schema fetching, SSR hydration, dynamic profile fields, required-field handling, and regex validation for the linked issue.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@coderabbitai coderabbitai 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.

Actionable comments posted: 8

Caution

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

⚠️ Outside diff range comments (1)
packages/vue/src/components/presentation/user-profile/BaseUserProfile.ts (1)

287-297: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

renderSchemaFieldRow does not forward the isSchemaBased flag.

Line 289 calls shouldShowField(name) with the default isSchemaBased = false. Schema-backed fields are therefore filtered a second time against FIELDS_TO_SKIP, which now contains attributes, isReadOnly, and isReadonly (lines 77-80). The isSchemaBased pre-filter at line 489 is then ineffective for those names, and a schema attribute with a matching name never renders.

Pass the flag through.

🐛 Proposed fix
-    function renderSchemaFieldRow(schema: ExtendedSchema): VNode | null {
+    function renderSchemaFieldRow(schema: ExtendedSchema, isSchemaBased = false): VNode | null {
       const {name, displayName, description, mutability, value} = schema;
-      if (!name || !shouldShowField(name)) return null;
+      if (!name || !shouldShowField(name, isSchemaBased)) return null;

Then call renderSchemaFieldRow(schema, true) from the userSchema branch at line 511.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/vue/src/components/presentation/user-profile/BaseUserProfile.ts`
around lines 287 - 297, Update renderSchemaFieldRow to accept and forward the
isSchemaBased flag when calling shouldShowField, then invoke it with true from
the userSchema rendering branch so schema-backed fields are not filtered by the
non-schema field exclusions.
🧹 Nitpick comments (10)
packages/vue/src/components/presentation/user-profile/UserProfile.css.ts (2)

47-57: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Clean up the img avatar rule.

border-radius: 50% is declared twice (lines 48 and 52). display: flex, align-items: center, and justify-content: center have no effect on a replaced img element. The base .thunderid-user-profile__avatar rule (lines 42-45) already sets border-radius and object-fit, so this rule only needs the explicit width and height.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/vue/src/components/presentation/user-profile/UserProfile.css.ts`
around lines 47 - 57, Clean up the img.thunderid-user-profile__avatar rule by
removing the duplicate border-radius declaration and the ineffective display,
align-items, and justify-content properties; retain only the explicit width and
height overrides, relying on the base .thunderid-user-profile__avatar rule for
shared styles.

168-181: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Remove the conflicting word-break declaration.

white-space: nowrap (line 177) prevents wrapping, so word-break: break-word (line 175) has no effect. display: inline-block also makes align-items: center (line 172) inert. Keep the truncation set (nowrap, overflow: hidden, text-overflow: ellipsis) and drop the two dead declarations.

Also consider adding title on the value span in BaseUserProfile.ts so truncated values remain readable on hover.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/vue/src/components/presentation/user-profile/UserProfile.css.ts`
around lines 168 - 181, Update the .thunderid-user-profile__field-value styles
by removing the ineffective word-break and align-items declarations while
preserving the existing truncation properties. In BaseUserProfile, add a title
attribute to the value span using its displayed value so truncated content is
readable on hover.
packages/vue/src/components/presentation/user-dropdown/BaseUserDropdown.ts (1)

203-204: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low value

The available-space calculation uses the wrong edge.

'left' alignment pins the panel's left edge to the trigger's left edge, so the panel expands rightward from rect.left. The usable space is therefore window.innerWidth - rect.left, not window.innerWidth - rect.right. The current check underestimates the space by the trigger width, so the panel falls back to right alignment in cases where left alignment would still fit.

-      return window.innerWidth - rect.right >= menuWidth ? 'left' : 'right';
+      return window.innerWidth - rect.left >= menuWidth ? 'left' : 'right';
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/vue/src/components/presentation/user-dropdown/BaseUserDropdown.ts`
around lines 203 - 204, Update the left-alignment space check in the dropdown
positioning logic to compare menuWidth against window.innerWidth - rect.left,
since the panel expands rightward from the trigger’s left edge. Preserve the
existing preference for left alignment when it fits and the right-alignment
fallback otherwise.
packages/vue/src/api/getUsersMeMeta.ts (1)

39-54: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract the shared HTTP-to-Response adapter.

This defaultFetcher body duplicates the adapters in packages/vue/src/api/getUsersMe.ts and packages/vue/src/api/updateMeProfile.ts. Only the method default and the request payload differ. Move the adapter into one shared helper, ideally in @thunderid/javascript or @thunderid/browser, and reuse it here.

Before adding a helper to a framework package, check whether it belongs in a lower package in the dependency hierarchy.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/vue/src/api/getUsersMeMeta.ts` around lines 39 - 54, Extract the
shared HTTP-to-Response conversion currently implemented in defaultFetcher into
a reusable helper in the lowest appropriate package, preferably
`@thunderid/javascript` or `@thunderid/browser`, then update defaultFetcher and the
corresponding adapters in getUsersMe and updateMeProfile to reuse it while
preserving their distinct HTTP method defaults and request payloads.

Source: Coding guidelines

packages/vue/src/components/presentation/user-profile/BaseUserProfile.ts (2)

217-244: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

saveField exits edit mode before the update resolves.

props.onUpdate returns a Promise. Line 244 clears the editing flag synchronously, so the row returns to display mode while the request is still running. If the request fails, the row shows the stale value and the field-level error area at line 317 stays empty.

Await the update, and close the editor only after it succeeds.

♻️ Proposed refactor
-    function saveField(schema: ExtendedSchema): void {
+    async function saveField(schema: ExtendedSchema): Promise<void> {
@@
       fieldErrors.value = {...fieldErrors.value, [fieldName]: ''};
       const payload: Record<string, unknown> = buildPatchValue(fieldName, value, schema.schemaId, schema.multiValued);
-      props.onUpdate(payload);
-      editingFields.value = {...editingFields.value, [fieldName]: false};
+      try {
+        await props.onUpdate(payload);
+        editingFields.value = {...editingFields.value, [fieldName]: false};
+      } catch (updateError: unknown) {
+        fieldErrors.value = {
+          ...fieldErrors.value,
+          [fieldName]: updateError instanceof Error ? updateError.message : `Failed to update ${fieldLabel}.`,
+        };
+      }
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/vue/src/components/presentation/user-profile/BaseUserProfile.ts`
around lines 217 - 244, Update saveField to be async and await the Promise
returned by props.onUpdate before clearing editingFields for the field. Keep the
editor open when the update rejects, allowing the existing field-level error
handling to report the failure, and only close it after a successful update.

400-406: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Handle a broken avatar image URL.

picture is rendered directly into img.src. If the URL is unreachable, the browser shows a broken-image icon and the gradient-initials fallback never appears. Add an onError handler that clears the resolved picture and re-renders the initials avatar.

The same pattern exists in packages/vue/src/components/presentation/user-dropdown/BaseUserDropdown.ts (lines 246-259 and 280-286).

Also applies to: 424-434

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/vue/src/components/presentation/user-profile/BaseUserProfile.ts`
around lines 400 - 406, Update BaseUserProfile’s picture rendering and related
avatar markup to handle image load errors: add an onError handler that clears
the resolved picture and triggers the initials fallback when the URL is
unreachable. Follow the existing implementation pattern in BaseUserDropdown, and
apply the same behavior to both referenced avatar render paths.
packages/vue/src/providers/ThunderIDProvider.ts (1)

247-275: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Run the two fetches in parallel and avoid refetching static schema metadata.

getUsersMe and getUsersMeMeta are independent, but they run sequentially. This doubles the latency of every session update. revalidateProfile now calls updateSession() (line 526), so both requests run again after every single field save in BaseUserProfile. The schema metadata is static per tenant, so the repeated /users/me/meta call adds no value.

Run both requests concurrently. Fetch the schema only when userSchema.value is still null.

♻️ Proposed refactor
         if (currentSignInStatus && shouldFetchProfile) {
-          try {
-            const fetchedProfile: User = await getUsersMe({
-              baseUrl,
-              url: resolveResourceEndpoint('usersMe', {endpoints: props.endpoints}),
-              instanceId: props.instanceId,
-            });
-            profileData = {...claims, ...fetchedProfile};
-          } catch {
-            // silent failure, fall back to token claims
-          }
-
-          try {
-            const metaRes = await getUsersMeMeta({
-              baseUrl,
-              url: resolveResourceEndpoint('usersMeMeta', {endpoints: props.endpoints}),
-              instanceId: props.instanceId,
-            });
-            if (metaRes?.schema) {
-              userSchema.value = metaRes.schema;
-            } else {
-              userSchema.value = null;
-            }
-          } catch {
-            userSchema.value = null;
-          }
+          const profilePromise: Promise<User | null> = getUsersMe({
+            baseUrl,
+            url: resolveResourceEndpoint('usersMe', {endpoints: props.endpoints}),
+            instanceId: props.instanceId,
+          }).catch(() => null);
+
+          const metaPromise: Promise<UsersMeMetaResponse | null> = userSchema.value
+            ? Promise.resolve(null)
+            : getUsersMeMeta({
+                baseUrl,
+                url: resolveResourceEndpoint('usersMeMeta', {endpoints: props.endpoints}),
+                instanceId: props.instanceId,
+              }).catch(() => null);
+
+          const [fetchedProfile, metaRes] = await Promise.all([profilePromise, metaPromise]);
+
+          if (fetchedProfile) {
+            profileData = {...claims, ...fetchedProfile};
+          }
+          if (metaRes) {
+            userSchema.value = metaRes.schema ?? null;
+          }
         } else {
           userSchema.value = null;
         }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/vue/src/providers/ThunderIDProvider.ts` around lines 247 - 275,
Update the currentSignInStatus/shouldFetchProfile branch to start getUsersMe
and, only when userSchema.value is null, getUsersMeMeta concurrently rather than
awaiting them sequentially. Preserve the existing profile fallback and schema
error handling, and skip the metadata request when userSchema.value is already
populated.
packages/vue/src/components/presentation/user-dropdown/UserDropdown.css.ts (1)

92-98: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

The img rules duplicate the base rules.

img.thunderid-user-dropdown__avatar sets object-fit: cover and border-radius: 50%, but the base .thunderid-user-dropdown__avatar rule already sets both (lines 84 and 92). The same duplication exists for img.thunderid-user-dropdown__menu-header-avatar against lines 227 and 234. Both img blocks can be removed.

The max-width/max-height pairs at lines 82-83, 104-105, and 113-114 also restate the existing width/height values. Keep them only if a specific image intrinsic-size overflow was observed.

Also applies to: 234-240

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/vue/src/components/presentation/user-dropdown/UserDropdown.css.ts`
around lines 92 - 98, Remove the redundant img.thunderid-user-dropdown__avatar
and img.thunderid-user-dropdown__menu-header-avatar rules because their
object-fit and border-radius declarations duplicate the base avatar styles. Also
remove the redundant max-width/max-height declarations that merely restate the
corresponding width/height values, unless they are required to prevent observed
intrinsic image overflow.
packages/vue/src/components/presentation/user-profile/UserProfile.ts (1)

83-90: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use deepMerge for preference resolution.

deepMerge is already imported in this file. The manual spread only merges the user branch. If a consumer passes a partial theme or i18n object in props.preferences, that branch replaces the context value entirely instead of merging.

-    const resolvedPreferences = computed(() => ({
-      ...contextPreferences,
-      ...props.preferences,
-      user: {
-        ...contextPreferences?.user,
-        ...props.preferences?.user,
-      },
-    }));
+    const resolvedPreferences = computed<Preferences>(() => deepMerge({...(contextPreferences ?? {})}, props.preferences));
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/vue/src/components/presentation/user-profile/UserProfile.ts` around
lines 83 - 90, Update the resolvedPreferences computed value to use the imported
deepMerge utility instead of manual object spreads, merging contextPreferences
with props.preferences so nested preference branches such as user, theme, and
i18n are preserved and combined.
packages/vue/src/providers/UserProvider.ts (1)

66-68: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Drop the nonexistent inline fallback for profile.userSchema.

UserProvider accepts userSchema as its own prop, and both current producers pass it separately. The (props.profile as any)?.userSchema branch can never resolve to a sourced value, and the as any hides the UserProfile shape mismatch. Use props.userSchema ?? null.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/vue/src/providers/UserProvider.ts` around lines 66 - 68, Update the
userSchemaRef computed value in UserProvider to use only props.userSchema,
falling back to null when absent; remove the profile.userSchema access and its
any cast.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/nuxt/src/runtime/server/ThunderIDNuxtClient.ts`:
- Around line 219-235: Update ThunderIDNuxtClient.getUserSchema to preserve
schema-fetch failures by rejecting errors or returning an explicit error result
instead of converting every failure to null; retain null only for a valid
response without schema. In
packages/nuxt/src/runtime/server/plugins/thunderid-ssr.ts#L165-L167, handle the
selected failure contract in the Promise.allSettled result and log schema
request failures.

In `@packages/nuxt/src/runtime/utils/stateKeys.ts`:
- Around line 25-31: Update getUserSchemaStateKey to derive its vendor prefix
through the shared getVendorPrefix(vendor) resolver instead of applying the
local default directly, while preserving the existing user-schema key format and
default behavior.

In `@packages/vue/src/components/presentation/user-dropdown/BaseUserDropdown.ts`:
- Around line 116-122: The avatar URL candidate list is duplicated and
incorrectly treats the OIDC profile URL as an image; create one shared resolver
in the lowest suitable package, excluding profile and preserving the fallback to
attributes.picture, then replace the inline resolution in BaseUserDropdown.ts
(116-122) and BaseUserProfile.ts (400-406) with that resolver. Neither site
requires separate logic; both must use the same resolver and retain
gradient-initials fallback when image loading fails.
- Around line 380-407: Update the teleported profile modal rendering and
handleKeyDown to provide dialog semantics: role="dialog", aria-modal="true", and
an accessible name. On modal open, move focus to the modal or close button; trap
Tab and Shift+Tab within modal controls, and close on Escape. Wrap the existing
props.onProfileModalClose path so closing restores focus to the dropdown
trigger, including overlay and close-button dismissal.

In `@packages/vue/src/components/presentation/user-profile/BaseUserProfile.ts`:
- Around line 229-239: Update the regex validation in BaseUserProfile to bound
the server-supplied schema.regex length before compiling or testing it,
preventing pathological patterns from freezing the UI. Cache the compiled RegExp
per field instead of rebuilding it on every save, while preserving
invalid-pattern pass-through behavior. In the catch block, add a console.warn
containing the field context and compilation error for diagnosability.
- Around line 224-234: Update BaseUserProfile validation messages and the
Save/Cancel labels to use the SDK i18n layer via useI18n, passing translation
keys with the current English text as fallbacks. Apply this to the required and
regex errors (including the additional referenced occurrences), and use “has an
invalid format” instead of “is invalid format.”
- Around line 508-513: Update the metaSchemas mapping in the field-row rendering
flow to reuse the value already resolved from editedValues and the current
user’s attributes/top-level fields, rather than recomputing
currentUser[schema.name]. Preserve that computed value when passing the schema
to renderSchemaFieldRow so nested attributes and in-progress edits remain
visible.

In `@packages/vue/src/components/presentation/user-profile/UserProfile.css.ts`:
- Around line 13-23: Update the .thunderid-user-profile rule to replace the
fixed 600px min-width with a responsive constraint that fits its parent and
small viewports. Preserve the existing compact modifier behavior and prevent
horizontal overflow within the UserDropdown modal.

---

Outside diff comments:
In `@packages/vue/src/components/presentation/user-profile/BaseUserProfile.ts`:
- Around line 287-297: Update renderSchemaFieldRow to accept and forward the
isSchemaBased flag when calling shouldShowField, then invoke it with true from
the userSchema rendering branch so schema-backed fields are not filtered by the
non-schema field exclusions.

---

Nitpick comments:
In `@packages/vue/src/api/getUsersMeMeta.ts`:
- Around line 39-54: Extract the shared HTTP-to-Response conversion currently
implemented in defaultFetcher into a reusable helper in the lowest appropriate
package, preferably `@thunderid/javascript` or `@thunderid/browser`, then update
defaultFetcher and the corresponding adapters in getUsersMe and updateMeProfile
to reuse it while preserving their distinct HTTP method defaults and request
payloads.

In `@packages/vue/src/components/presentation/user-dropdown/BaseUserDropdown.ts`:
- Around line 203-204: Update the left-alignment space check in the dropdown
positioning logic to compare menuWidth against window.innerWidth - rect.left,
since the panel expands rightward from the trigger’s left edge. Preserve the
existing preference for left alignment when it fits and the right-alignment
fallback otherwise.

In `@packages/vue/src/components/presentation/user-dropdown/UserDropdown.css.ts`:
- Around line 92-98: Remove the redundant img.thunderid-user-dropdown__avatar
and img.thunderid-user-dropdown__menu-header-avatar rules because their
object-fit and border-radius declarations duplicate the base avatar styles. Also
remove the redundant max-width/max-height declarations that merely restate the
corresponding width/height values, unless they are required to prevent observed
intrinsic image overflow.

In `@packages/vue/src/components/presentation/user-profile/BaseUserProfile.ts`:
- Around line 217-244: Update saveField to be async and await the Promise
returned by props.onUpdate before clearing editingFields for the field. Keep the
editor open when the update rejects, allowing the existing field-level error
handling to report the failure, and only close it after a successful update.
- Around line 400-406: Update BaseUserProfile’s picture rendering and related
avatar markup to handle image load errors: add an onError handler that clears
the resolved picture and triggers the initials fallback when the URL is
unreachable. Follow the existing implementation pattern in BaseUserDropdown, and
apply the same behavior to both referenced avatar render paths.

In `@packages/vue/src/components/presentation/user-profile/UserProfile.css.ts`:
- Around line 47-57: Clean up the img.thunderid-user-profile__avatar rule by
removing the duplicate border-radius declaration and the ineffective display,
align-items, and justify-content properties; retain only the explicit width and
height overrides, relying on the base .thunderid-user-profile__avatar rule for
shared styles.
- Around line 168-181: Update the .thunderid-user-profile__field-value styles by
removing the ineffective word-break and align-items declarations while
preserving the existing truncation properties. In BaseUserProfile, add a title
attribute to the value span using its displayed value so truncated content is
readable on hover.

In `@packages/vue/src/components/presentation/user-profile/UserProfile.ts`:
- Around line 83-90: Update the resolvedPreferences computed value to use the
imported deepMerge utility instead of manual object spreads, merging
contextPreferences with props.preferences so nested preference branches such as
user, theme, and i18n are preserved and combined.

In `@packages/vue/src/providers/ThunderIDProvider.ts`:
- Around line 247-275: Update the currentSignInStatus/shouldFetchProfile branch
to start getUsersMe and, only when userSchema.value is null, getUsersMeMeta
concurrently rather than awaiting them sequentially. Preserve the existing
profile fallback and schema error handling, and skip the metadata request when
userSchema.value is already populated.

In `@packages/vue/src/providers/UserProvider.ts`:
- Around line 66-68: Update the userSchemaRef computed value in UserProvider to
use only props.userSchema, falling back to null when absent; remove the
profile.userSchema access and its any cast.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 0300dac9-dc66-442f-bb3f-7ff96ccca54c

📥 Commits

Reviewing files that changed from the base of the PR and between 2ef39d8 and 3df13f1.

📒 Files selected for processing (21)
  • packages/nuxt/src/module.ts
  • packages/nuxt/src/runtime/components/ThunderIDRoot.ts
  • packages/nuxt/src/runtime/plugins/thunderid.ts
  • packages/nuxt/src/runtime/server/ThunderIDNuxtClient.ts
  • packages/nuxt/src/runtime/server/plugins/thunderid-ssr.ts
  • packages/nuxt/src/runtime/server/utils/session.ts
  • packages/nuxt/src/runtime/types.ts
  • packages/nuxt/src/runtime/utils/stateKeys.ts
  • packages/nuxt/tests/unit/define-thunderid-middleware.test.ts
  • packages/nuxt/tests/unit/thunderid-ssr.test.ts
  • packages/vue/src/api/getUsersMeMeta.ts
  • packages/vue/src/api/updateMeProfile.ts
  • packages/vue/src/components/presentation/user-dropdown/BaseUserDropdown.ts
  • packages/vue/src/components/presentation/user-dropdown/UserDropdown.css.ts
  • packages/vue/src/components/presentation/user-profile/BaseUserProfile.ts
  • packages/vue/src/components/presentation/user-profile/UserProfile.css.ts
  • packages/vue/src/components/presentation/user-profile/UserProfile.ts
  • packages/vue/src/index.ts
  • packages/vue/src/models/contexts.ts
  • packages/vue/src/providers/ThunderIDProvider.ts
  • packages/vue/src/providers/UserProvider.ts

Comment thread packages/nuxt/src/runtime/server/ThunderIDNuxtClient.ts Outdated
Comment thread packages/nuxt/src/runtime/utils/stateKeys.ts Outdated
Comment thread packages/vue/src/components/presentation/user-dropdown/BaseUserDropdown.ts Outdated

Copilot AI 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.

Pull request overview

Integrates the /users/me/meta schema endpoint into the Vue (@thunderid/vue) and Nuxt (@thunderid/nuxt) SDKs so user-profile UI can render schema-driven fields and Nuxt can SSR-hydrate both the user profile and its schema metadata.

Changes:

  • Vue: fetches /users/me + /users/me/meta during session update, threads userSchema through providers/composables, and renders schema-driven profile fields with validation.
  • Nuxt: adds SSR parallel fetch for user schema, hydrates it into useState, and exposes it through <ThunderIDRoot>/UserProvider.
  • UI: updates avatar rendering (image fallback) and adjusts UserProfile/UserDropdown styling and modal behavior.

Reviewed changes

Copilot reviewed 21 out of 21 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
packages/vue/src/providers/UserProvider.ts Adds userSchema to the User context and wires it from props into provide() context.
packages/vue/src/providers/ThunderIDProvider.ts Fetches /users/me and /users/me/meta (gated by preferences) and passes userSchema downstream.
packages/vue/src/models/contexts.ts Extends Vue context types to include preferences and userSchema.
packages/vue/src/index.ts Exports the new Vue getUsersMeMeta helper.
packages/vue/src/components/presentation/user-profile/UserProfile.ts Adds preferences merging, gates editability when profile fetch is disabled, and supports updateProfile callback + schema-driven profile editing.
packages/vue/src/components/presentation/user-profile/UserProfile.css.ts Updates UserProfile styling to align closer to React parity.
packages/vue/src/components/presentation/user-profile/BaseUserProfile.ts Implements schema-driven field rendering, required/regex validation, and avatar image fallback.
packages/vue/src/components/presentation/user-dropdown/UserDropdown.css.ts Updates avatar sizing/object-fit and modal sizing/layout.
packages/vue/src/components/presentation/user-dropdown/BaseUserDropdown.ts Adds avatar image support and teleports the profile modal overlay to body.
packages/vue/src/api/updateMeProfile.ts Switches default update method to PUT.
packages/vue/src/api/getUsersMeMeta.ts Adds Vue API wrapper for /users/me/meta using SPA FetchHttpClient.
packages/nuxt/tests/unit/thunderid-ssr.test.ts Updates SSR unit test mocking to include getUserSchema().
packages/nuxt/tests/unit/define-thunderid-middleware.test.ts Extends Nuxt #app mocks to include useRuntimeConfig().
packages/nuxt/src/runtime/utils/stateKeys.ts Adds a vendor-scoped state key for SSR-hydrated user schema.
packages/nuxt/src/runtime/types.ts Refactors ThunderIDNuxtConfig to extend AuthClientConfig and adds SSR userSchema typing.
packages/nuxt/src/runtime/server/utils/session.ts Makes session cookie name helpers vendor-aware via CookieConfig helpers.
packages/nuxt/src/runtime/server/ThunderIDNuxtClient.ts Adds server-side helpers for fetching user/profile/schema and updating the profile via /users/me.
packages/nuxt/src/runtime/server/plugins/thunderid-ssr.ts Fetches userSchema concurrently during SSR and injects into event.context.
packages/nuxt/src/runtime/plugins/thunderid.ts Hydrates userSchema into vendor-scoped useState on server.
packages/nuxt/src/runtime/components/ThunderIDRoot.ts Passes SSR-hydrated userSchema into Vue UserProvider.
packages/nuxt/src/module.ts Exposes endpoints through Nuxt runtime config and module types.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread packages/vue/src/models/contexts.ts Outdated
Comment thread packages/nuxt/src/runtime/server/ThunderIDNuxtClient.ts
Comment thread packages/nuxt/src/runtime/server/ThunderIDNuxtClient.ts Outdated
@janithjay
janithjay force-pushed the feat/vue-profile-edit branch from 3df13f1 to 1d1eafb Compare August 7, 2026 10:50

@coderabbitai coderabbitai 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.

Actionable comments posted: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/vue/src/components/presentation/user-profile/BaseUserProfile.ts`:
- Line 247: Remove the hardcoded “[ThunderID]” prefix from the warning in the
user schema regular-expression validation path, or replace it with the
configured value from getVendorPrefix(vendor). Update the console.warn call
without changing its existing error details or behavior.
- Around line 224-254: The saveField function currently closes the editor
without waiting for the asynchronous props.onUpdate result. Make saveField
asynchronous, await props.onUpdate(payload), and move the editingFields update
so it runs only after the update succeeds, allowing failures to keep the field
in edit mode.
- Around line 359-367: Update the Button configuration in BaseUserProfile’s
edit-button render to use supported ButtonProps values: replace the invalid
tertiary color and icon variant with valid color and variant values, and
represent the edit icon through startIcon or endIcon instead of variant.
- Around line 227-253: Update the payload construction in the field-validation
flow to submit the normalized trimmed string value (strVal) rather than the
original value, while preserving existing handling for empty and non-string
values as required by buildPatchValue and multiValued fields.
- Around line 216-221: Update cancelEditing to restore nested attribute values
using the same lookup precedence as schema rendering: when the requested field
is absent at the profile top level, read it from data.attributes before falling
back to an empty string. Preserve existing top-level restoration and
editing/error state updates.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 166fa4f6-9a8f-4a10-9552-033862444dbe

📥 Commits

Reviewing files that changed from the base of the PR and between 3df13f1 and 1d1eafb.

📒 Files selected for processing (6)
  • packages/nuxt/src/runtime/server/ThunderIDNuxtClient.ts
  • packages/nuxt/src/runtime/utils/stateKeys.ts
  • packages/vue/src/components/presentation/user-dropdown/BaseUserDropdown.ts
  • packages/vue/src/components/presentation/user-profile/BaseUserProfile.ts
  • packages/vue/src/components/presentation/user-profile/UserProfile.css.ts
  • packages/vue/src/components/presentation/user-profile/UserProfile.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • packages/nuxt/src/runtime/server/ThunderIDNuxtClient.ts
  • packages/vue/src/components/presentation/user-profile/UserProfile.css.ts
  • packages/vue/src/components/presentation/user-dropdown/BaseUserDropdown.ts
  • packages/vue/src/components/presentation/user-profile/UserProfile.ts

Comment thread packages/vue/src/components/presentation/user-profile/BaseUserProfile.ts Outdated
Comment thread packages/vue/src/components/presentation/user-profile/BaseUserProfile.ts Outdated
Comment thread packages/vue/src/components/presentation/user-profile/BaseUserProfile.ts Outdated
Comment thread packages/vue/src/components/presentation/user-profile/BaseUserProfile.ts Outdated
@janithjay
janithjay force-pushed the feat/vue-profile-edit branch from 1d1eafb to b7060fe Compare August 7, 2026 12:13

@coderabbitai coderabbitai 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.

Actionable comments posted: 4

♻️ Duplicate comments (2)
packages/vue/src/components/presentation/user-profile/BaseUserProfile.ts (2)

220-226: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Use the schema value precedence when cancelling.

Line 223 reads the top-level field before attributes. Line 522 renders attributes before the top-level field. If both values exist, Cancel restores one value but the row renders another value.

Proposed fix
-        (data as Record<string, any>)?.[fieldName] ?? (data as any)?.attributes?.[fieldName] ?? '';
+        (data as any)?.attributes?.[fieldName] ?? (data as Record<string, any>)?.[fieldName] ?? '';
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/vue/src/components/presentation/user-profile/BaseUserProfile.ts`
around lines 220 - 226, Update cancelEditing so originalValue reads
data.attributes[fieldName] before the top-level data[fieldName], matching the
rendering precedence used by the component and ensuring Cancel restores the
displayed value when both exist.

244-265: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Validate regex safety before browser execution.

The 250-character limit and cache do not prevent catastrophic-backtracking patterns. A schema regex can still block the browser main thread during reg.test(strVal). Enforce regex safety at the schema source, or avoid executing tenant-defined regexes in the client.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/vue/src/components/presentation/user-profile/BaseUserProfile.ts`
around lines 244 - 265, Update the regex validation flow in the user-profile
field handling around regexCache and reg.test so tenant-defined schema patterns
cannot execute unsafely in the browser. Prefer validating or rejecting regexes
at the schema source; otherwise remove client-side execution of tenant-provided
patterns while preserving the existing invalid-format behavior for safe
validation.

Source: Linters/SAST tools

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/vue/src/components/presentation/user-dropdown/BaseUserDropdown.ts`:
- Around line 179-224: Scope modal focus and close handling to the current
BaseUserDropdown instance by storing its teleported modal overlay in a component
ref and using that ref instead of document.querySelector in handleKeyDown and
the isProfileModalOpen watcher. Query the close button from the
instance-specific overlay ref, and ensure Escape only closes this instance’s
modal without affecting other open dropdowns.
- Around line 285-298: Update the avatar rendering in BaseUserDropdown to track
image-load failure reactively, attach an error handler to the rendered img, and
reset the failure state whenever picture changes. When loading fails, render the
existing gradient initials span instead of the broken image, while preserving
the current picture-based rendering for successful loads.
- Around line 242-243: Update the auto-alignment logic in BaseUserDropdown to
compare available viewport space on both sides of the trigger and choose the
side with more room, preserving the documented menuAlign="auto" contract. Do not
retain the current right-space-only, left-preferred condition unless the public
documentation is intentionally changed instead.

In `@packages/vue/src/components/presentation/user-profile/BaseUserProfile.ts`:
- Around line 385-393: Replace the hardcoded "Edit" title in the Button
configuration within BaseUserProfile with the existing t translation helper,
matching the localization pattern used by adjacent actions while leaving the
button behavior unchanged.

---

Duplicate comments:
In `@packages/vue/src/components/presentation/user-profile/BaseUserProfile.ts`:
- Around line 220-226: Update cancelEditing so originalValue reads
data.attributes[fieldName] before the top-level data[fieldName], matching the
rendering precedence used by the component and ensuring Cancel restores the
displayed value when both exist.
- Around line 244-265: Update the regex validation flow in the user-profile
field handling around regexCache and reg.test so tenant-defined schema patterns
cannot execute unsafely in the browser. Prefer validating or rejecting regexes
at the schema source; otherwise remove client-side execution of tenant-provided
patterns while preserving the existing invalid-format behavior for safe
validation.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: be2ef43d-d00c-44d9-bff7-ea8f978978fe

📥 Commits

Reviewing files that changed from the base of the PR and between 1d1eafb and b7060fe.

📒 Files selected for processing (2)
  • packages/vue/src/components/presentation/user-dropdown/BaseUserDropdown.ts
  • packages/vue/src/components/presentation/user-profile/BaseUserProfile.ts

Comment thread packages/vue/src/components/presentation/user-dropdown/BaseUserDropdown.ts Outdated
Comment thread packages/vue/src/components/presentation/user-dropdown/BaseUserDropdown.ts Outdated
@janithjay
janithjay force-pushed the feat/vue-profile-edit branch 2 times, most recently from 2ccd992 to f6ae91e Compare August 7, 2026 12:39
@janithjay
janithjay force-pushed the feat/vue-profile-edit branch from f6ae91e to 93d0cbb Compare August 7, 2026 13:06
@brionmario
brionmario merged commit 16c65c2 into thunder-id:main Aug 7, 2026
3 checks passed
@janithjay
janithjay deleted the feat/vue-profile-edit branch August 8, 2026 04:34
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.

Integrate /users/me/meta schema endpoint into SDKs for field validation on profile updates

3 participants