Skip to content

feat: implement realtime logging interface with subscription support#3080

Open
ArnabChatterjee20k wants to merge 1 commit into
mainfrom
test-console-realtime-tail-logs
Open

feat: implement realtime logging interface with subscription support#3080
ArnabChatterjee20k wants to merge 1 commit into
mainfrom
test-console-realtime-tail-logs

Conversation

@ArnabChatterjee20k
Copy link
Copy Markdown
Member

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.)

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Jun 7, 2026

Greptile Summary

This 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 realtime.forProject/forConsole helpers with an optional queries parameter for server-side filtering, and introduces the full route scaffold (+layout, +page, store, header, breadcrumbs).

  • store.ts defines TailFrame, filter options, and the frameScopeId display helper \u2014 but collectionId is omitted from the helper's priority chain despite being declared in the type, meaning collection-scoped events silently show no resource ID.
  • +page.svelte opens a WebSocket subscription via two coordinated $effect blocks, but uses Svelte 4 template syntax inside a Svelte 5 runes component and uses the array index as the #each key, which negates DOM-diffing when events are prepended.
  • The async subscribe() call has no .catch() path, so connection failures are swallowed silently with no user notification.

Confidence Score: 3/5

The 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

Filename Overview
src/routes/(console)/project-[region]-[project]/realtime/+page.svelte Main realtime log viewer page — mixes Svelte 4 template syntax (on:click, slot=, let:root) with Svelte 5 runes; uses index as #each key causing unnecessary DOM churn on prepend; async subscribe() has no error handling.
src/routes/(console)/project-[region]-[project]/realtime/store.ts Defines TailFrame type with collectionId field, but frameScopeId() omits it from the display chain, so collection-scoped events never surface the collection ID in the Resource column.
src/lib/stores/sdk.ts Adds optional queries parameter to realtime.forProject, realtime.forConsole, and createRealtimeSubscription — clean, backward-compatible change.
src/routes/(console)/project-[region]-[project]/realtime/header.svelte New header component using Svelte 4 slot syntax (svelte:fragment slot=) — should use Svelte 5 snippet API since this is a new file.
src/lib/components/sidebar.svelte Adds Realtime entry (IconRss, slug: 'realtime') to the build category sidebar items — straightforward addition.

Reviews (1): Last reviewed commit: "feat: implement realtime logging interfa..." | Re-trigger Greptile

Comment on lines +44 to +53
export function frameScopeId(frame: TailFrame): string {
return (
frame.databaseId ??
frame.bucketId ??
frame.functionId ??
frame.teamId ??
frame.resourceId ??
''
);
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 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.

Suggested change
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)}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 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.

Suggested change
{#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!

Comment on lines +120 to +133
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>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 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.

Comment on lines +83 to +100
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.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Unhandled subscription error

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.

Comment on lines +6 to +9
<Cover>
<svelte:fragment slot="header">
<Typography.Title color="--fgcolor-neutral-primary" size="xl">Realtime</Typography.Title>
</svelte:fragment>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 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!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant