Skip to content

Conversation

ItzNotABug
Copy link
Member

@ItzNotABug ItzNotABug commented Sep 10, 2025

What does this PR do?

Fixes the usage layout shift.

Test Plan

Manual.

Before -

Screen.Recording.2025-09-10.at.1.02.05.PM.mov

After -

shifts-fixed.mov

Related PRs and Issues

N/A.

Have you read the Contributing Guidelines on issues?

Yes.

Summary by CodeRabbit

  • New Features

    • Overview and chart skeleton components added; charts gain a public toggle to disable built-in styling.
    • Cards can opt to make inner content fill available height.
    • New seeded fake data generators for charts.
  • Performance

    • Client-side caching for usage data and a minimum loading display time to avoid flicker; new loading state exposed.
  • UX Improvements

    • Period selectors disabled while loading; consistent skeleton-driven loading and clearer no-data presentations.

@ItzNotABug ItzNotABug self-assigned this Sep 10, 2025
Copy link

appwrite bot commented Sep 10, 2025

Console

Project ID: 688b7bf400350cbd60e9

Sites (2)
Site Status Logs Preview QR
 console-stage
688b7cf6003b1842c9dc
Ready Ready View Logs Preview URL QR Code
 console-cloud
688b7c18002b9b871a8f
Ready Ready View Logs Preview URL QR Code

Note

Appwrite has a Discord community with over 16 000 members.

Copy link
Contributor

coderabbitai bot commented Sep 10, 2025

Walkthrough

Adds UI and data changes across the project overview area. New public props: applyStyles on the line chart and fullHeightChild on Card. Adds two fake-data generators (generateFakeBarChartData, generateFakeLineChartData) and two skeleton components (extended.svelte, simple.svelte). Refactors overview pages (bandwidth, requests, layout) to read { period, loading } from $props(), use derived stores, produce loading-aware chart data/options, and render skeletons while loading. Updates overview store with hash-based request caching, a loadingProjectUsage flag with a minimum display time, and new exports for dev-key UI state and column definitions.

Suggested reviewers

  • HarshMN2345
  • lohanidamodar

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title references the PR’s objective of fixing usage shifts and therefore relates to the main changeset, although it omits the term “layout” which could make the purpose more explicit.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix-usage-layout-shifts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 9

🧹 Nitpick comments (11)
src/lib/charts/line.svelte (1)

17-29: Avoid mutating incoming series; return new objects to prevent side-effects.

The current map mutates each series object. Prefer immutable mapping to keep props pure and avoid subtle bugs when the same series array is reused elsewhere.

Apply:

-    series={series.map((s) => {
-        s.type = 'line';
-        s.stack = 'total';
-        s.lineStyle = {
-            shadowBlur: applyStyles ? 38 : undefined,
-            shadowColor: applyStyles ? Colors.Primary : undefined,
-            shadowOffsetY: applyStyles ? 15 : undefined,
-            shadowOffsetX: 0,
-            width: applyStyles ? undefined : 2
-        };
-        s.showSymbol = false;
-
-        return s;
-    })}
+    series={series.map((s) => ({
+        ...s,
+        type: 'line',
+        stack: s.stack ?? 'total',
+        lineStyle: {
+            ...s.lineStyle,
+            shadowBlur: applyStyles ? 38 : undefined,
+            shadowColor: applyStyles ? Colors.Primary : undefined,
+            shadowOffsetY: applyStyles ? 15 : undefined,
+            shadowOffsetX: 0,
+            width: applyStyles ? undefined : 2
+        },
+        showSymbol: false
+    }))}
src/lib/components/card.svelte (1)

49-73: Ensure the parent container can actually stretch to 100% height.

Setting height="100%" on Layout.Stack only works if the Card variant (Link/Button/Base) also participates in a height-constrained layout. If you see no effect, add a fallback inline style to the stack.

Example tweak:

-        <Layout.Stack gap="xl" height={fullHeightChild ? '100%' : undefined}>
+        <Layout.Stack
+            gap="xl"
+            height={fullHeightChild ? '100%' : undefined}
+            style={fullHeightChild ? 'min-height:100%' : undefined}>

(Repeat for the other two branches.)

src/routes/(console)/project-[region]-[project]/overview/(components)/skeletons/extended.svelte (2)

30-47: Minor a11y: mark container busy and announce updates politely.

-        <div class="overlay-container">
+        <div class="overlay-container" aria-busy={loading} aria-live="polite">

