feat: implement realtime logging interface with subscription support#3080
feat: implement realtime logging interface with subscription support#3080ArnabChatterjee20k wants to merge 1 commit into
Conversation
Greptile SummaryThis PR adds a new Realtime section to the project sidebar that renders a live tail log of Appwrite events over a WebSocket subscription. It extends the SDK's
Confidence Score: 3/5The core subscription logic is structurally sound, but the Resource column will silently display wrong data for collection-scoped events due to a missing field in the display helper. The frameScopeId function omits collectionId from the lookup chain despite it being declared in TailFrame, so any database-collection event without a databaseId will show no resource identifier at all. The component also mixes Svelte 4 template syntax with Svelte 5 runes, uses index-based keys that negate DOM diffing under high event throughput, and silently swallows WebSocket connection errors. store.ts (missing collectionId in frameScopeId) and +page.svelte (mixed Svelte 4/5 syntax, index key, missing error handling) need the most attention before merging. Important Files Changed
Reviews (1): Last reviewed commit: "feat: implement realtime logging interfa..." | Re-trigger Greptile |
| export function frameScopeId(frame: TailFrame): string { | ||
| return ( | ||
| frame.databaseId ?? | ||
| frame.bucketId ?? | ||
| frame.functionId ?? | ||
| frame.teamId ?? | ||
| frame.resourceId ?? | ||
| '' | ||
| ); | ||
| } |
There was a problem hiding this comment.
collectionId is declared in TailFrame but omitted from the priority chain. Any event where only collectionId is set (no databaseId) will fall through to bucketId or further, silently displaying the wrong resource — or nothing at all.
| export function frameScopeId(frame: TailFrame): string { | |
| return ( | |
| frame.databaseId ?? | |
| frame.bucketId ?? | |
| frame.functionId ?? | |
| frame.teamId ?? | |
| frame.resourceId ?? | |
| '' | |
| ); | |
| } | |
| export function frameScopeId(frame: TailFrame): string { | |
| return ( | |
| frame.collectionId ?? | |
| frame.databaseId ?? | |
| frame.bucketId ?? | |
| frame.functionId ?? | |
| frame.teamId ?? | |
| frame.resourceId ?? | |
| '' | |
| ); | |
| } |
| <Table.Header.Cell column={id} {root}>{title}</Table.Header.Cell> | ||
| {/each} | ||
| </svelte:fragment> | ||
| {#each events as frame, index (index)} |
There was a problem hiding this comment.
Using the array index as the Svelte keyed-
each key means every existing row's key shifts whenever new events are prepended to the front of the list, so Svelte patches all rows instead of only inserting the new ones. For a live log viewer that can receive high-frequency bursts this causes avoidable DOM churn. A stable key derived from content is preferred.
| {#each events as frame, index (index)} | |
| {#each events as frame (`${frame.timestamp}:${frame.event}`)} |
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
| options={actionOptions} | ||
| bind:value={actionFilter} /> | ||
| </Layout.Stack> | ||
|
|
||
| <Layout.Stack direction="row" gap="s" inline> | ||
| <Button secondary on:click={() => (paused = !paused)}> | ||
| <Icon icon={paused ? IconPlay : IconPause} slot="start" size="s" /> | ||
| {paused ? 'Resume' : 'Pause'} | ||
| </Button> | ||
| <Button secondary disabled={!events.length} on:click={clear}> | ||
| <Icon icon={IconTrash} slot="start" size="s" /> | ||
| Clear | ||
| </Button> | ||
| </Layout.Stack> |
There was a problem hiding this comment.
Mixed Svelte 4/5 template syntax
The script block uses Svelte 5 runes ($state, $derived, $effect) but the template uses Svelte 4 syntax: on:click event directives, named slot="start" attributes, and let:root slot bindings on Table.Root. AGENTS.md explicitly prohibits mixing both syntaxes in a single component. These should be the Svelte 5 equivalents: onclick={}, snippet {#snippet start()}…{/snippet}, and the snippet-based table API.
| realtime.subscribe(channel, onMessage, untrack(buildQueries)).then((sub) => { | ||
| if (cancelled) { | ||
| sub.close(); | ||
| return; | ||
| } | ||
| localSub = sub; | ||
| subscription = sub; | ||
| }); | ||
|
|
||
| return () => { | ||
| cancelled = true; | ||
| localSub?.close(); | ||
| if (subscription === localSub) subscription = null; | ||
| }; | ||
| }); | ||
|
|
||
| // Apply server-side filter changes in place via update() — no reconnect. Runs when a | ||
| // filter changes or when the subscription is first established. |
There was a problem hiding this comment.
The async realtime.subscribe(…).then(…) call has no .catch() or error branch. If the WebSocket handshake fails (bad region, network outage, permissions), the Promise rejects silently — no notification is shown to the user and subscription stays null indefinitely. Adding a .catch() that calls addNotification({ type: 'error', … }) would surface the failure in line with the project's error handling convention.
| <Cover> | ||
| <svelte:fragment slot="header"> | ||
| <Typography.Title color="--fgcolor-neutral-primary" size="xl">Realtime</Typography.Title> | ||
| </svelte:fragment> |
There was a problem hiding this comment.
Svelte 4 slot syntax in a new file
<svelte:fragment slot="header"> is Svelte 4 syntax. Since this is a brand-new file there is no migration cost; it should use the Svelte 5 snippet API ({#snippet header()}…{/snippet}) from the start, per the project's convention that new code should use runes/snippets.
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
What does this PR do?
(Provide a description of what this PR does.)
Test Plan
(Write your test plan here. If you changed any code, please provide us with clear instructions on how you verified your changes work.)
Related PRs and Issues
(If this PR is related to any other PR or resolves any issue or related to any issue link all related PR and issues here.)
Have you read the Contributing Guidelines on issues?
(Write your answer here.)