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
261 changes: 242 additions & 19 deletions src/lib/components/filters/parsedTagList.svelte
Original file line number Diff line number Diff line change
@@ -1,35 +1,254 @@
<script lang="ts">
import { Icon, Layout, Tag, Tooltip } from '@appwrite.io/pink-svelte';
import { queries, tagFormat, tags } from './store';
import {
Icon,
Layout,
Tooltip,
CompoundTagRoot,
CompoundTagChild,
Typography,
ActionMenu,
Selector
} from '@appwrite.io/pink-svelte';
import { capitalize } from '$lib/helpers/string';
import { queries, tags } from './store';
import { IconX } from '@appwrite.io/pink-icons-svelte';
import { parsedTags } from './setFilters';
import { Button } from '$lib/elements/forms';
import type { Column } from '$lib/helpers/types';
import type { Writable } from 'svelte/store';
import Menu from '$lib/components/menu/menu.svelte';
import { addFilterAndApply, buildFilterCol, type FilterData } from './quickFilters';
import QuickFilters from '$lib/components/filters/quickFilters.svelte';
let {
columns,
analyticsSource = ''
}: { columns: Writable<Column[]> | undefined; analyticsSource?: string } = $props();
Comment on lines +23 to +26
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cd /tmp/repo 2>/dev/null || cd /
# First, find the file
find . -name "parsedTagList.svelte" -type f 2>/dev/null | head -5

Repository: appwrite/console

Length of output: 127


🏁 Script executed:

# List the file to understand its size before reading
wc -l src/lib/components/filters/parsedTagList.svelte 2>/dev/null || echo "File not found, searching..."

Repository: appwrite/console

Length of output: 111


🏁 Script executed:

# Search for the file in the repository
find . -path "*filters*parsedTagList*" -type f 2>/dev/null

Repository: appwrite/console

Length of output: 109


🏁 Script executed:

cat -n src/lib/components/filters/parsedTagList.svelte

Repository: appwrite/console

Length of output: 15676


🏁 Script executed:

rg "parsedTagList" --type svelte -A 3 -B 3 | head -50

Repository: appwrite/console

Length of output: 90


🏁 Script executed:

# Check QuickFilters to see if it expects optional columns
head -100 src/lib/components/filters/quickFilters.svelte

Repository: appwrite/console

Length of output: 2578


🏁 Script executed:

# Check addFilterAndApply function
rg "export.*function addFilterAndApply" -A 15

Repository: appwrite/console

Length of output: 1353


🏁 Script executed:

# Find where parsedTagList component is imported and used
rg "parsedTagList" -t ts -t tsx -t js -t jsx

Repository: appwrite/console

Length of output: 87


🏁 Script executed:

# More direct search without file type
rg "import.*parsedTagList\|<parsedTagList\|<ParsedTagList"

Repository: appwrite/console

Length of output: 42


🏁 Script executed:

# Check the file that imports parsedTagList
rg "parsedTagList" --files-with-matches

Repository: appwrite/console

Length of output: 95


🏁 Script executed:

# Check the index file
cat src/lib/components/filters/index.ts

Repository: appwrite/console

Length of output: 535


🏁 Script executed:

# Search for imports of parsedTagList more broadly
rg "ParsedTagList|parsedTagList" -A 2 -B 2

Repository: appwrite/console

Length of output: 2488


🏁 Script executed:

# Search for where this component is actually used in the application
rg "ParsedTagList" --no-filename

Repository: appwrite/console

Length of output: 446


🏁 Script executed:

# Check the responsiveContainerHeader to see if columns is always defined
cat src/lib/layout/responsiveContainerHeader.svelte | head -100

Repository: appwrite/console

Length of output: 4048


🏁 Script executed:

# Get the full context around where ParsedTagList is used
rg "ParsedTagList" -B 20 -A 5 src/lib/layout/responsiveContainerHeader.svelte | head -80

Repository: appwrite/console

Length of output: 2443


🏁 Script executed:

# Check the exact lines where $columns is passed without guards in parsedTagList
sed -n '145,160p' src/lib/components/filters/parsedTagList.svelte

Repository: appwrite/console

Length of output: 1340


🏁 Script executed:

# Also check lines around 235
sed -n '225,240p' src/lib/components/filters/parsedTagList.svelte

Repository: appwrite/console

