fix(macos): title-case profile name when label is missing or blank#30420
Closed
ashleeradka wants to merge 1 commit into
Closed
fix(macos): title-case profile name when label is missing or blank#30420ashleeradka wants to merge 1 commit into
ashleeradka wants to merge 1 commit into
Conversation
LUM-1519. The chat composer pill, profile list, default-profile dropdown and accessibility labels read from `InferenceProfile.displayName`, which fell back to the raw `name` identifier when `label` was absent. On Cloud, the platform-overlay seeds profiles without populating `label` (see assistant/src/config/seed-inference-profiles.ts where platform mode short-circuits re-seeding once a profile entry exists), so freshly hatched assistants showed kebab-case or empty values in the UI. Treat empty or whitespace-only `label` as missing (in addition to nil), and title-case the `name` fallback by splitting on `-`, `_`, and whitespace. The decoder at `init(name:json:)` already normalises empty strings, but centralising the empty check at the display layer keeps the fallback robust against any other mutation path. The `name` field itself stays untouched — it is the stable identifier used as the profile key. Noa is separately addressing the underlying data issue (why `label` is blank for new assistants); this PR is the UI-side hardening. Closes LUM-1519 Co-Authored-By: ashlee@vellum.ai <ashlee@vellum.ai>
Contributor
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes LUM-1519.
Prompt / plan
InferenceProfile.displayNameis read by the chat composer pill, theprofile list, the default-profile dropdown, the composer settings menu,
and a handful of accessibility labels. It fell back to the raw
nameidentifier whenever
labelwas nil. On Cloud, the platform-overlayseeds profiles without populating
label(assistant/src/config/seed-inference-profiles.tsshort-circuits re-seeding under
isPlatformonce a profile entryexists), so freshly hatched assistants showed kebab-case identifiers
like
quality-optimized— or nothing at all if the label happened tobe an empty string rather than nil — in spots that should read
"Quality Optimized".
Make the fallback empty-aware (treat
""and whitespace as missing,not just
nil) and title-case thenamewhen it fires. Thenamefield itself stays untouched; it's the stable identifier used as the
profile key in lookups, the conversation override, and call-site
overrides.
Noa is separately addressing the underlying data issue (why
labelisblank for new assistants on Cloud); this PR is the UI-side hardening
explicitly requested in the ticket.
What changed
clients/macos/vellum-assistant/Features/Settings/InferenceProfile.swiftdisplayNamenow returnslabelonly when it is non-nil andnon-empty after trimming; otherwise it returns
Self.titleCased(name).static func titleCased(_:)which splits on-,_, andwhitespace, capitalises the first character of each non-empty part,
and joins with single spaces. Falls through to the input unchanged
when no usable parts remain (e.g.
"---").Why this is safe
NotificationCenter payload touches the change.
the rendered string. Old daemon + new macOS and new daemon + old
macOS both behave identically since this lives entirely inside the
macOS client's render path.
displayName(profile list rows,ChatProfilePicker.label,InferenceServiceCarddropdown options,ComposerSettingsMenuprofile picker, accessibility labels inInferenceProfilesSheet) benefit transparently — no call-sitechanges needed.
Alternatives considered
nameat decode time so it's pre-formatted. Rejected:nameis the stable identifier used as a profile key inactiveProfilelookups,id, call-site overrides, and conversationoverride fields. Display formatting must remain a derived view-only
concern.
sites depend on
displayName. Centralising in the computed propertykeeps the convention enforceable and the test surface narrow.
String.capitalizeddirectly. Rejected:"quality-optimized".capitalizedreturns"Quality-Optimized"because Foundation's
capitalizedtreats
-and_as part of a word, not a separator.(provider connections). Out of scope per the ticket — Noa is
handling the root-cause data issue separately. UI hardening also
guards against future label-blank scenarios.
Stringextension and DRY up withMessageInspectorSummaryFormatters.titleCasedProviderLabel.Considered but explicitly de-scoped for this PR per request: the
ticket asks for the minimal UI fallback while the data root cause is
being fixed separately. The provider-label helper lives in a
different domain (inspector summary) and a follow-up consolidation
is safer once both call sites have settled.
Test plan
InferenceProfiledisplayNametests inInferenceProfileTests.swiftto expect the title-cased fallback:testTitleCasedHandlesCommonIdentifierShapescoveringbalanced,quality-optimized,custom_balanced,custom-quality-optimized, surrounding/repeated whitespace, allseparators (
---), and empty input.ChatProfilePickerTestscases that previouslyencoded the raw-kebab fallback (
"Default (balanced)","quality-optimized","Default (cost-optimized)") to thetitle-cased output.
before merging.
Root cause analysis
displayNamewas addedas
label ?? namewhen the label/source/description fieldslanded. At the time, every seeded profile included a
label, sothe fallback was only ever exercised by ad-hoc user-created
profiles where the kebab
namewas acceptable.never explicitly designed for the platform-overlay case where
labelis missing. The decoder normalises""to nil, whichmasked the empty-string scenario in unit tests and meant the gap
only surfaced once the Cloud platform started serving
label-less profiles.
fixed the same symptom for provider connections (label blank,
editor renders empty) by backfilling
label = name. The samepattern across the codebase suggests a generalised "always have a
sensible display fallback when label is missing" convention is
missing.
identifier field paired with a user-friendly
labelshouldcompute a display value that is presentable even when
labelisabsent. Title-casing the identifier is the cheap, reliable
default.
points (
InferenceProfile, provider connections) is suggestivebut not enough to warrant a project-wide rule; a follow-up that
factors out a shared
titleCasedIdentifierextension would be theright time to codify the convention.
References
String.capitalized(why we can't use it directly: keeps
-and_as part of a word)CharacterSetString.capitalized,CharacterSet.union(_:),String.components(separatedBy:)Link to Devin session: https://app.devin.ai/sessions/bac5a01cf5be4de69e79e16034b50cfb
Requested by: @ashleeradka