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
2 changes: 2 additions & 0 deletions src/lib/components/imagePreview.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
style="inline-size: 100%; block-size: 100%;"
target="_blank"
rel="noopener noreferrer"
on:click|stopPropagation
on:mousedown|stopPropagation
aria-label="open file in new window">
<div class="file-preview-image">
<img src={$app.themeInUse === 'dark' ? darkSrc : lightSrc} {alt} />
Expand Down
2 changes: 1 addition & 1 deletion src/lib/elements/forms/helper.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
</script>

<p
class="helper u-margin-block-start-8 {classes}"
class="helper u-margin-block-start-6 {classes}"
class:u-color-text-info={type === 'info'}
class:u-color-text-danger={type === 'error'}
class:u-color-text-success={type === 'success'}
Expand Down
15 changes: 13 additions & 2 deletions src/lib/layout/responsiveContainerHeader.svelte
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
<script lang="ts">
import { SearchQuery, ViewSelector } from '$lib/components';
import { FiltersBottomSheet, ParsedTagList, queryParamToMap } from '$lib/components/filters';
import {
FiltersBottomSheet,
ParsedTagList,
queryParamToMap,
Filters
} 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';
Expand All @@ -23,6 +28,7 @@
hasSearch = false,
searchPlaceholder = 'Search by ID',
hasFilters = false,
hasCustomFiltersOnly = false,
analyticsSource = '',
children
}: {
Expand All @@ -33,6 +39,7 @@
hasSearch?: boolean;
searchPlaceholder?: string;
hasFilters?: boolean;
hasCustomFiltersOnly?: boolean;
analyticsSource?: string;
children?: Snippet;
} = $props();
Expand Down Expand Up @@ -109,7 +116,11 @@
</Layout.Stack>
<Layout.Stack direction="row" alignItems="center" justifyContent="flex-end">
{#if hasFilters && $columns?.length}
<QuickFilters {columns} {analyticsSource} {filterCols} />
{#if hasCustomFiltersOnly}
<Filters query="[]" {columns} {analyticsSource} />
{:else}
<QuickFilters {columns} {analyticsSource} {filterCols} />
{/if}
Comment on lines +119 to +123
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 | 🔴 Critical

🧩 Analysis chain

Verify the hardcoded query="[]" value.

The Filters component is being passed a hardcoded query="[]", which means it won't reflect any existing query state from URL parameters. In contrast, in file src/routes/(console)/project-[region]-[project]/messaging/topics/topic-[topic]/+page.svelte (line 93), the Filters component receives query={data.query} from server data.

This could lead to a UX issue where active filters from URL params aren't displayed in the UI when using hasCustomFiltersOnly mode.

Consider whether the query should be:

  1. Passed as a prop to ResponsiveContainerHeader (similar to other props like columns, analyticsSource)
  2. Derived from URL search params within the component
  3. Intentionally hardcoded to "[]" (please confirm if this is the expected behavior)

Run the following script to check how Filters component is used elsewhere in the codebase:


🏁 Script executed:

#!/bin/bash
# Search for other usages of the Filters component to understand the expected query prop pattern
rg -n -A2 -B2 '<Filters\s+query=' --type=svelte

Length of output: 30


🏁 Script executed:

#!/bin/bash
# Search for Filters component usages across all files
# Try without the type restriction
rg -n -A2 -B2 '<Filters\s+query=' 

Length of output: 3402


🏁 Script executed:

#!/bin/bash
# Also search for all Filters component usages to understand the pattern
rg -n '<Filters' --glob='*.svelte' | head -50

Length of output: 1116


🏁 Script executed:

#!/bin/bash
# Check the specific file for context around the hardcoded query
cat src/lib/layout/responsiveContainerHeader.svelte | sed -n '110,130p'

Length of output: 1045


🏁 Script executed:

#!/bin/bash
# Check the referenced comparison file
cat src/routes/*/project-*/messaging/topics/topic-*/+page.svelte 2>/dev/null || echo "File pattern not found, searching..."
fd -e svelte 'topic' --exec grep -l 'Filters.*query=' {} \;

Length of output: 5835


🏁 Script executed:

#!/bin/bash
# Check the component definition and props
head -50 src/lib/layout/responsiveContainerHeader.svelte

Length of output: 1915


🏁 Script executed:

#!/bin/bash
# Search for how responsiveContainerHeader is used throughout the codebase
rg -n 'responsiveContainerHeader' --glob='*.svelte' -B2 -A5

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Search for how the component is imported/exported
rg -n 'responsiveContainerHeader' -C3

Length of output: 419


🏁 Script executed:

#!/bin/bash
# Check the lib layout index to see what's exported
cat src/lib/layout/index.ts 2>/dev/null || cat src/lib/layout/index.js 2>/dev/null || echo "No index found"

Length of output: 1653


🏁 Script executed:

#!/bin/bash
# Look at what's in the layout directory
ls -la src/lib/layout/

Length of output: 2251


🏁 Script executed:

#!/bin/bash
# Search for where ResponsiveContainerHeader might be used
rg -in 'ResponsiveContainerHeader|responsive.*container.*header' --glob='*.svelte'

Length of output: 3933


🏁 Script executed:

#!/bin/bash
# Check if hasCustomFiltersOnly is used anywhere
rg -n 'hasCustomFiltersOnly' --glob='*.svelte' -B2 -A2

Length of output: 2505


🏁 Script executed:

#!/bin/bash
# Check a few actual ResponsiveContainerHeader usages to see their context
sed -n '15,40p' src/routes/\(console\)/project-\[region\]-\[project\]/messaging/topics/\+page.svelte

Length of output: 931


🏁 Script executed:

#!/bin/bash
# Check the messaging/providers page for data.query availability
sed -n '1,50p' 'src/routes/(console)/project-[region]-[project]/messaging/providers/+page.svelte'

Length of output: 1841


🏁 Script executed:

#!/bin/bash
# Check the messaging/topics page for data.query availability
sed -n '1,100p' 'src/routes/(console)/project-[region]-[project]/messaging/topics/+page.svelte'

Length of output: 3357


🏁 Script executed:

#!/bin/bash
# Check if data.query is available in messaging topics page - look for +page.server.ts
cat 'src/routes/(console)/project-[region]-[project]/messaging/topics/+page.server.ts' 2>/dev/null | head -80

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Check the page.ts or +page.ts for these routes
cat 'src/routes/(console)/project-[region]-[project]/messaging/topics/+page.ts' 2>/dev/null | head -80

Length of output: 1184


🏁 Script executed:

#!/bin/bash
# Check the PageData type definition
fd -e 'ts' '\$types' 'src/routes/(console)/project-[region]-[project]/messaging/topics/' --exec cat {} \;

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Confirm data.query is returned in the page load for messaging/topics
grep -n 'query' 'src/routes/(console)/project-[region]-[project]/messaging/topics/+page.ts'

Length of output: 184


🏁 Script executed:

#!/bin/bash
# Check if messaging/providers also has data.query
cat 'src/routes/(console)/project-[region]-[project]/messaging/providers/+page.ts' 2>/dev/null | grep -A5 -B5 'query'

Length of output: 962


Add query prop to ResponsiveContainerHeader and pass it to the Filters component.

The hardcoded query="[]" on line 120 prevents custom filters from displaying saved filter state. Both pages using hasCustomFiltersOnly=true (messaging/topics and messaging/providers) have data.query available from their page loaders, but cannot pass it to the component since the query prop doesn't exist.

This breaks the established pattern: all other direct Filters usages in the codebase receive query={data.query}.

Required changes:

  1. Add query?: string to the component's prop definition
  2. Update line 120 to pass {query} instead of query="[]"
  3. Update both pages to pass query={data.query} when setting hasCustomFiltersOnly
🤖 Prompt for AI Agents
In src/lib/layout/responsiveContainerHeader.svelte around lines 119-123, the
Filters invocation uses a hardcoded query="[]" which prevents restoring saved
filter state; add a new optional prop to the component (query?: string) in the
component's prop definition, change the Filters call on line 120 to pass {query}
instead of query="[]", and update the two pages that set hasCustomFiltersOnly
(messaging/topics and messaging/providers) to pass their loader value as
query={data.query} when rendering this component.

{/if}
{#if hasDisplaySettings}
<ViewSelector ui="new" {view} {columns} {hideView} {hideColumns} />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import { page } from '$app/state';
</script>

<Popover let:toggle padding="none" placement="bottom-start">
<Popover let:toggle padding="none" placement="bottom-end">
<slot {toggle}>
<Button on:click={toggle} event="create_message">
<Icon icon={IconPlus} slot="start" size="s" />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<script lang="ts">
import { MessagingProviderType, type Models } from '@appwrite.io/console';
import { CardGrid, Empty, PaginationInline, EmptySearch } from '$lib/components';
import { CardGrid, Empty, PaginationInline } from '$lib/components';
import { onMount } from 'svelte';
import { sdk } from '$lib/stores/sdk';
import { invalidate } from '$app/navigation';
Expand All @@ -12,8 +12,17 @@
import UserTargetsModal from '../userTargetsModal.svelte';
import { isValueOfStringEnum } from '$lib/helpers/types';
import { IconPlus } from '@appwrite.io/pink-icons-svelte';
import { Alert, Icon, Layout, Table, Typography } from '@appwrite.io/pink-svelte';
import {
Alert,
Card,
Empty as PinkEmpty,
Icon,
Layout,
Table,
Typography
} from '@appwrite.io/pink-svelte';
import { page } from '$app/state';
import { Link } from '$lib/elements';

export let message: Models.Message & { data: Record<string, unknown> };
export let selectedTargetsById: Record<string, Models.Target>;
Expand Down Expand Up @@ -207,17 +216,17 @@
{:else if isDraft}
<Empty on:click={() => (showTargets = true)}>Add a target</Empty>
{:else if !sum && !hasDeletedUsers}
<EmptySearch hidePagination>
<div class="u-text-center">
No targets have been selected.
<p>
Need a hand? Check out our <Button
href="https://appwrite.io/docs/products/messaging/targets">
documentation</Button
<Card.Base padding="none">
<PinkEmpty type="secondary" title="No targets were selected">
<svelte:fragment slot="description">
Need a hand? Check out our <Link
variant="muted"
href="https://appwrite.io/docs/products/messaging/targets"
external>documentation</Link
>.
</p>
</div>
</EmptySearch>
</svelte:fragment>
</PinkEmpty>
</Card.Base>
{/if}
</svelte:fragment>
<svelte:fragment slot="actions">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
view={View.Table}
hideView
hasFilters
hasCustomFiltersOnly
hasSearch
analyticsSource="messaging_providers"
searchPlaceholder="Search by name or ID">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@
}
</script>

<Wizard title="Create provider" columnSize="l">
<Wizard title="Create provider" columnSize="l" confirmExit>
<Form bind:this={formRef} onSubmit={create} isModal={false}>
<Layout.Stack gap="xxl">
<Fieldset legend="Provider">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import { ActionMenu, Popover } from '@appwrite.io/pink-svelte';
</script>

<Popover let:toggle padding="none" placement="bottom-start">
<Popover let:toggle padding="none" placement="bottom-end">
<slot {toggle} />
<ActionMenu.Root slot="tooltip">
{#each Object.entries(providers) as [type, option]}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@
view={View.Table}
hideView
hasFilters
hasCustomFiltersOnly
hasSearch
analyticsSource="messaging_topics_filter"
searchPlaceholder="Search by name or ID">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@
await Promise.all(promises);
addNotification({
type: 'success',
message: `${targetIds.length} subscriber${targetIds.length !== 1 ? 's' : ''} have been added`
message: `${targetIds.length} subscriber${targetIds.length !== 1 ? 's' : ''} created`
});
trackEvent(Submit.MessagingTopicSubscriberAdd);
await invalidate(Dependencies.MESSAGING_TOPIC_SUBSCRIBERS);
Expand All @@ -88,7 +88,7 @@

<Container>
<Layout.Stack direction="row" justifyContent="space-between">
<SearchQuery placeholder="Search by type or IDs"></SearchQuery>
<SearchQuery placeholder="Search by type or ID"></SearchQuery>
<Layout.Stack direction="row" inline>
<Filters query={data.query} {columns} analyticsSource="messaging_topics" />
<ViewSelector ui="new" view={View.Table} {columns} hideView />
Expand All @@ -99,7 +99,7 @@
}}
event="create_subscriber">
<Icon icon={IconPlus} slot="start" size="s" />
Add subscriber
Create subscriber
</Button>
</Layout.Stack>
</Layout.Stack>
Expand All @@ -115,14 +115,11 @@
{:else if $hasPageQueries}
<EmptyFilter resource="subscribers" />
{:else if data.search}
<EmptySearch>
<div class="u-text-center">
<b>Sorry, we couldn't find '{data.search}'</b>
<p>There are no subscribers that match your search.</p>
</div>
<EmptySearch bind:search={data.search} target="subscribers">
<Button
secondary
href={`${base}/project-${page.params.region}-${page.params.project}/messaging/topics/topic-${page.params.topic}/subscribers`}>
Clear Search
</Button>
</EmptySearch>
{:else}
Expand All @@ -141,7 +138,7 @@
on:update={addTargets}>
<svelte:fragment slot="description">
<Typography.Text>
Add subscribers to this topic by selecting the targets for directing messages.{' '}
Create subscribers for this topic by selecting the targets for directing messages.{' '}
<Link
href="https://appwrite.io/docs/products/messaging/topics#subscribe-targets-to-topics"
external>
Expand Down
Copy link
Member

Choose a reason for hiding this comment

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

why was this moved from svelte5 to 4?

Original file line number Diff line number Diff line change
Expand Up @@ -16,29 +16,19 @@
import { getProviderText } from './helper';
import { page } from '$app/state';
let {
providerType,
show = $bindable(),
topicsById = $bindable(),
title = 'Select topics'
} = $props<{
providerType: MessagingProviderType;
show: boolean;
topicsById: Record<string, Models.Topic>;
title?: string;
}>();
export let providerType: MessagingProviderType;
export let show: boolean;
export let topicsById: Record<string, Models.Topic>;
export let title: string = 'Select topics';
const dispatch = createEventDispatcher();
let search = $state('');
let offset = $state(0);
let totalResults = $state(0);
let topicResultsById = $state<Record<string, Models.Topic>>({});
let selected = $state<Record<string, Models.Topic>>({});
let emptyTopicsExists = $state(false);
let previousSearch = $state('');
let wasOpen = $state(false);
let search = '';
let offset = 0;
let totalResults = 0;
let topicResultsById: Record<string, Models.Topic> = {};
let selected: Record<string, Models.Topic> = {};
let emptyTopicsExists = false;
function getTopicTotal(topic: Models.Topic): number {
switch (providerType) {
Expand Down Expand Up @@ -96,44 +86,38 @@
}
function onTopicSelection(event: CustomEvent<boolean>, topic: Models.Topic) {
const updatedSelected = { ...selected };
if (event.detail) {
selected = {
...selected,
[topic.$id]: topic
};
updatedSelected[topic.$id] = topic;
} else {
const { [topic.$id]: _, ...rest } = selected;
selected = rest;
delete updatedSelected[topic.$id];
}
}
$effect(() => {
if (search !== previousSearch) {
previousSearch = search;
offset = 0;
}
});
selected = updatedSelected;
}
$effect(() => {
if (!show) return;
offset ?? null;
search ?? null;
$: if (show) {
request();
});
$effect(() => {
if (show && !wasOpen) {
selected = topicsById;
}
wasOpen = show;
});
}
$: if (offset !== null) {
request();
}
$: if (search !== null) {
offset = 0;
request();
}
Comment on lines +100 to +109
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

Fix reactive blocks to prevent duplicate API calls.

The current reactive blocks have several issues:

  1. Lines 103-105 and 106-109: The conditions if (offset !== null) and if (search !== null) are always true since offset is a number (initialized to 0) and search is a string (initialized to ''), causing these blocks to run on every component mount and on every change.

  2. Cascading triggers: When search changes, line 107 sets offset = 0, which then triggers lines 103-105 again, causing request() to be called twice.

  3. Initial mount: All three reactive blocks will execute on component mount, potentially triggering multiple simultaneous API calls.

This leads to wasted API calls, potential race conditions, and unpredictable results.

Consider refactoring to a single reactive block with proper dependency tracking:

-    $: if (show) {
-        request();
-    }
-    $: if (offset !== null) {
-        request();
-    }
-    $: if (search !== null) {
-        offset = 0;
-        request();
-    }
+    $: if (show) {
+        offset = 0;
+        search = '';
+    }
+    
+    $: if (show || offset >= 0 || search !== undefined) {
+        request();
+    }

Alternatively, use separate reactive blocks with proper guards:

-    $: if (show) {
-        request();
-    }
-    $: if (offset !== null) {
-        request();
-    }
-    $: if (search !== null) {
+    let prevSearch = search;
+    $: if (search !== prevSearch) {
+        prevSearch = search;
         offset = 0;
-        request();
     }
+    
+    $: show, offset, search, request();
📝 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
$: if (show) {
request();
});
$effect(() => {
if (show && !wasOpen) {
selected = topicsById;
}
wasOpen = show;
});
}
$: if (offset !== null) {
request();
}
$: if (search !== null) {
offset = 0;
request();
}
$: if (show) {
offset = 0;
search = '';
}
$: if (show || offset >= 0 || search !== undefined) {
request();
}
🤖 Prompt for AI Agents
In src/routes/(console)/project-[region]-[project]/messaging/topicsModal.svelte
around lines 100-109, the three reactive blocks cause duplicate API calls
because offset and search are never null, search resets offset which re-triggers
the offset block, and all blocks run on mount; replace these with a single
reactive block that depends on show, offset, and search (or use separate blocks
with proper guards) so request() is only called once per meaningful
change—specifically, consolidate logic to check show before calling request,
debounce or guard search changes by setting offset and then calling request in
the same block (avoid setting offset in one reactive and relying on another to
call request), and ensure initial mount triggers a single request by using a
single $: block that early-returns when show is false.

let selectedSize = $derived(Object.keys(selected).length);
let hasSelection = $derived(selectedSize > 0);
$: selectedSize = Object.keys(selected).length;
$: hasSelection = selectedSize > 0;
let topicSelectionStates = $derived(
Object.fromEntries(Object.keys(topicResultsById).map((id) => [id, !!selected[id]]))
$: topicSelectionStates = Object.fromEntries(
Object.keys(topicResultsById).map((id) => [id, !!selected[id]])
);
$: if (show) {
selected = topicsById;
}
</script>

<Modal {title} bind:show onSubmit={submit} on:close={reset}>
Expand Down Expand Up @@ -185,7 +169,7 @@
<Empty
type="secondary"
title={`You have no topics${
emptyTopicsExists ? ` with ${providerType.toUpperCase()} targets` : ''
emptyTopicsExists ? ` with ${providerType.toLowerCase()} targets` : ''
}`}
description={`Create a topic to see them here.`}>
<Button
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@
</script>

<Modal {title} bind:show onSubmit={submit} on:close={reset}>
<slot name="description" />
<slot name="description" slot="description" />
<Layout.Stack>
<InputSearch
autofocus
Expand Down