54-59: Provide a safe fallback for lh unit to reduce cross-browser layout jitter.

-    .overlay-container {
-        display: grid;
-        min-height: 1lh;
+    .overlay-container {
+        display: grid;
+        min-height: 1em; /* fallback */
+        min-height: 1lh;
src/routes/(console)/project-[region]-[project]/overview/(components)/skeletons/simple.svelte (2)

19-39: Minor a11y: mark busy and announce politely.

-    <div class="overlay-container">
+    <div class="overlay-container" aria-busy={loading} aria-live="polite">

46-51: Add lh fallback to avoid layout differences across browsers.

-    .overlay-container {
-        display: grid;
-        min-height: 1lh;
+    .overlay-container {
+        display: grid;
+        min-height: 1em; /* fallback */
+        min-height: 1lh;
src/routes/(console)/project-[region]-[project]/overview/+layout.svelte (2)

37-39: Avoid double-invoking handle on initial load.

onMount(handle) + afterNavigate(handle) can both fire on first render. Consider keeping only afterNavigate, or gate the first call.

Example:

let first = true;
afterNavigate(() => {
    if (first) { first = false; handle(); return; }
    handle();
});

98-98: Remove debug log.

Stray console log in production.

-    $: console.log(`$loadingProjectUsage: ${$loadingProjectUsage}`);
+    // Intentionally left blank
src/routes/(console)/project-[region]-[project]/overview/store.ts (1)

54-57: Minor: avoid store.get for static columns

get(devKeyColumns) freezes the current value at module init. Since columns are static, just spread the literal to keep things simple.

Apply this diff:

-export const keyColumns = readable<Column[]>([
-    ...get(devKeyColumns),
-    { id: 'scopes', title: 'Scopes', type: 'string', width: { min: 120 } }
-]);
+const baseDevKeyColumns: Column[] = [
+    { id: 'name', title: 'Name', type: 'string', width: { min: 120 } },
+    { id: 'last_accessed', title: 'Last accessed', type: 'datetime', width: { min: 120 } },
+    { id: 'expiration', title: 'Expiration date', type: 'datetime', width: { min: 120 } }
+];
+export const devKeyColumns = readable<Column[]>(baseDevKeyColumns);
+export const keyColumns = readable<Column[]>([
+    ...baseDevKeyColumns,
+    { id: 'scopes', title: 'Scopes', type: 'string', width: { min: 120 } }
+]);
src/routes/(console)/project-[region]-[project]/overview/bandwidth.svelte (2)

46-47: Guard chart data when not loading

If network is undefined, chartData becomes undefined. Safer to default to [] to avoid surprises in downstream consumers.

Apply this diff:

-    let chartData = $derived(loading ? fakeBarChartData : network?.map((e) => [e.date, e.value]));
+    let chartData = $derived(
+        loading ? fakeBarChartData : (network?.map((e) => [e.date, e.value]) ?? [])
+    );

57-71: Micro: avoid double compute in axis label formatter

humanFileSize(+value) is called twice. Cache once.

Apply this diff:

-                    formatter: (value: number) => {
-                        return loading
-                            ? '-- MB'
-                            : !value
-                              ? '0'
-                              : `${humanFileSize(+value).value} ${humanFileSize(+value).unit}`;
-                    }
+                    formatter: (value: number) => {
+                        if (loading) return '-- MB';
+                        if (!value) return '0';
+                        const hf = humanFileSize(+value);
+                        return `${hf.value} ${hf.unit}`;
+                    }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5b98696 and 60d6ead.

⛔ Files ignored due to path filters (11)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
  • src/routes/(console)/project-[region]-[project]/overview/intro-dark.png is excluded by !**/*.png
  • src/routes/(console)/project-[region]-[project]/overview/intro-light.png is excluded by !**/*.png
  • src/routes/(console)/project-[region]-[project]/overview/onboard-1-dark-desktop.svg is excluded by !**/*.svg
  • src/routes/(console)/project-[region]-[project]/overview/onboard-1-dark-mobile.svg is excluded by !**/*.svg
  • src/routes/(console)/project-[region]-[project]/overview/onboard-1-light-desktop.svg is excluded by !**/*.svg
  • src/routes/(console)/project-[region]-[project]/overview/onboard-1-light-mobile.svg is excluded by !**/*.svg
  • src/routes/(console)/project-[region]-[project]/overview/onboard-2-dark-desktop.svg is excluded by !**/*.svg
  • src/routes/(console)/project-[region]-[project]/overview/onboard-2-dark-mobile.svg is excluded by !**/*.svg
  • src/routes/(console)/project-[region]-[project]/overview/onboard-2-light-desktop.svg is excluded by !**/*.svg
  • src/routes/(console)/project-[region]-[project]/overview/onboard-2-light-mobile.svg is excluded by !**/*.svg
📒 Files selected for processing (10)
  • package.json (1 hunks)
  • src/lib/charts/line.svelte (2 hunks)
  • src/lib/components/card.svelte (3 hunks)
  • src/lib/helpers/faker.ts (1 hunks)
  • src/routes/(console)/project-[region]-[project]/overview/(components)/skeletons/extended.svelte (1 hunks)
  • src/routes/(console)/project-[region]-[project]/overview/(components)/skeletons/simple.svelte (1 hunks)
  • src/routes/(console)/project-[region]-[project]/overview/+layout.svelte (3 hunks)
  • src/routes/(console)/project-[region]-[project]/overview/bandwidth.svelte (1 hunks)
  • src/routes/(console)/project-[region]-[project]/overview/requests.svelte (1 hunks)
  • src/routes/(console)/project-[region]-[project]/overview/store.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/routes/(console)/project-[region]-[project]/overview/store.ts (2)
src/lib/helpers/string.ts (1)
  • hash (77-84)
src/lib/stores/sdk.ts (1)
  • sdk (142-165)
🔇 Additional comments (4)
package.json (1)

27-29: Lockfile consistency verified
Both @appwrite.io/pink-svelte and @appwrite.io/pink-icons-svelte in pnpm-lock.yaml resolve to commit 1489b16 with no lingering duplicate versions.

src/lib/charts/line.svelte (1)

20-21: Confirm that stacking is intended for all line series.

stack = 'total' changes chart semantics; verify this is desired for every caller of .

src/lib/components/card.svelte (1)

20-21: Prop addition LGTM; name reads well.

fullHeightChild is clear and defaulting to false avoids behavior changes.

src/routes/(console)/project-[region]-[project]/overview/+layout.svelte (1)

111-121: Nice: passing loading through to child charts aligns with the skeleton flow.

This is consistent and reduces perceived layout shift during fetch.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (2)
src/routes/(console)/project-[region]-[project]/overview/store.ts (1)

19-60: Critical: Race conditions and stuck loading state remain unaddressed.

The past review comment on lines 20-42 identified two critical issues that are still present:

  1. Race condition: Concurrent loads can result in older responses overwriting newer data. Without tracking the in-flight request, lastParamsHash can be set out of order.

  2. Stuck loading on errors: If the API call throws, loadingProjectUsage remains true indefinitely. Combined with the minTime delay, this creates a poor UX where the UI is stuck in a loading state.

Apply the diff from the previous review comment to add try/finally blocks and in-flight request tracking:

 export const usage = cachedStore<
     Models.UsageProject,
     {
         load: (start: string, end: string, period: ProjectUsageRange) => Promise<void>;
     }
 >('projectUsage', function ({ set }) {
     const minTime = 1250;
     let lastParamsHash: string | null = null;
+    let inflightParamsHash: string | null = null;

     return {
         load: async (start, end, period) => {
             const currentData = get(usage);
             const currentParamsHash = hash([
                 page.params.project,
                 page.params.region,
                 start,
                 end,
                 period.toString()
             ]);

             // don't hit the API call if we have the data!
             if (lastParamsHash === currentParamsHash && currentData && !isDev) {
                 loadingProjectUsage.set(false);
                 return;
             }

             const initTime = Date.now();
             loadingProjectUsage.set(true);
+            inflightParamsHash = currentParamsHash;

-            const usages = await sdk
-                .forProject(page.params.region, page.params.project)
-                .project.getUsage({
-                    startDate: start,
-                    endDate: end,
-                    period
-                });
-
-            const elapsed = Date.now() - initTime;
-            const remainingTime = minTime - elapsed;
-
-            if (remainingTime >= 0) {
-                await sleep(remainingTime);
-            }
-
-            set(usages);
-            lastParamsHash = currentParamsHash;
-            loadingProjectUsage.set(false);
+            try {
+                const usages = await sdk
+                    .forProject(page.params.region, page.params.project)
+                    .project.getUsage({
+                        startDate: start,
+                        endDate: end,
+                        period
+                    });
+
+                // Ignore stale responses
+                if (inflightParamsHash !== currentParamsHash) return;
+
+                const elapsed = Date.now() - initTime;
+                const remainingTime = minTime - elapsed;
+
+                if (remainingTime >= 0) {
+                    await sleep(remainingTime);
+                }
+
+                set(usages);
+                lastParamsHash = currentParamsHash;
+            } catch (err) {
+                console.error('Failed to load project usage', err);
+            } finally {
+                // Only clear loading if this request is still the latest
+                if (inflightParamsHash === currentParamsHash) {
+                    loadingProjectUsage.set(false);
+                    inflightParamsHash = null;
+                }
+            }
         }
     };
 });
src/routes/(console)/project-[region]-[project]/overview/requests.svelte (1)

38-47: Runtime risk: requests can be undefined.

The derived requests value uses a type assertion but doesn't guarantee the value exists. At line 46, requests.map() will throw if requests is undefined. While the author previously stated "not when not loading", there's no guarantee that $usage?.requests is populated when loading becomes false.

Apply this diff to add defensive checks:

 let requests = $derived(
-    $usage?.requests as unknown as Array<{
+    ($usage?.requests ?? []) as Array<{
         date: number;
         value: number;
     }>
 );

 let chartData = $derived(
-    loading ? fakeLineChartData : [...requests.map((e) => [e.date, e.value])]
+    loading ? fakeLineChartData : requests.map((e) => [e.date, e.value])
 );
🧹 Nitpick comments (1)
src/routes/(console)/project-[region]-[project]/overview/bandwidth.svelte (1)

38-47: Consider improving type safety and null handling.

While line 47 uses optional chaining (network?.map()), the type assertion pattern at lines 38-43 is similar to requests.svelte and could be more defensive. Consider explicitly defaulting to an empty array:

 let network = $derived(
-    $usage?.network as unknown as Array<{
+    ($usage?.network ?? []) as Array<{
         date: number;
         value: number;
     }>
 );

 let bandwidth = $derived(humanFileSize(totalMetrics($usage?.network)));

-let chartData = $derived(loading ? fakeBarChartData : network?.map((e) => [e.date, e.value]));
+let chartData = $derived(loading ? fakeBarChartData : network.map((e) => [e.date, e.value]));

This ensures network is always an array and eliminates the need for optional chaining.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between df7f405 and 7cb4a5a.

📒 Files selected for processing (4)
  • src/lib/helpers/faker.ts (1 hunks)
  • src/routes/(console)/project-[region]-[project]/overview/bandwidth.svelte (1 hunks)
  • src/routes/(console)/project-[region]-[project]/overview/requests.svelte (1 hunks)
  • src/routes/(console)/project-[region]-[project]/overview/store.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/lib/helpers/faker.ts
🧰 Additional context used
🧬 Code graph analysis (1)
src/routes/(console)/project-[region]-[project]/overview/store.ts (3)
src/lib/helpers/string.ts (1)
  • hash (77-84)
src/lib/system.ts (1)
  • isDev (28-28)
src/lib/stores/sdk.ts (1)
  • sdk (147-170)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: build
  • GitHub Check: e2e
🔇 Additional comments (2)
src/routes/(console)/project-[region]-[project]/overview/requests.svelte (1)

85-100: LGTM! Dispatch issue resolved.

The sequential toggle(event); dispatch(...) pattern correctly ensures both functions execute. This addresses the previous review comment about the short-circuit issue.

src/routes/(console)/project-[region]-[project]/overview/bandwidth.svelte (1)

84-106: LGTM! Dispatch issue resolved.

The sequential toggle(event); dispatch(...) pattern correctly addresses the previous short-circuit issue, ensuring both the menu toggle and period change dispatch execute as expected.

Comment on lines +67 to +69
$effect(() => {
console.log(totalMetrics($usage?.requests));
});
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

Remove debug logging from production code.

The console.log statement should be removed before merging to production.

Apply this diff:

-    $effect(() => {
-        console.log(totalMetrics($usage?.requests));
-    });
📝 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
$effect(() => {
console.log(totalMetrics($usage?.requests));
});
🤖 Prompt for AI Agents
In src/routes/(console)/project-[region]-[project]/overview/requests.svelte
around lines 67 to 69, remove the debug console.log call inside the $effect
block; replace it with nothing or, if needed for production-safe telemetry, send
the value to a proper logger/analytics function instead of console.log, ensuring
no debug logs remain in production code.

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