Skip to content

Conversation

HarshMN2345
Copy link
Member

@HarshMN2345 HarshMN2345 commented Sep 2, 2025

What does this PR do?

image

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?

yes

Summary by CodeRabbit

  • New Features

    • Independent pagination and per-page limits for archived projects, with separate totals and navigation for active vs archived lists.
  • Improvements

    • More accurate counts shown in badges and cards for both lists.
    • Customizable page query parameter with option to hide it on the first page.
    • UI tweaks: slightly shorter name truncation, adjusted platform display threshold, and improved pagination spacing and layout passthrough.

Copy link

appwrite bot commented Sep 2, 2025

Console

Project ID: 688b7bf400350cbd60e9

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

Note

You can use Avatars API to generate QR code for any text or URLs.

@HarshMN2345 HarshMN2345 self-assigned this Sep 2, 2025
Copy link
Contributor

coderabbitai bot commented Sep 2, 2025

Warning

Rate limit exceeded

@HarshMN2345 has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 11 minutes and 2 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between 6ad7ee1 and c8bbb97.

📒 Files selected for processing (1)
  • src/routes/(console)/organization-[organization]/+page.ts (1 hunks)

Walkthrough

The load function now fetches active and archived projects separately and returns per-type pages, totals, and archived paging state (archivedPage, archivedOffset). The organization page uses these fields to bind lists, totals, and pagination; it passes archivedTotalOverall, archivedOffset, and limit into ArchiveProject. ArchiveProject now reads those props, shows archived totals, and renders PaginationWithLimit with pageParam="archivedPage" and removeOnFirstPage. Pagination, Limit, and PaginationWithLimit accept pageParam and removeOnFirstPage; paginator and paginationWithLimit forward extra props to their layout containers. Minor UI tweaks include name truncation and pagination spacing.

Possibly related PRs

Suggested reviewers

  • lohanidamodar
  • ItzNotABug
  • TorstenDittmann

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title accurately summarizes the primary change—adding pagination for organization projects and adding archived-projects pagination—and matches the PR objectives and code changes across multiple files. It is focused and readable, though slightly redundant in phrasing ("pagination" appears twice) and could be tightened. Overall it communicates the main intent clearly for reviewers scanning history.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.

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.

@HarshMN2345 HarshMN2345 changed the title fix org projects pagination and add archived pagination feat: org projects pagination and add archived pagination Sep 2, 2025
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: 2

🧹 Nitpick comments (10)
src/routes/(console)/organization-[organization]/+page.svelte (3)

107-109: Avoid redundant filtering when server already provides archived set

Use the pre-filtered data.archivedProjectsPage directly; fall back to filtering only when it’s absent.

-    $: projectsToArchive = (data.archivedProjectsPage ?? data.projects.projects).filter(
-        (project) => project.status !== 'active'
-    );
+    $: projectsToArchive = data.archivedProjectsPage
+        ?? data.projects.projects.filter((project) => project.status !== 'active');

111-115: Prefer activeTotal fallback order and align all consumers to it

  • Reorder fallbacks so we prefer data.projects.total before data.organization.projects.length (the latter may include archived).
  • Also update any plan checks and props (e.g., Line 140 condition and Line 261 prop to CreateProjectCloud) to use activeTotalOverall for consistency.
-    $: activeProjects = (data.activeProjectsPage ?? data.projects.projects).filter(
-        (project) => project.status === 'active'
-    );
-    $: activeTotalOverall = data?.activeTotalOverall ?? data?.organization?.projects?.length ?? data?.projects?.total ?? 0;
+    $: activeProjects = data.activeProjectsPage
+        ?? data.projects.projects.filter((project) => project.status === 'active');
+    $: activeTotalOverall =
+        data?.activeTotalOverall
+        ?? data?.projects?.total
+        ?? data?.organization?.projects?.length
+        ?? 0;

1-1: Fix Prettier issues

CI flagged a Prettier formatting warning for this file. Please run: pnpm prettier --write src/routes/(console)/organization-[organization]/+page.svelte.

src/lib/components/archiveProject.svelte (3)

149-151: Name truncation tightened to 16 chars — confirm UX parity

Active projects use 19/25 (small/regular). Dropping archived to 16 may create inconsistent card widths. Confirm with design or align to active logic.

