-
Notifications
You must be signed in to change notification settings - Fork 433
Asset Browser Modal Component #5607
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
🎭 Playwright Test Results✅ All tests passed! ⏰ Completed at: 09/17/2025, 06:44:07 PM UTC 📈 Summary
📊 Test Reports by Browser
🎉 Click on the links above to view detailed test results for each browser configuration. |
tests-ui/platform/assets/composables/useAssetBrowserDialog.test.ts
Outdated
Show resolved
Hide resolved
tests-ui/platform/assets/composables/useAssetBrowserDialog.test.ts
Outdated
Show resolved
Hide resolved
f345a2d to
8ef528b
Compare
| // Replace plugins entirely to avoid inheritance issues | ||
| plugins: [ | ||
| // Only include plugins we explicitly need for Storybook | ||
| tailwindcss(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the filtering above was filtering out tailwind apparently.
And all related sub components
b395af9 to
81a0046
Compare
| safelist: [ | ||
| 'icon-[lucide--folder]', | ||
| 'icon-[lucide--package]', | ||
| 'icon-[lucide--image]', | ||
| 'icon-[lucide--video]', | ||
| 'icon-[lucide--box]', | ||
| 'icon-[lucide--audio-waveform]', | ||
| 'icon-[lucide--message-circle]' | ||
| ], | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think you'll need this if you use the pattern in #5453
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're right about the iconify plugin from PR #5453, but that approach only works for static template usage like:
<i class="icon-[lucide--folder]">The safelist is still needed for dynamic usage where icons are assigned as strings in JavaScript objects:
{ icon: 'icon-[lucide--folder]' } // Tailwind can't detect thisWe have 3 icons used this way in useAssetBrowser.ts (we have to map them to api categories) and menu composables. However, 4 icons in the safelist are unused and can be removed.
The iconify plugin can't solve the dynamic string assignment problem. Tha'ts why we need the safelist.
| // Compute content title from selected category | ||
| const contentTitle = computed(() => { | ||
| if (selectedCategory.value === 'all') { | ||
| return 'All Models' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[quality] medium Priority
Issue: Hardcoded strings 'All Models' and 'Assets' are not internationalized
Context: Project guidelines require all user-facing strings use vue-i18n for consistency and localization
Suggestion: Replace with i18n keys like $t('assetBrowser.allModels') and $t('assetBrowser.assets')
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This one might be harder to catch in a linter
| // Extract description from metadata or create from tags | ||
| const typeTag = asset.tags.find((tag) => tag !== 'models') | ||
| const description = | ||
| asset.user_metadata?.description || `${typeTag || 'Unknown'} model` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[quality] medium Priority
Issue: Hardcoded 'Unknown' string in template literal is not internationalized
Context: Project guidelines require all user-facing strings use vue-i18n
Suggestion: Replace with $t('assetBrowser.unknownModel') or similar i18n key
| " | ||
| @click="interactive && $emit('select', asset)" | ||
| @keydown.enter="interactive && $emit('select', asset)" | ||
| @keydown.space.prevent="interactive && $emit('select', asset)" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[quality] medium Priority
Issue: Keyboard space handler uses prevent modifier which may interfere with screen reader functionality
Context: For button elements, space key should use natural button behavior rather than preventing default
Suggestion: Remove .prevent from space handler or ensure proper button semantics are maintained for accessibility
| created_at: z.string(), | ||
| updated_at: z.string(), | ||
| last_access_time: z.string(), | ||
| user_metadata: z.record(z.any()).optional(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[quality] low Priority
Issue: z.any() usage in user_metadata field reduces type safety
Context: z.any() defeats the purpose of schema validation and TypeScript type checking
Suggestion: Consider using z.record(z.string()) or a more specific schema for user_metadata to maintain type safety
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ironically this isn't low, this is probably the most important one.
|
|
||
| // Create display stats from API data | ||
| const stats = { | ||
| formattedDate: new Date(asset.created_at).toLocaleDateString(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[architecture] medium Priority
Issue: Date parsing with new Date() lacks error handling for invalid date strings
Context: If asset.created_at has invalid date format, this will create Invalid Date objects leading to 'Invalid Date' display
Suggestion: Add date validation or fallback: new Date(asset.created_at).toLocaleDateString() || 'Unknown date'
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Quick warning, the suggested fix would not behave as described
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Our i18n library has date localization as well
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Comprehensive PR Review
This review is generated by Claude. It may not always be accurate, as with human reviewers. If you believe that any of the comments are invalid or incorrect, please state why for each. For others, please implement the changes in one way or another.
Review Summary
PR: Asset Browser Modal Component (#5607)
Impact: 2518 additions, 61 deletions across 25 files
Issue Distribution
- Critical: 0
- High: 1
- Medium: 6
- Low: 1
Category Breakdown
- Architecture: 1 issue
- Security: 0 issues
- Performance: 1 issue
- Code Quality: 6 issues
Key Findings
Architecture & Design
The asset browser implementation follows Vue 3 Composition API patterns well and maintains proper separation of concerns between components and composables. The modal system integration is architected correctly using the dialog store. However, there's a missing error handling pattern for date parsing that could lead to poor user experience.
Security Considerations
No critical security issues identified. The implementation properly uses TypeScript interfaces and schema validation with Zod, though the use of z.any() reduces type safety marginally.
Performance Impact
Asset transformation occurs on every filter operation which could be expensive for large lists. Consider memoizing the transformation results to avoid redundant processing. Bundle size impact appears minimal with good component chunking.
Integration Points
Good integration with existing UI components (SearchBox, LeftSidePanel, BaseModalLayout). Proper use of PrimeVue components and following established patterns. The composable architecture allows for good reusability.
Positive Observations
- Excellent use of Vue 3 Composition API patterns
- Good component decomposition and reusability
- Proper TypeScript typing throughout
- Comprehensive test coverage including Storybook stories
- Good use of existing UI components rather than reinventing
- Proper Tailwind utility usage with dark mode support
- Clean modal dialog integration
Critical Issues to Address
- Class merging patterns: The AssetCard component uses array-based class merging which violates project guidelines. Must use cn() utility instead.
References
Next Steps
- Address the high-priority class merging issue before merge
- Consider internationalization for all hardcoded strings
- Add error handling for date parsing
- Consider performance optimization for large asset lists
- Add tests for error scenarios
This is a comprehensive automated review. For architectural decisions requiring human judgment, please request additional manual review.
5499489 to
de5bb01
Compare
|
@DrJKL You're absolutely correct about the cn() usage in AssetCard.vue:7. The code is properly using cn() with the correct pattern. The automated review may have been looking at an earlier commit or different section. Regarding the tailwind config safelist comment: You're right that with the iconify plugin from PR #5453, those hardcoded icon names in the safelist might not be necessary since the plugin handles dynamic icon selection. Should I remove those from the safelist? |
| :class=" | ||
| cn( | ||
| // Base layout and container styles (always applied) | ||
| 'rounded-xl overflow-hidden transition-all duration-200 min-w-60 max-w-64', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The min-width and max-width CSS on the button should be removed. It’s better to let the width adjust dynamically based on the browser size, with the parent component controlling the layout using grid-template. Removing those two properties makes the screen look like this.
As a side note, if the card looks too wide, you can set a smaller minWidth in createGridStyle to balance it out.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i will remove the min/max for now and if we run into issues talking to design on the size of hte cards we'll adjust minWidth in createGridStyle
| } | ||
| }, | ||
| template: ` | ||
| <div class="flex items-center justify-center min-h-screen bg-stone-200 dark-theme:bg-stone-200 p-4"> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| <div class="flex items-center justify-center min-h-screen bg-stone-200 dark-theme:bg-stone-200 p-4"> | |
| <div class="flex items-center justify-center min-h-screen bg-stone-200 p-4"> |
| return { ...args, onAssetSelect, onClose, assets: singleTypeAssets } | ||
| }, | ||
| template: ` | ||
| <div class="flex items-center justify-center min-h-screen bg-stone-200 dark-theme:bg-stone-200 p-4"> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| <div class="flex items-center justify-center min-h-screen bg-stone-200 dark-theme:bg-stone-200 p-4"> | |
| <div class="flex items-center justify-center min-h-screen bg-stone-200 p-4"> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here and elsewhere. Don't want to confuse the robots with this pattern.
| @update:model-value="handleFilterChange" | ||
| > | ||
| <template #icon> | ||
| <i-lucide:arrow-up-down class="size-3" /> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should be able to add a lint rule to catch these.
| { | ||
| id: 'all', | ||
| label: t('assetBrowser.allModels'), | ||
| icon: 'icon-[lucide--folder]' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this cause them to be picked up by Tailwind?
| icon: 'icon-[lucide--folder]' | |
| icon: cn('icon-[lucide--folder]') |
| .sort() | ||
| .map((category) => ({ | ||
| id: category, | ||
| label: category.charAt(0).toUpperCase() + category.slice(1), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This would also probably be an i18n issue.
christian-byrne
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM!
| label="File formats" | ||
| :options="fileFormatOptions" | ||
| :class="selectClasses" | ||
| data-component-id="asset-filter-file-formats" | ||
| @update:model-value="handleFilterChange" | ||
| /> | ||
|
|
||
| <MultiSelect | ||
| v-model="baseModels" | ||
| label="Base models" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are the labels translated?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Another good catch. I will make sure that happens.
* [ci] ignore playwright mcp directory * [feat] add AssetBrowserModal And all related sub components * [feat] reactive filter functions * [ci] clean up storybook config * [feat] add sematic AssetCard * [fix] i love lucide * [fix] AssetCard layout issues * [fix] add AssetBadge type * [fix] simplify useAssetBrowser * [fix] modal layout * [fix] simplify useAssetBrowserDialog * [fix] add tailwind back to storybook * [fix] better reponsive layout * [fix] missed i18n string * [fix] missing i18n translations * [fix] remove erroneous prevent on keyboard.space * [feat] add asset metadata validation utilities * [fix] remove erroneous test code * [fix] remove forced min and max width on AssetCard * [fix] import statement nits




Summary
These are all the components and updated api logic for our Asset Browser. Next step is wiring up the flow end to end!
Changes
Not in this PR:
Review Focus
Architecture. Did I Vue it right?
Screenshots (if applicable)
You can download this branch and also run the storybook stories
┆Issue is synchronized with this Notion page by Unito