Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .jules/bolt.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,7 @@
## 2025-05-23 - [Inline SVG Bloat]
**Learning:** Inline SVGs in Vue components, especially those exported directly from design tools without optimization, can be excessively large (e.g., 10KB+ for a single icon). This bloats the initial HTML payload and bundle size.
**Action:** Always inspect inline SVGs in critical components (like Headers/Footers). Replace complex paths with optimized standard icons (e.g., Heroicons) or use an SVG optimization tool.

## 2025-06-12 - [Client-Side Filtering Overhead in Vue Computed Properties]
**Learning:** Performing expensive string manipulations like `.toLowerCase()` on every item inside a Vue `computed` property loop (e.g., for search filtering) causes massive O(N) string allocations every time the user types a keystroke, resulting in GC spikes.
**Action:** Use Nuxt 3's `useFetch` `transform` option to pre-compute and attach lowercased search strings (`_searchName`, `_searchDesc`) to the fetched data objects once. The computed filter property can then just do a simple `.includes()` lookup, bypassing string allocations per keystroke.
17 changes: 13 additions & 4 deletions pages/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,18 @@ const { t } = useI18n()
const colorMode = useColorMode()

// Fetch apps data
const { data: apps, pending } = await useFetch('/api/apps')
// ⚡ Bolt Optimization: Use `transform` to pre-compute lowercased search strings once per fetch,
// avoiding O(N) string allocations and .toLowerCase() calls on every keystroke in the computed property.
const { data: apps, pending } = await useFetch('/api/apps', {
transform: (data) => {
if (!Array.isArray(data)) return data;
return data.map(app => ({
...app,
_searchName: (app.name || '').toLowerCase(),
_searchDesc: (app.description || '').toLowerCase()
}));
}
})

// Search and filter state
const searchQuery = ref('')
Expand Down Expand Up @@ -43,9 +54,7 @@ const filteredApps = computed(() => {

// Search check
if (query) {
const name = (app.name || '').toLowerCase()
const desc = (app.description || '').toLowerCase()
if (!name.includes(query) && !desc.includes(query)) return false
if (!app._searchName?.includes(query) && !app._searchDesc?.includes(query)) return false
}

return true
Expand Down