-function formatName(name: string, limit: number = 16) {
+function formatName(name: string, limit: number = 19) { // or make it responsive like the active list

163-168: Paginator config: hide UI affordances when not needed and avoid magic numbers

  • With ≤6 items, you already hide the footer; consider also hiding page numbers.
  • Extract 6 into a local constant for clarity and future tuning.
-                <Paginator
-                    items={projectsToArchive}
-                    limit={6}
-                    hidePages={false}
-                    hideFooter={projectsToArchive.length <= 6}>
+                {#key projectsToArchive}<!-- reset page when data changes -->
+                <Paginator
+                    items={projectsToArchive}
+                    limit={ARCHIVE_PAGE_SIZE}
+                    hidePages={projectsToArchive.length <= ARCHIVE_PAGE_SIZE}
+                    hideFooter={projectsToArchive.length <= ARCHIVE_PAGE_SIZE}>
                     {#snippet children(items)}
                         <CardContainer disableEmpty={true} total={projectsToArchive.length}>
                             {#each items as project}
 ...
                         </CardContainer>
                     {/snippet}
                 </Paginator>

Add near the script top:

const ARCHIVE_PAGE_SIZE = 6;

Also applies to: 169-171, 264-267


1-1: Fix Prettier issues

CI flagged a Prettier formatting warning for this file. Please run: pnpm prettier --write src/lib/components/archiveProject.svelte.

src/routes/(console)/organization-[organization]/+page.ts (4)

56-56: Set region before deriving slices (minor)

Move region normalization before computing allActiveProjects/allArchivedProjects to keep derived arrays fully normalized. Low impact.

-    // set `default` if no region!
-    for (const project of allProjects) {
+    // set `default` if no region!
+    for (const project of allProjects) {
         project.region ??= 'default';
     }
-    const allActiveProjects = allProjects.filter((p) => p.status === 'active');
-    const allArchivedProjects = allProjects.filter((p) => p.status !== 'active');
+    const allActiveProjects = allProjects.filter((p) => p.status === 'active');
+    const allArchivedProjects = allProjects.filter((p) => p.status !== 'active');

31-48: Reduce N+1 API calls: decouple aggregation page size from UI page size

Looping with Query.limit(limit) ties server fetch size to UI page size. Use a larger server-side chunk (e.g., 100) within API limits to cut requests.

-    const limit = getLimit(url, route, CARD_LIMIT);
+    const limit = getLimit(url, route, CARD_LIMIT);
+    const AGG_LIMIT = Math.min(100, limit); // tune to SDK/API max page size
...
-                Query.limit(limit),
+                Query.limit(AGG_LIMIT),

1-1: Fix Prettier issues

CI flagged a Prettier formatting warning for this file. Please run: pnpm prettier --write src/routes/(console)/organization-[organization]/+page.ts.


37-42: Use explicit sort field or document default

Empty string in Query.orderDesc('') is used across the codebase to invoke the SDK’s default descending order (typically by creation date). For clarity, replace '' with an explicit field (e.g. '$createdAt') or add a comment noting that an empty string triggers default sorting.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 1c0af8d and 2a058be.

📒 Files selected for processing (3)
  • src/lib/components/archiveProject.svelte (4 hunks)
  • src/routes/(console)/organization-[organization]/+page.svelte (3 hunks)
  • src/routes/(console)/organization-[organization]/+page.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/routes/(console)/organization-[organization]/+page.ts (2)
src/routes/(console)/organization-[organization]/store.ts (1)
  • projects (17-17)
src/lib/stores/sdk.ts (1)
  • sdk (142-165)
🪛 GitHub Actions: Tests
src/routes/(console)/organization-[organization]/+page.svelte

[warning] 1-1: Prettier formatting issue detected. Run 'prettier --write' to fix.

src/routes/(console)/organization-[organization]/+page.ts

[warning] 1-1: Prettier formatting issue detected. Run 'prettier --write' to fix.

src/lib/components/archiveProject.svelte

[warning] 1-1: Prettier formatting issue detected. Run 'prettier --write' to fix.

⏰ 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). (1)
  • GitHub Check: e2e
🔇 Additional comments (5)
src/routes/(console)/organization-[organization]/+page.svelte (2)

165-165: LGTM: CardContainer now bound to active total

Using activeTotalOverall ensures paging/total reflects active projects only.


250-250: LGTM: Pagination total matches active total

PaginationWithLimit.total={activeTotalOverall} is correct and consistent with the active list.

src/lib/components/archiveProject.svelte (2)

3-3: LGTM: Paginator import

Importing Paginator from $lib/components is consistent with the component library usage.


258-261: Safe access on region already handled

Using region?.name avoids runtime errors when regions aren’t loaded yet. Good.

src/routes/(console)/organization-[organization]/+page.ts (1)

50-54: LGTM: Server-side split of active vs archived and per-page slice

This matches the UI’s new bindings and avoids filtering everything on the client.

Comment on lines 31 to 48
let allProjects: typeof projects.projects = [];
let fetchedCount = 0;
const total = projects.total;

while (fetchedCount < total) {
const next = await sdk.forConsole.projects.list({
queries: [
Query.offset(fetchedCount),
Query.equal('teamId', params.organization),
Query.limit(limit),
Query.orderDesc('')
],
search: search || undefined
});
allProjects = allProjects.concat(next.projects);
fetchedCount += next.projects.length;
if (next.projects.length === 0) break;
}
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

Seed aggregation with the first response and avoid redundant page-0 fetch

You drop the first call’s results and refetch from offset 0, doubling cost and latency. Seed allProjects with the initial page and continue from there.

-    let allProjects: typeof projects.projects = [];
-    let fetchedCount = 0;
+    let allProjects: typeof projects.projects = projects.projects.slice();
+    let fetchedCount = allProjects.length;
     const total = projects.total;
 
-    while (fetchedCount < total) {
+    while (fetchedCount < total) {
         const next = await sdk.forConsole.projects.list({
             queries: [
-                Query.offset(fetchedCount),
+                Query.offset(fetchedCount),
                 Query.equal('teamId', params.organization),
-                Query.limit(limit),
+                Query.limit(limit),
                 Query.orderDesc('')
             ],
             search: search || undefined
         });
         allProjects = allProjects.concat(next.projects);
         fetchedCount += next.projects.length;
         if (next.projects.length === 0) break;
     }
📝 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
let allProjects: typeof projects.projects = [];
let fetchedCount = 0;
const total = projects.total;
while (fetchedCount < total) {
const next = await sdk.forConsole.projects.list({
queries: [
Query.offset(fetchedCount),
Query.equal('teamId', params.organization),
Query.limit(limit),
Query.orderDesc('')
],
search: search || undefined
});
allProjects = allProjects.concat(next.projects);
fetchedCount += next.projects.length;
if (next.projects.length === 0) break;
}
// Seed with the initial page of projects instead of starting empty
let allProjects: typeof projects.projects = projects.projects.slice();
let fetchedCount = allProjects.length;
const total = projects.total;
while (fetchedCount < total) {
const next = await sdk.forConsole.projects.list({
queries: [
Query.offset(fetchedCount),
Query.equal('teamId', params.organization),
Query.limit(limit),
Query.orderDesc('')
],
search: search || undefined
});
allProjects = allProjects.concat(next.projects);
fetchedCount += next.projects.length;
if (next.projects.length === 0) break;
}
🤖 Prompt for AI Agents
In src/routes/(console)/organization-[organization]/+page.ts around lines 31 to
48, the code currently performs an initial projects fetch but then starts the
loop that refetches from offset 0, doubling cost and latency; instead, perform
the first sdk.forConsole.projects.list call once, initialize allProjects with
that response and set fetchedCount to that response.projects.length, then enter
a loop that requests subsequent pages starting at the current fetchedCount
(using Query.offset(fetchedCount)) until fetchedCount >= total or a page returns
zero items; this removes the redundant page-0 fetch while preserving the same
paging logic.

Comment on lines 63 to 67
projects: { ...projects, projects: allProjects, total: allActiveProjects.length },
activeProjectsPage: activeProjectsForPage,
archivedProjectsPage: allArchivedProjects,
activeTotalOverall: allActiveProjects.length,
search
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Don’t mutate the projects contract; return aggregates separately

Overwriting projects.projects with all items and projects.total with “active total” breaks consumers expecting API semantics. Keep projects intact and expose new top-level fields for UI.

-        projects: { ...projects, projects: allProjects, total: allActiveProjects.length },
-        activeProjectsPage: activeProjectsForPage,
-        archivedProjectsPage: allArchivedProjects,
-        activeTotalOverall: allActiveProjects.length,
+        projects, // preserve API contract for stores and other consumers
+        activeProjectsPage: activeProjectsForPage,
+        archivedProjectsPage: allArchivedProjects,
+        activeTotalOverall: allActiveProjects.length,
+        // Optionally expose allProjects if needed elsewhere:
+        // allProjects,

🏁 Script executed:

#!/bin/bash
# Audit for consumers that assume `projects.total` is "all results" (not just active).
rg -nP --type=ts --type=svelte -C2 '\bprojects\.total\b'

Length of output: 89


🏁 Script executed:

#!/bin/bash
# Audit for consumers that assume `projects.total` remains the full count
rg -nP -g '*.ts' -g '*.svelte' -C2 '\bprojects\.total\b'

Length of output: 4397


Preserve projects API contract and expose aggregates separately
Overriding projects.projects and projects.total with active-only values breaks UI components that rely on the full project count (plan checks, creation modals, settings pages). Undo the mutation and introduce new top-level fields for the aggregates, for example:

-        projects: { ...projects, projects: allProjects, total: allActiveProjects.length },
+        projects, // preserve API contract
         activeProjectsPage: activeProjectsForPage,
         archivedProjectsPage: allArchivedProjects,
+        activeTotalOverall: allActiveProjects.length,
+        allProjects, // full list if needed

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In src/routes/(console)/organization-[organization]/+page.ts around lines 63 to
67, the code is mutating the existing projects object by replacing
projects.projects and projects.total with active-only values which breaks
consumers expecting the full projects API; revert that mutation so the original
projects object remains intact and add new top-level fields (e.g.,
activeProjectsPage and activeProjectsTotal or activeProjectsAggregate) to expose
active-only lists and counts, update any callers that need the active-only
values to use these new fields, and ensure the returned payload preserves the
original projects structure while exposing the aggregates separately.

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: 2

♻️ Duplicate comments (2)
src/routes/(console)/organization-[organization]/+page.ts (2)

31-48: Avoid double-fetching and reduce total requests while aggregating.
You re-query from offset 0 after already fetching one page; on page 1 this duplicates page-0, and on later pages you still pay an extra request. Seed aggregation or restructure to a single aggregation pass.

Example approach (parallelized, larger page size):

-    let allProjects: typeof projects.projects = [];
-    let fetchedCount = 0;
-    const total = projects.total;
-
-    while (fetchedCount < total) {
-        const next = await sdk.forConsole.projects.list({
-            queries: [
-                Query.offset(fetchedCount),
-                Query.equal('teamId', params.organization),
-                Query.limit(limit),
-                Query.orderDesc('')
-            ],
-            search: search || undefined
-        });
-        allProjects = allProjects.concat(next.projects);
-        fetchedCount += next.projects.length;
-        if (next.projects.length === 0) break;
-    }
+    const total = projects.total;
+    const pageSize = Math.max(limit, 100); // fewer round-trips
+    const fetches: Promise<typeof projects>[] = [];
+    for (let start = 0; start < total; start += pageSize) {
+        fetches.push(
+            sdk.forConsole.projects.list({
+                queries: [
+                    Query.offset(start),
+                    Query.equal('teamId', params.organization),
+                    Query.limit(pageSize),
+                    Query.orderDesc('')
+                ],
+                search: search || undefined
+            })
+        );
+    }
+    const pages = await Promise.all(fetches);
+    let allProjects: typeof projects.projects = pages.flatMap((p) => p.projects);

63-66: Preserve the projects API contract; expose aggregates separately.
Overwriting projects.projects and projects.total breaks consumers that expect server semantics.

-        projects: { ...projects, projects: allProjects, total: allActiveProjects.length },
-        activeProjectsPage: activeProjectsForPage,
-        archivedProjectsPage: allArchivedProjects,
-        activeTotalOverall: allActiveProjects.length,
+        projects, // keep original paging contract
+        allProjects, // optional: full list for local consumers
+        activeProjectsPage: activeProjectsForPage,
+        archivedProjectsPage: allArchivedProjects,
+        activeTotalOverall: allActiveProjects.length,
🧹 Nitpick comments (4)
src/lib/components/archiveProject.svelte (2)

171-171: Key your each blocks for stable DOM reconciliation.
Use project id and platform name as keys.

-                            {#each items as project}
+                            {#each items as project (project.$id)}
...
-                                    {#each platforms.slice(0, 2) as platform}
+                                    {#each platforms.slice(0, 2) as platform (platform.name)}

Also applies to: 250-258


166-167: Nit: hidePages={false} is redundant if hideFooter hides controls.
You can remove hidePages unless it toggles other UI.

-                    hidePages={false}
                     hideFooter={projectsToArchive.length <= 6}>
src/routes/(console)/organization-[organization]/+page.ts (2)

56-58: Avoid mutating SDK objects in-place when normalizing region.
Safer to return normalized copies to prevent accidental shared-state mutations.

-    for (const project of allProjects) {
-        project.region ??= 'default';
-    }
+    allProjects = allProjects.map((p) => ({ ...p, region: p.region ?? 'default' }));

31-48: Optional: cap concurrency to balance latency and load.
If you parallelize page fetches, use a small pool (e.g., 4–6) rather than Promise.all on hundreds of pages.

If helpful, I can provide a pooled fetch helper.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 2a058be and 82cc38d.

📒 Files selected for processing (3)
  • src/lib/components/archiveProject.svelte (3 hunks)
  • src/routes/(console)/organization-[organization]/+page.svelte (3 hunks)
  • src/routes/(console)/organization-[organization]/+page.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/routes/(console)/organization-[organization]/+page.svelte
🔇 Additional comments (3)
src/lib/components/archiveProject.svelte (2)

3-3: Good addition: local pagination wrapper imported.
Importing Paginator here aligns with the new archived pagination flow.


149-151: Name truncation tightened (19 → 16): verify UI doesn’t regress.
Please sanity-check long archived names across viewports for truncation/tooltip behavior.

src/routes/(console)/organization-[organization]/+page.ts (1)

53-54: Confirm page semantics when slicing only active projects.
offset is derived from all projects, but you slice allActiveProjects. If archived/active are interleaved, active pages may shift. Ensure the route’s page is intended to page active-only results.

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: 0

♻️ Duplicate comments (1)
src/lib/components/archiveProject.svelte (1)

171-176: Guard against undefined platforms to avoid runtime errors.
This still calls .map on a possibly undefined project.platforms. Use a safe fallback.

-                                {@const platforms = filterPlatforms(
-                                    project.platforms.map((platform) =>
-                                        getPlatformInfo(platform.type)
-                                    )
-                                )}
+                                {@const platforms = filterPlatforms(
+                                    (project.platforms ?? []).map((platform) =>
+                                        getPlatformInfo(platform.type)
+                                    )
+                                )}
🧹 Nitpick comments (3)
src/lib/components/archiveProject.svelte (3)

149-151: Truncation default lowered to 16 — confirm UX consistency.
Verify this aligns with design across breakpoints; otherwise consider keeping the previous limit or making it a shared constant.


163-170: Paginator wiring and CardContainer total — confirm contract.

  • Ensure Paginator supports the {#snippet children(items)} API with items as the paged slice and that hideFooter behaves as intended.
  • If CardContainer’s total is meant to reflect items on the current page (not overall), switch to items.length to avoid UI/skeleton mismatches.

Proposed change if per-page is expected:

-                        <CardContainer disableEmpty={true} total={projectsToArchive.length}>
+                        <CardContainer disableEmpty={true} total={items.length}>

170-170: Key the each block for stable DOM when paginating.
Helps avoid unnecessary re-renders when page content changes.

-                            {#each items as project}
+                            {#each items as project (project.$id)}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 82cc38d and 11e460c.

📒 Files selected for processing (1)
  • src/lib/components/archiveProject.svelte (3 hunks)
⏰ 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: e2e
  • GitHub Check: build
🔇 Additional comments (3)
src/lib/components/archiveProject.svelte (3)

3-3: Importing Paginator for client-side pagination looks good.


260-265: Off-by-one fix for “+N” badge — LGTM.
Condition > 2 correctly shows “+1” when there are 3 platforms.


268-271: Region rendering gated safely — LGTM.
The $regionsStore?.regions check prevents .find on undefined and the UI handles missing names.

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: 2

♻️ Duplicate comments (2)
src/routes/(console)/organization-[organization]/+page.ts (1)

73-77: Don’t repurpose projects for active-only; preserve API contract

Overwriting projects.total with the active count breaks consumers expecting the full “for this query” semantics (seen elsewhere in the app). Expose per-type fields separately as you already do.

-        projects: {
-            ...activeProjects,
-            projects: activeProjects.projects,
-            total: activeTotal.total
-        },
+        // Preserve original list response for the active query without altering semantics
+        projects: activeProjects,
         activeProjectsPage: activeProjects.projects,
         archivedProjectsPage: archivedProjects.projects,
         activeTotalOverall: activeTotal.total,
         archivedTotalOverall: archivedTotal.total,
src/lib/components/archiveProject.svelte (1)

218-221: Guard against undefined platforms array

.map on undefined will throw for projects missing platforms.

-                        {@const platforms = filterPlatforms(
-                            project.platforms.map((platform) => getPlatformInfo(platform.type))
-                        )}
+                        {@const platforms = filterPlatforms(
+                            (project.platforms ?? []).map((platform) => getPlatformInfo(platform.type))
+                        )}
🧹 Nitpick comments (3)
src/lib/components/archivedPagination.svelte (1)

7-7: Guard currentPage against limit=0/undefined

Avoid NaN/Infinity and clamp to page >= 1.

-    const currentPage = $derived(Math.floor(offset / limit + 1));
+    const currentPage = $derived(Math.max(1, Math.floor(offset / Math.max(1, Number(limit) || 1)) + 1));
src/lib/components/archivedPaginationWithLimit.svelte (1)

30-30: Drop unused event listener

on:page isn’t handled; remove to avoid confusion.

-    <ArchivedPagination on:page {limit} {offset} sum={total} />
+    <ArchivedPagination {limit} {offset} sum={total} />
src/routes/(console)/organization-[organization]/+page.ts (1)

21-23: Clamp archivedPage to ≥ 1

Protect against invalid/negative/zero values.

-    const archivedPage = parseInt(url.searchParams.get('archivedPage') || '1');
+    const archivedPage = Math.max(1, parseInt(url.searchParams.get('archivedPage') || '1', 10) || 1);
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 11e460c and 655b053.

📒 Files selected for processing (6)
  • src/lib/components/archiveProject.svelte (6 hunks)
  • src/lib/components/archivedLimit.svelte (1 hunks)
  • src/lib/components/archivedPagination.svelte (1 hunks)
  • src/lib/components/archivedPaginationWithLimit.svelte (1 hunks)
  • src/routes/(console)/organization-[organization]/+page.svelte (3 hunks)
  • src/routes/(console)/organization-[organization]/+page.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/routes/(console)/organization-[organization]/+page.ts (2)
src/lib/helpers/load.ts (1)
  • pageToOffset (4-6)
src/lib/stores/sdk.ts (1)
  • sdk (142-165)
⏰ 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). (1)
  • GitHub Check: e2e

Comment on lines 18 to 36
async function limitChange() {
const url = new URL(pageStore.url);
const previousLimit = Number(url.searchParams.get('limit'));
url.searchParams.set('limit', limit.toString());
preferences.setLimit(limit);
// Handle archived page pagination
if (url.searchParams.has('archivedPage')) {
const page = Number(url.searchParams.get('archivedPage'));
const newPage = Math.floor(((page - 1) * previousLimit) / limit);
if (newPage === 1) {
url.searchParams.delete('archivedPage');
} else {
url.searchParams.set('archivedPage', newPage.toString());
}
}
await goto(url.toString());
}
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

Fix off-by-one and null handling when recalculating archivedPage on limit change

  • Off-by-one: should be floor(startIndex/newLimit) + 1.
  • When limit query is missing, Number(null) becomes 0 and can produce page 0.
  • Clamp to page >= 1 and defend against zero/invalid limits.
-        const previousLimit = Number(url.searchParams.get('limit'));
+        const previousLimit = Number(url.searchParams.get('limit') ?? limit) || limit;
@@
-        if (url.searchParams.has('archivedPage')) {
-            const page = Number(url.searchParams.get('archivedPage'));
-            const newPage = Math.floor(((page - 1) * previousLimit) / limit);
-            if (newPage === 1) {
+        if (url.searchParams.has('archivedPage')) {
+            const page = Math.max(1, Number(url.searchParams.get('archivedPage')) || 1);
+            const startIndex = (page - 1) * Math.max(1, previousLimit);
+            const newPage = Math.max(1, Math.floor(startIndex / Math.max(1, limit)) + 1);
+            if (newPage <= 1) {
                 url.searchParams.delete('archivedPage');
             } else {
-                url.searchParams.set('archivedPage', newPage.toString());
+                url.searchParams.set('archivedPage', String(newPage));
             }
         }
📝 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
async function limitChange() {
const url = new URL(pageStore.url);
const previousLimit = Number(url.searchParams.get('limit'));
url.searchParams.set('limit', limit.toString());
preferences.setLimit(limit);
// Handle archived page pagination
if (url.searchParams.has('archivedPage')) {
const page = Number(url.searchParams.get('archivedPage'));
const newPage = Math.floor(((page - 1) * previousLimit) / limit);
if (newPage === 1) {
url.searchParams.delete('archivedPage');
} else {
url.searchParams.set('archivedPage', newPage.toString());
}
}
await goto(url.toString());
}
async function limitChange() {
const url = new URL(pageStore.url);
const previousLimit = Number(url.searchParams.get('limit') ?? limit) || limit;
url.searchParams.set('limit', limit.toString());
preferences.setLimit(limit);
// Handle archived page pagination
if (url.searchParams.has('archivedPage')) {
const page = Math.max(1, Number(url.searchParams.get('archivedPage')) || 1);
const startIndex = (page - 1) * Math.max(1, previousLimit);
const newPage = Math.max(1, Math.floor(startIndex / Math.max(1, limit)) + 1);
if (newPage <= 1) {
url.searchParams.delete('archivedPage');
} else {
url.searchParams.set('archivedPage', String(newPage));
}
}
await goto(url.toString());
}

Comment on lines 112 to 118
$: projectsToArchive = (data.archivedProjectsPage ?? data.projects.projects).filter(
(project) => (isCloud ? project.status === 'archived' : project.status !== 'active') // fallback for non-cloud
);
$: activeProjects = data.projects.projects.filter((project) => project.status !== 'archived');
$: activeProjects = (data.activeProjectsPage ?? data.projects.projects).filter(
(project) => project.status === 'active'
);
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

Correct archived/active filters for non-cloud (null means active)

Per prior guidance, only status === 'archived' is archived; null is active. Current fallback misclassifies nulls.

-    $: projectsToArchive = (data.archivedProjectsPage ?? data.projects.projects).filter(
-        (project) => (isCloud ? project.status === 'archived' : project.status !== 'active') // fallback for non-cloud
-    );
+    $: projectsToArchive = (data.archivedProjectsPage ?? data.projects.projects).filter(
+        (project) => project.status === 'archived'
+    );
@@
-    $: activeProjects = (data.activeProjectsPage ?? data.projects.projects).filter(
-        (project) => project.status === 'active'
-    );
+    $: activeProjects = (data.activeProjectsPage ?? data.projects.projects).filter(
+        (project) => project.status !== 'archived'
+    );
📝 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
$: projectsToArchive = (data.archivedProjectsPage ?? data.projects.projects).filter(
(project) => (isCloud ? project.status === 'archived' : project.status !== 'active') // fallback for non-cloud
);
$: activeProjects = data.projects.projects.filter((project) => project.status !== 'archived');
$: activeProjects = (data.activeProjectsPage ?? data.projects.projects).filter(
(project) => project.status === 'active'
);
$: projectsToArchive = (data.archivedProjectsPage ?? data.projects.projects).filter(
(project) => project.status === 'archived'
);
$: activeProjects = (data.activeProjectsPage ?? data.projects.projects).filter(
(project) => project.status !== 'archived'
);
🤖 Prompt for AI Agents
In src/routes/(console)/organization-[organization]/+page.svelte around lines
112–118, the fallback for non-cloud currently treats null as non-active causing
misclassification; change the archived filter to only include project.status ===
'archived' (no non-cloud fallback that treats null as archived), and change the
activeProjects filter to include project.status === 'active' or, for non-cloud,
treat null as active (i.e. project.status === 'active' || project.status == null
when isCloud is false).

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: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/routes/(console)/organization-[organization]/+page.ts (1)

10-12: Use throw redirect(...), not return redirect(...).

SvelteKit expects a thrown redirect. Returning it won’t redirect.

-    if (!scopes.includes('projects.read') && scopes.includes('billing.read')) {
-        return redirect(301, `/console/organization-${params.organization}/billing`);
-    }
+    if (!scopes.includes('projects.read') && scopes.includes('billing.read')) {
+        throw redirect(301, `/console/organization-${params.organization}/billing`);
+    }
src/lib/components/archiveProject.svelte (1)

219-221: Guard against undefined platforms to prevent runtime crash

project.platforms can be undefined; .map would throw.

-                        {@const platforms = filterPlatforms(
-                            project.platforms.map((platform) => getPlatformInfo(platform.type))
-                        )}
+                        {@const platforms = filterPlatforms(
+                            (project.platforms ?? []).map((platform) =>
+                                getPlatformInfo(platform.type)
+                            )
+                        )}
♻️ Duplicate comments (2)
src/routes/(console)/organization-[organization]/+page.ts (1)

72-76: Preserve projects contract; don’t override with active-only total.

Overriding projects.total diverges from the SDK response shape and has broken consumers before. Keep projects as returned by the SDK and expose active/archived aggregates separately.

-        projects: {
-            ...activeProjects,
-            projects: activeProjects.projects,
-            total: activeTotal.total
-        },
+        projects: activeProjects, // preserve SDK response as-is

If callers need active total, use the already exposed activeTotalOverall.

src/routes/(console)/organization-[organization]/+page.svelte (1)

116-118: Active filter should include null/undefined (treat anything not archived as active)

Self-hosted often returns no status for active items. Safer filter:

-    $: activeProjects = (data.activeProjectsPage ?? data.projects.projects).filter(
-        (project) => project.status === 'active'
-    );
+    $: activeProjects = (data.activeProjectsPage ?? data.projects.projects).filter(
+        (project) => project.status !== 'archived'
+    );
🧹 Nitpick comments (5)
src/lib/components/pagination.svelte (1)

17-24: Optional: sanitize target page input.

Defensive clamp avoids accidental 0/negative pages if upstream emits bad values.

-        if (page === 1) {
+        const p = Math.max(1, Math.floor(Number(page)));
+        if (p === 1) {
             if (removeOnFirstPage) {
                 url.searchParams.delete(pageParam);
             } else {
-                url.searchParams.set(pageParam, '1');
+                url.searchParams.set(pageParam, '1');
             }
         } else {
-            url.searchParams.set(pageParam, page.toString());
+            url.searchParams.set(pageParam, p.toString());
         }
src/routes/(console)/organization-[organization]/+page.ts (2)

24-59: Reduce payload for total-only queries.

Add a small limit to the total-only requests to avoid fetching full pages unnecessarily.

         sdk.forConsole.projects.list({
             queries: [
                 Query.equal('teamId', params.organization),
-                Query.or([Query.equal('status', 'active'), Query.isNull('status')])
+                Query.or([Query.equal('status', 'active'), Query.isNull('status')]),
+                Query.limit(1)
             ],
             search: search || undefined
         }),
         sdk.forConsole.projects.list({
             queries: [
                 Query.equal('teamId', params.organization),
-                Query.equal('status', 'archived')
+                Query.equal('status', 'archived'),
+                Query.limit(1)
             ],
             search: search || undefined
         })

If Appwrite totals are independent of limit, Query.limit(1) will still return correct total while reducing payload.


61-67: Mutation is fine, but consider immutability.

Setting project.region in-place is convenient but can surprise consumers if they rely on raw SDK objects. Consider mapping to new objects if immutability is preferred project-wide.

src/lib/components/archiveProject.svelte (2)

203-203: Name truncation default

Default 16 is fine; consider centralizing this constant to avoid drift with other views.


404-406: Pagination spacing style

Using :global is acceptable here; if PaginationWithLimit forwards classes, you could also keep it scoped by styling a local wrapper.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 655b053 and 4f4c679.

📒 Files selected for processing (7)
  • src/lib/components/archiveProject.svelte (6 hunks)
  • src/lib/components/limit.svelte (2 hunks)
  • src/lib/components/pagination.svelte (1 hunks)
  • src/lib/components/paginationWithLimit.svelte (1 hunks)
  • src/lib/components/paginator.svelte (2 hunks)
  • src/routes/(console)/organization-[organization]/+page.svelte (3 hunks)
  • src/routes/(console)/organization-[organization]/+page.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/routes/(console)/organization-[organization]/+page.ts (2)
src/lib/helpers/load.ts (1)
  • pageToOffset (4-6)
src/lib/stores/sdk.ts (1)
  • sdk (142-165)
🪛 ESLint
src/lib/components/paginationWithLimit.svelte

[error] 23-23: Unexpected any. Specify a different type.

(@typescript-eslint/no-explicit-any)

src/lib/components/paginator.svelte

[error] 30-30: Unexpected any. Specify a different type.

(@typescript-eslint/no-explicit-any)

⏰ 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: e2e
  • GitHub Check: build
🔇 Additional comments (12)
src/lib/components/limit.svelte (1)

11-12: Good API extension with sensible defaults.

pageParam and removeOnFirstPage props look correct and align with the new archived pagination flow.

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

9-10: Prop plumbing for pageParam/removeOnFirstPage looks good.

Matches Limit/PaginationWithLimit and keeps default behavior intact.

src/lib/components/paginator.svelte (2)

16-18: Rest-props pass-through: OK, but ensure no accidental prop shadowing.

Spreading ...restProps after {gap} allows consumers to override gap if needed; just confirming this is intentional.


38-38: Prop forwarding is correct.

{...restProps} on Layout.Stack unlocks styling/attrs without changing behavior.

src/lib/components/paginationWithLimit.svelte (2)

11-14: New props are wired correctly through to children.

useCreateLink, pageParam, and removeOnFirstPage propagation is consistent with Pagination and Limit.


31-44: Wrapper wiring LGTM.

  • sum={total} matches Pagination API.
  • {pageParam} and {removeOnFirstPage} are correctly passed to both Limit and Pagination.
src/routes/(console)/organization-[organization]/+page.svelte (3)

112-114: Archived filter fix looks correct

Filtering strictly by project.status === 'archived' resolves the earlier misclassification.


120-124: Verify active totals propagate consistently to list and paginator

Using activeTotalOverall for both CardContainer and Pagination is correct. Please confirm server returns this as the full active count (not page size), so pagination math matches limit/offset.

Also applies to: 176-176, 261-261


267-270: Wiring archived pagination props to ArchiveProject

Props passed match the component’s new API.

src/lib/components/archiveProject.svelte (3)

52-55: Prop surface extension is coherent

New props (archivedTotalOverall, archivedOffset, limit) are clearly typed and destructured.

Also applies to: 57-64


2-2: Archived totals + pagination wiring

Import + usage of PaginationWithLimit and switching badges/totals to archivedTotalOverall look good. Please confirm:

  • PaginationWithLimit accepts pageParam and removeOnFirstPage.
  • It forwards $$restProps so class="pagination-container" applies to its root.
  • The loader reads archivedPage and computes archivedOffset accordingly.

Also applies to: 210-210, 217-217, 323-330


306-306: Off-by-one fix for “+N” badge

Condition > 2 is correct.

Comment on lines +28 to +36
if (url.searchParams.has(pageParam)) {
const page = Number(url.searchParams.get(pageParam));
const newPage = Math.floor(((page - 1) * previousLimit) / limit);
url.searchParams.set('page', newPage.toString());
const safePage = Math.max(1, Number.isFinite(newPage) ? newPage : 1);
if (removeOnFirstPage && safePage === 1) {
url.searchParams.delete(pageParam);
} else {
url.searchParams.set(pageParam, safePage.toString());
}
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

Fix off-by-one when recomputing page after limit change.

newPage needs a +1 to convert from 0-based index to 1-based page. Without it, unchanged limits incorrectly drop page N to page N-1, and scaling limits can jump to the wrong page.

Apply:

-        if (url.searchParams.has(pageParam)) {
-            const page = Number(url.searchParams.get(pageParam));
-            const newPage = Math.floor(((page - 1) * previousLimit) / limit);
-            const safePage = Math.max(1, Number.isFinite(newPage) ? newPage : 1);
+        if (url.searchParams.has(pageParam)) {
+            const page = Number(url.searchParams.get(pageParam));
+            const prev = Number.isFinite(previousLimit) && previousLimit > 0 ? previousLimit : limit;
+            const newPage = Math.floor(((page - 1) * prev) / limit) + 1;
+            const safePage = Math.max(1, Number.isFinite(newPage) ? newPage : 1);
             if (removeOnFirstPage && safePage === 1) {
                 url.searchParams.delete(pageParam);
             } else {
                 url.searchParams.set(pageParam, safePage.toString());
             }
         }
📝 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 (url.searchParams.has(pageParam)) {
const page = Number(url.searchParams.get(pageParam));
const newPage = Math.floor(((page - 1) * previousLimit) / limit);
url.searchParams.set('page', newPage.toString());
const safePage = Math.max(1, Number.isFinite(newPage) ? newPage : 1);
if (removeOnFirstPage && safePage === 1) {
url.searchParams.delete(pageParam);
} else {
url.searchParams.set(pageParam, safePage.toString());
}
if (url.searchParams.has(pageParam)) {
const page = Number(url.searchParams.get(pageParam));
const prev = Number.isFinite(previousLimit) && previousLimit > 0 ? previousLimit : limit;
const newPage = Math.floor(((page - 1) * prev) / limit) + 1;
const safePage = Math.max(1, Number.isFinite(newPage) ? newPage : 1);
if (removeOnFirstPage && safePage === 1) {
url.searchParams.delete(pageParam);
} else {
url.searchParams.set(pageParam, safePage.toString());
}
}
🤖 Prompt for AI Agents
In src/lib/components/limit.svelte around lines 28 to 36, the recomputation of
newPage when adjusting limits uses a 0-based index but the app uses 1-based page
numbers; add +1 to the newPage calculation so it converts back to 1-based pages
(i.e., compute newPage = Math.floor(((page - 1) * previousLimit) / limit) + 1),
then keep the existing safePage logic and pageParam removal/setting unchanged.

Comment on lines +24 to +59
const [activeProjects, archivedProjects, activeTotal, archivedTotal] = await Promise.all([
sdk.forConsole.projects.list({
queries: [
Query.offset(offset),
Query.equal('teamId', params.organization),
Query.or([Query.equal('status', 'active'), Query.isNull('status')]),
Query.limit(limit),
Query.orderDesc('')
],
search: search || undefined
}),
sdk.forConsole.projects.list({
queries: [
Query.offset(archivedOffset),
Query.equal('teamId', params.organization),
Query.equal('status', 'archived'),
Query.limit(limit),
Query.orderDesc('')
],
search: search || undefined
}),
sdk.forConsole.projects.list({
queries: [
Query.equal('teamId', params.organization),
Query.or([Query.equal('status', 'active'), Query.isNull('status')])
],
search: search || undefined
}),
sdk.forConsole.projects.list({
queries: [
Query.equal('teamId', params.organization),
Query.equal('status', 'archived')
],
search: search || undefined
})
]);
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Optional: verify Query.orderDesc('') usage.

Empty field name may be ignored or error depending on SDK version. Confirm intended default ordering or set an explicit field (e.g., createdAt/updatedAt).


🌐 Web query:

Is an empty string a valid field for Appwrite `Query.orderDesc('')`? What field should be used to order projects by most recently created in the Appwrite Console SDK?

💡 Result:

No — an empty string is not a valid field for Query.orderDesc(''). To sort by most-recently-created use the created timestamp field (Appwrite exposes $createdAt). Example: Query.orderDesc('$createdAt') — (escape the $ if your language/string parser requires, e.g. '$createdAt' in Flutter). You can also use $sequence for insertion order if preferred. [1][2][3]


Use explicit timestamp field in Query.orderDesc
Replace both instances of Query.orderDesc('') with Query.orderDesc('$createdAt') to ensure projects are sorted by most-recently-created timestamp.

🤖 Prompt for AI Agents
In src/routes/(console)/organization-[organization]/+page.ts around lines 24 to
59, the two calls using Query.orderDesc('') should explicitly sort by creation
timestamp; replace both Query.orderDesc('') occurrences with
Query.orderDesc('$createdAt') so projects are ordered by most-recently-created
items.

HarshMN2345 and others added 2 commits September 15, 2025 14:00
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
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.

3 participants