Length of output: 1145


🏁 Script executed:

# Check the responsiveContainerHeader to understand the flow with columns
sed -n '38,55p' src/lib/layout/responsiveContainerHeader.svelte

Repository: appwrite/console

Length of output: 759


🏁 Script executed:

cat src/lib/components/filters/quickFilters.ts | grep -A 20 "export function addFilterAndApply"

Repository: appwrite/console

Length of output: 841


Dereference of undefined store will cause runtime errors.

columns is typed as optional but the component attempts to dereference it with $columns in multiple places (lines 148, 158, 235) and passes it to QuickFilters (line 263), which expects a required Writable<Column[]>. If the parent (responsiveContainerHeader) doesn't always provide columns, the component will crash when trying to subscribe or pass the store to child components.

Provide a default empty writable store to make columns required:

🔧 Suggested fix
+    import { writable, type Writable } from 'svelte/store';
-    import type { Writable } from 'svelte/store';
     
     let {
-        columns,
+        columns = writable<Column[]>([]),
         analyticsSource = ''
-    }: { columns: Writable<Column[]> | undefined; analyticsSource?: string } = $props();
+    }: { columns?: Writable<Column[]>; analyticsSource?: string } = $props();
🤖 Prompt for AI Agents
In `@src/lib/components/filters/parsedTagList.svelte` around lines 23 - 26, The
component currently treats the optional prop "columns" as if it always exists
(uses $columns and passes columns to QuickFilters), which will crash if the
parent omits it; fix by giving "columns" a default writable store (e.g., import
{ writable } from 'svelte/store' and initialize columns to
writable<Column[]>([]) in the destructure) and update the prop typing to
non-optional Writable<Column[]> so all uses of $columns and the QuickFilters
prop receive a valid store even when the parent doesn't provide one.

function parseTagParts(tagString: string): { text: string; operator: boolean }[] {
return tagString
.split(/\*\*(.*?)\*\*/)
.map((part, index) => {
// Even indices are outside bold (operators), odd indices are inside bold (values)
if (index % 2 === 0) {
return part
.split(/\s+/)
.filter(Boolean)
.map((t) => ({ text: t, operator: true }));
} else {
return [{ text: part, operator: false }];
}
})
.flat()
.filter((p) => Boolean(p.text));
}
function firstBoldText(tagString: string): string | null {
const m = /\*\*(.*?)\*\*/.exec(tagString);
return m ? m[1] : null;
}
Comment on lines +46 to +49
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cat -n src/lib/components/filters/parsedTagList.svelte

Repository: appwrite/console

Length of output: 15676


Avoid deriving filter identity from tag string formatting.

The code uses two brittle approaches to extract column titles: firstBoldText() at lines 97 and 77 relies on bold markers (**...**), while line 186 uses tag.tag.split(' ')[0] to extract the first word. These assume a specific tag format; any change breaks the logic. Multi-word column titles also fail with word splitting—only the first word is extracted. Instead, carry a stable column id/title identifier directly in parsedTags items to avoid parsing the display string and risking menu misrouting or incorrect filter dismissals.

Applies to lines 46–49, 96–99, 185–188.

🤖 Prompt for AI Agents
In `@src/lib/components/filters/parsedTagList.svelte` around lines 46 - 49, The
code is deriving column identity from display formatting (firstBoldText and
tag.tag.split(' ')), which is brittle; instead add a stable identifier (e.g.,
parsedTag.id or parsedTag.title) to each parsedTags item and wire all logic to
use that property. Update the parsedTags construction (where parsedTags is
built) to include a dedicated id/title field, replace all calls to
firstBoldText(...) and tag.tag.split(' ')[0] with references to parsedTag.id or
parsedTag.title, and change dismissal/routing logic (the functions that read the
bold text or first word) to operate on that stable property; remove or deprecate
firstBoldText once callers are migrated. Ensure multi-word titles are preserved
by using the new field rather than splitting the display string.

function getFilterFor(title: string): FilterData | null {
if (!columns) return null;
const col = ($columns as unknown as Column[]).find((c) => c.title === title);
if (!col) return null;
const filter = buildFilterCol(col);
return filter ?? null;
}
// Build available filter definitions from provided columns
let availableFilters = $derived(
($columns as unknown as Column[] | undefined)?.length
? (($columns as unknown as Column[])
.map((c) => (c.filter !== false ? buildFilterCol(c) : null))
.filter((f) => f && f.options) as FilterData[])
: []
);
// QuickFilters uses the same filters list
let filterCols = $derived(availableFilters);
// Always-show placeholders are derived from available filters (no hardcoding)
// Use reactive array so runes can track changes
let hiddenPlaceholders: string[] = $state([]);
let placeholderVersion = $state(0); // used to force keyed re-render when needed
let activeTitles = $derived(
($parsedTags || []).map((t) => firstBoldText(t.tag)).filter(Boolean) as string[]
);
// Compute current placeholders (major filters not already active or dismissed)
let placeholders = $derived(
availableFilters
.filter((f) => !activeTitles.includes(f.title))
.filter((f) => !hiddenPlaceholders.includes(f.title))
);
</script>

{#if $parsedTags?.length}
<Layout.Stack direction="row" gap="s" wrap="wrap" alignItems="center" inline>
<Layout.Stack direction="row" gap="s" wrap="wrap" alignItems="center" inline>
{#if $parsedTags?.length}
{#each $parsedTags as tag (tag.tag)}
<span>
<Tooltip
disabled={Array.isArray(tag.value) ? tag.value?.length < 3 : true}
maxWidth="600px">
<Tag
size="s"
on:click={() => {
const t = $tags.filter((t) => t.tag.includes(tag.tag.split(' ')[0]));
t.forEach((t) => (t ? queries.removeFilter(t) : null));
queries.apply();
parsedTags.update((tags) => tags.filter((t) => t.tag !== tag.tag));
}}>
{#key tag.tag}
<span use:tagFormat>{tag.tag}</span>
{/key}
<Icon icon={IconX} size="s" slot="end" />
</Tag>
<CompoundTagRoot size="s">
{@const parts = parseTagParts(tag.tag)}
{@const property = firstBoldText(tag.tag)}

{#each parts as part}
<CompoundTagChild>
<Menu>
<span>
{#if part.operator}
<Typography.Text color="--fgcolor-neutral-secondary"
>{part.text}</Typography.Text>
{:else}
<Typography.Text
variant="m-500"
color="--fgcolor-neutral-secondary"
>{part.text
.split(' or ')
.map((t) => capitalize(t))
.join(' or ')}</Typography.Text>
{/if}
</span>
<svelte:fragment slot="menu">
{#if property}
{@const filter = getFilterFor(property)}
{#if filter}
{@const isArray = filter?.array}
{@const selectedArray = Array.isArray(tag.value)
? tag.value
: []}
{#each filter.options as option (filter.title + option.value + option.label)}
<ActionMenu.Root>
<ActionMenu.Item.Button
on:click={() => {
if (isArray) {
const exists =
selectedArray.includes(
option.value
);
const next = exists
? selectedArray.filter(
(v) =>
v !== option.value
)
: [
...selectedArray,
option.value
];
addFilterAndApply(
filter.id,
filter.title,
filter.operator,
null,
next,
$columns,
''
);
} else {
addFilterAndApply(
filter.id,
filter.title,
filter.operator,
option.value,
[],
$columns,
''
);
Comment on lines +142 to +160
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

analyticsSource prop not passed to addFilterAndApply.

The analyticsSource prop is received but hardcoded empty strings are passed to addFilterAndApply calls. This will break analytics tracking for filter interactions.

Proposed fix
                                                     addFilterAndApply(
                                                         filter.id,
                                                         filter.title,
                                                         filter.operator,
                                                         null,
                                                         next,
                                                         $columns,
-                                                        ''
+                                                        analyticsSource
                                                     );
                                                 } else {
                                                     addFilterAndApply(
                                                         filter.id,
                                                         filter.title,
                                                         filter.operator,
                                                         option.value,
                                                         [],
                                                         $columns,
-                                                        ''
+                                                        analyticsSource
                                                     );
                                                 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
addFilterAndApply(
filter.id,
filter.title,
filter.operator,
null,
next,
$columns,
''
);
} else {
addFilterAndApply(
filter.id,
filter.title,
filter.operator,
option.value,
[],
$columns,
''
);
addFilterAndApply(
filter.id,
filter.title,
filter.operator,
null,
next,
$columns,
analyticsSource
);
} else {
addFilterAndApply(
filter.id,
filter.title,
filter.operator,
option.value,
[],
$columns,
analyticsSource
);
🤖 Prompt for AI Agents
In @src/lib/components/filters/parsedTagList.svelte around lines 172 - 190, The
calls to addFilterAndApply in parsedTagList.svelte are passing empty strings for
the analyticsSource argument, which drops the component's analyticsSource prop;
update both call sites (the branch that passes null for value/next and the
branch that passes option.value/[]) to forward the component's analyticsSource
prop instead of '' so analyticsSource is sent through addFilterAndApply.

}
Comment on lines +142 to +161
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

analyticsSource prop not used in tag menu handlers.

The addFilterAndApply calls at lines 179, 189, and 266 pass an empty string '' for analyticsSource, while the component receives analyticsSource as a prop. This means filter changes made via tag menus won't be tracked, unlike those from QuickFilters (line 282) which correctly uses the prop.

🔧 Suggested fix
                                                 addFilterAndApply(
                                                     filter.id,
                                                     filter.title,
                                                     filter.operator,
                                                     null,
                                                     next,
                                                     $columns,
-                                                    ''
+                                                    analyticsSource
                                                 );

Apply the same change to lines 189 and 266.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
addFilterAndApply(
filter.id,
filter.title,
filter.operator,
null,
next,
$columns,
''
);
} else {
addFilterAndApply(
filter.id,
filter.title,
filter.operator,
option.value,
[],
$columns,
''
);
}
addFilterAndApply(
filter.id,
filter.title,
filter.operator,
null,
next,
$columns,
analyticsSource
);
} else {
addFilterAndApply(
filter.id,
filter.title,
filter.operator,
option.value,
[],
$columns,
analyticsSource
);
}
🤖 Prompt for AI Agents
In @src/lib/components/filters/parsedTagList.svelte around lines 172 - 191, The
tag menu handlers call addFilterAndApply with a hardcoded empty string for
analyticsSource instead of using the component's analyticsSource prop; update
all addFilterAndApply invocations in the tag/menu-related handlers (the calls
currently passing '' — the ones near the other tag option branches and the later
tag removal branch) to pass the analyticsSource prop value so they match the
QuickFilters usage that already passes analyticsSource to addFilterAndApply.

}}>
<Layout.Stack direction="row" gap="s">
{#if isArray}
<Selector.Checkbox
checked={selectedArray.includes(
option.value
)}
size="s" />
{/if}
{capitalize(option.label)}
</Layout.Stack>
</ActionMenu.Item.Button>
</ActionMenu.Root>
{/each}
{/if}
{/if}
</svelte:fragment>
</Menu>
</CompoundTagChild>
{/each}
<CompoundTagChild
dismiss
on:click={() => {
const t = $tags.filter((t) =>
t.tag.includes(tag.tag.split(' ')[0])
);
t.forEach((t) => (t ? queries.removeFilter(t) : null));
queries.apply();
parsedTags.update((tags) => tags.filter((t) => t.tag !== tag.tag));
}}>
<Icon icon={IconX} size="s" />
</CompoundTagChild>
</CompoundTagRoot>
<span slot="tooltip">{tag?.value?.toString()}</span>
</Tooltip>
</span>
{/each}
{/if}

<!-- Always render remaining placeholder tags alongside active tags -->
{#key placeholderVersion}
{#if placeholders?.length}
{#each placeholders as filter (filter.title + filter.id)}
<span>
<Menu>
<CompoundTagRoot size="s">
<CompoundTagChild>
<span>{capitalize(filter.title)}</span>
</CompoundTagChild>
<CompoundTagChild
dismiss
on:click={(e) => {
e.stopPropagation();
if (!hiddenPlaceholders.includes(filter.title)) {
hiddenPlaceholders.push(filter.title);
}
placeholderVersion++;
}}>
Comment on lines +213 to +219
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Direct array mutation may cause reactivity issues.

In Svelte 5, while array mutations can trigger reactivity in some cases, the pattern of directly mutating hiddenPlaceholders with .push() combined with a manual placeholderVersion++ increment suggests instability. Use immutable updates for reliable reactivity:

Proposed fix
                             on:click={(e) => {
                                 e.stopPropagation();
-                                if (!hiddenPlaceholders.includes(filter.title)) {
-                                    hiddenPlaceholders.push(filter.title);
-                                }
-                                placeholderVersion++;
+                                if (!hiddenPlaceholders.includes(filter.title)) {
+                                    hiddenPlaceholders = [...hiddenPlaceholders, filter.title];
+                                }
                             }}>

If reactivity works correctly with immutable updates, you can also remove the placeholderVersion state and the {#key placeholderVersion} wrapper (lines 110 and 232).

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
on:click={(e) => {
e.stopPropagation();
if (!hiddenPlaceholders.includes(filter.title)) {
hiddenPlaceholders.push(filter.title);
}
placeholderVersion++;
}}>
on:click={(e) => {
e.stopPropagation();
if (!hiddenPlaceholders.includes(filter.title)) {
hiddenPlaceholders = [...hiddenPlaceholders, filter.title];
}
}}>
🤖 Prompt for AI Agents
In @src/lib/components/filters/parsedTagList.svelte around lines 243 - 249, The
click handler mutates hiddenPlaceholders with .push() and manually increments
placeholderVersion which can break Svelte reactivity; replace the mutation with
an immutable assignment like setting hiddenPlaceholders =
[...hiddenPlaceholders, filter.title] (only if filter.title not already
included) and remove the placeholderVersion++; after confirming immutable
updates work you can also remove placeholderVersion state and the {#key
placeholderVersion} wrapper (ensure you update the on:click handler references
and any code that reads hiddenPlaceholders accordingly).

<Icon icon={IconX} size="s" />
</CompoundTagChild>
</CompoundTagRoot>
<svelte:fragment slot="menu">
{#if filter.options}
{#each filter.options as option (filter.title + option.value + option.label)}
<ActionMenu.Root>
<ActionMenu.Item.Button
on:click={() => {
addFilterAndApply(
filter.id,
filter.title,
filter.operator,
filter?.array ? null : option.value,
filter?.array ? [option.value] : [],
$columns,
''
);
Comment on lines +229 to +237
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Same issue: analyticsSource not passed in placeholder filter actions.

Apply the same fix here for consistency:

Proposed fix
                                             addFilterAndApply(
                                                 filter.id,
                                                 filter.title,
                                                 filter.operator,
                                                 filter?.array ? null : option.value,
                                                 filter?.array ? [option.value] : [],
                                                 $columns,
-                                                ''
+                                                analyticsSource
                                             );
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
addFilterAndApply(
filter.id,
filter.title,
filter.operator,
filter?.array ? null : option.value,
filter?.array ? [option.value] : [],
$columns,
''
);
addFilterAndApply(
filter.id,
filter.title,
filter.operator,
filter?.array ? null : option.value,
filter?.array ? [option.value] : [],
$columns,
analyticsSource
);
🤖 Prompt for AI Agents
In @src/lib/components/filters/parsedTagList.svelte around lines 259 - 267, The
placeholder filter action call to addFilterAndApply is missing the
analyticsSource argument; update the call in parsedTagList.svelte so
addFilterAndApply(filter.id, filter.title, filter.operator, filter?.array ? null
: option.value, filter?.array ? [option.value] : [], $columns, '',
analyticsSource) includes the analyticsSource parameter (or the appropriate
variable name used elsewhere) to match other calls to addFilterAndApply.

}}>
{capitalize(option.label)}
</ActionMenu.Item.Button>
</ActionMenu.Root>
{/each}
{/if}
</svelte:fragment>
</Menu>
</span>
{/each}
{/if}
{/key}

{#if $parsedTags?.length}
<Button
size="s"
text
Expand All @@ -38,5 +257,9 @@
queries.apply();
parsedTags.set([]);
}}>Clear all</Button>
</Layout.Stack>
{/if}
{/if}

{#if filterCols?.length}
<QuickFilters {columns} {analyticsSource} {filterCols} />
{/if}
</Layout.Stack>
9 changes: 6 additions & 3 deletions src/lib/components/filters/quickFilters.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,12 @@
</script>

<Menu>
<Button secondary badge={$parsedTags?.length ? `${$parsedTags.length}` : undefined}>
<Icon icon={IconFilterLine} slot="start" size="s" />
Filters
<Button
ariaLabel="Filters"
text
icon
badge={$parsedTags?.length ? `${$parsedTags.length}` : undefined}>
<Icon icon={IconFilterLine} size="s" />
</Button>
<svelte:fragment slot="menu">
{#each filterCols.filter((f) => f?.options) as filter (filter.title + filter.id)}
Expand Down
34 changes: 25 additions & 9 deletions src/lib/layout/responsiveContainerHeader.svelte
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<script lang="ts">
import { SearchQuery, ViewSelector } from '$lib/components';
import { FiltersBottomSheet, ParsedTagList, queryParamToMap } from '$lib/components/filters';
import QuickFilters from '$lib/components/filters/quickFilters.svelte';

import Button from '$lib/elements/forms/button.svelte';
import { View } from '$lib/helpers/load';
import type { Column } from '$lib/helpers/types';
Expand Down Expand Up @@ -99,18 +99,35 @@
{#if showSearch && hasSearch}
<SearchQuery placeholder={searchPlaceholder} />
{/if}
<div style="overflow-x: auto;">
<ParsedTagList {columns} {analyticsSource} />
</div>
</Layout.Stack>
{:else}
<Layout.Stack direction="row" justifyContent="space-between">
<Layout.Stack direction="row" alignItems="center">
<Layout.Stack direction="row" justifyContent="space-between" alignItems="flex-start">
<Layout.Stack
direction="row"
alignItems="center"
gap="m"
style={`min-width: 0; flex: 1 1 auto;`}>
{#if hasSearch}
<SearchQuery placeholder={searchPlaceholder} />
{/if}
<!-- Tags with Filters button (rendered inside ParsedTagList) -->
<Layout.Stack
direction="row"
alignItems="center"
gap="s"
wrap="wrap"
style={`min-width: 0;`}>
<ParsedTagList {columns} {analyticsSource} />
</Layout.Stack>
Comment on lines +116 to +124
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Missing hasFilters conditional check for ParsedTagList.

In the small viewport case, filter rendering is gated by hasFilters && $columns?.length, but here ParsedTagList is rendered unconditionally. This could display filter UI when hasFilters is false.

Proposed fix
-                   <!-- Tags with Filters button (rendered inside ParsedTagList) -->
-                   <Layout.Stack
-                       direction="row"
-                       alignItems="center"
-                       gap="s"
-                       wrap="wrap"
-                       style={`min-width: 0;`}>
-                       <ParsedTagList {columns} {analyticsSource} />
-                   </Layout.Stack>
+                   {#if hasFilters && $columns?.length}
+                       <!-- Tags with Filters button (rendered inside ParsedTagList) -->
+                       <Layout.Stack
+                           direction="row"
+                           alignItems="center"
+                           gap="s"
+                           wrap="wrap"
+                           style={`min-width: 0;`}>
+                           <ParsedTagList {columns} {analyticsSource} />
+                       </Layout.Stack>
+                   {/if}
🤖 Prompt for AI Agents
In @src/lib/layout/responsiveContainerHeader.svelte around lines 113 - 121,
ParsedTagList is being rendered unconditionally inside the Layout.Stack for the
small-viewport branch which can show filter UI even when hasFilters is false;
update the rendering to conditionally render ParsedTagList only when hasFilters
&& $columns?.length (same condition used elsewhere) so wrap the
Layout.Stack/ParsedTagList block with that conditional check to match the
intended behavior.

</Layout.Stack>
<Layout.Stack direction="row" alignItems="center" justifyContent="flex-end">
{#if hasFilters && $columns?.length}
<QuickFilters {columns} {analyticsSource} {filterCols} />
{/if}
<Layout.Stack
direction="row"
alignItems="center"
justifyContent="flex-end"
style={`align-self: flex-start; white-space: nowrap;`}>
{#if hasDisplaySettings}
<ViewSelector ui="new" {view} {columns} {hideView} {hideColumns} />
{/if}
Expand All @@ -120,7 +137,6 @@
</Layout.Stack>
</Layout.Stack>
{/if}
<ParsedTagList />
</Layout.Stack>
</header>

Expand Down Expand Up @@ -160,7 +176,7 @@
{/snippet}

{#snippet filtersButton(icon = false)}
<Button ariaLabel="Filters" on:click={() => (showFilters = !showFilters)} secondary {icon}>
<Button ariaLabel="Filters" on:click={() => (showFilters = !showFilters)} text {icon}>
<Icon icon={IconFilterLine} />
</Button>
{/snippet}
Loading