From 6cda49cbeb5044ce330556750a823ea386f7c689 Mon Sep 17 00:00:00 2001 From: Harsh Mahajan Date: Tue, 24 Mar 2026 18:16:05 +0530 Subject: [PATCH 01/73] feat:blocked projects --- src/lib/layout/shell.svelte | 37 +++-- .../project-[region]-[project]/+layout.svelte | 148 +++++++++++++++++- .../blocked-lock.svg | 18 +++ 3 files changed, 188 insertions(+), 15 deletions(-) create mode 100644 src/routes/(console)/project-[region]-[project]/blocked-lock.svg diff --git a/src/lib/layout/shell.svelte b/src/lib/layout/shell.svelte index 4a2a7bcbf..58c1560d2 100644 --- a/src/lib/layout/shell.svelte +++ b/src/lib/layout/shell.svelte @@ -180,7 +180,7 @@ $: shouldRenderSidebar = !$isNewWizardStatusOpen && showSideNavigation && !$showOnboardingAnimation; $: hasSidebarSpace = shouldRenderSidebar && !$isTabletViewport && !!selectedProject; - + $: isProjectBlocked = selectedProject?.status !== 'paused' && !!selectedProject?.blocks?.length; $: { if ($isSidebarOpen) { yOnMenuOpen = window.scrollY; @@ -211,20 +211,22 @@ {/if} - {#if shouldRenderSidebar} - - {/if} +
+ {#if shouldRenderSidebar} + + {/if} - {#if !$showOnboardingAnimation} - - {/if} + {#if !$showOnboardingAnimation} + + {/if} +
From 7e0df1edb5a1d795a7db7cc40d1f3fd58bcb7471 Mon Sep 17 00:00:00 2001 From: Harsh Mahajan Date: Thu, 26 Mar 2026 14:07:22 +0530 Subject: [PATCH 14/73] fix layout --- .../project-[region]-[project]/+layout.svelte | 78 +++++++------------ 1 file changed, 29 insertions(+), 49 deletions(-) diff --git a/src/routes/(console)/project-[region]-[project]/+layout.svelte b/src/routes/(console)/project-[region]-[project]/+layout.svelte index 73310c29e..9305c9f0a 100644 --- a/src/routes/(console)/project-[region]-[project]/+layout.svelte +++ b/src/routes/(console)/project-[region]-[project]/+layout.svelte @@ -29,13 +29,20 @@ import PausedProjectModal from './pausedProjectModal.svelte'; import type { LayoutData } from './$types'; import { Button } from '$lib/elements/forms'; + import { currentPlan, organizationList } from '$lib/stores/organization'; import { wizard } from '$lib/stores/wizard'; import SupportWizard from '$routes/(console)/supportWizard.svelte'; import BlockedLock from './blocked-lock.svg'; import { isProjectBlocked as getIsProjectBlocked } from '$lib/helpers/project'; + import { Layout, Typography } from '@appwrite.io/pink-svelte'; + import type { Models } from '@appwrite.io/console'; export let data: LayoutData; $: isProjectBlocked = getIsProjectBlocked(data.project); + $: allOrgsHavePremiumSupport = $organizationList.teams.every( + (team) => (team as Models.Organization).billingPlanDetails.premiumSupport + ); + $: hasPremiumSupport = $currentPlan?.premiumSupport ?? allOrgsHavePremiumSupport ?? false; function contactSupport() { wizard.start(SupportWizard); @@ -145,12 +152,22 @@ class="project-layout__lock-icon" />
-
-

Project is currently blocked

-

Access to this project is restricted. Contact support if the issue persists.

+ + + + Project is currently blocked + + + Access to this project is restricted. Contact support if the issue persists. + + - -
+ {#if hasPremiumSupport} + + {:else} + + {/if} + {/if} @@ -192,13 +209,9 @@ align-items: center; justify-content: center; flex-direction: column; - gap: 1rem; + gap: 0.5rem; padding: 2rem; text-align: center; - pointer-events: none; - } - - .project-layout__overlay > * { pointer-events: auto; } @@ -209,15 +222,15 @@ } .project-layout__lock { - width: 3.5rem; - height: 3.5rem; + width: 2.75rem; + height: 2.75rem; display: flex; align-items: center; justify-content: center; - background: color-mix(in srgb, var(--bgcolor-neutral-primary, #ffffff) 92%, transparent); - border: 1px solid color-mix(in srgb, var(--border-neutral, #d7d7db) 70%, transparent); - border-radius: 1rem; - box-shadow: 0 12px 32px rgba(17, 24, 39, 0.08); + background: color-mix(in srgb, var(--bgcolor-neutral-primary, #ffffff) 96%, transparent); + border: 1px solid color-mix(in srgb, #fb4f7c 22%, var(--border-neutral, #d7d7db)); + border-radius: 0.875rem; + box-shadow: 0 8px 24px rgba(17, 24, 39, 0.06); } .project-layout__lock-icon { @@ -228,31 +241,6 @@ display: block; } - .project-layout__dialog { - max-width: 30rem; - display: flex; - flex-direction: column; - align-items: center; - gap: 0.75rem; - } - - .project-layout__dialog h2 { - margin: 0; - color: var(--fgcolor-neutral-primary, #2d2d31); - font-size: 2rem; - line-height: 1.05; - letter-spacing: -0.02em; - font-weight: 500; - } - - .project-layout__dialog p { - margin: 0; - max-width: 24rem; - color: var(--fgcolor-neutral-secondary, #56565c); - font-size: 1rem; - line-height: 1.4; - } - :global(.project-layout__dialog .button) { min-width: 10rem; min-height: 2.75rem; @@ -286,14 +274,6 @@ padding: 1.5rem; } - .project-layout__dialog h2 { - font-size: 1.5rem; - } - - .project-layout__dialog p { - font-size: 0.95rem; - } - .project-layout__lock { width: 3rem; height: 3rem; From dc06bf63aaac6c92107a6a0a7b1ec644e95cdd6a Mon Sep 17 00:00:00 2001 From: Harsh Mahajan Date: Thu, 26 Mar 2026 14:56:17 +0530 Subject: [PATCH 15/73] fixL global issues --- .../project-[region]-[project]/+layout.svelte | 30 +++++-------------- 1 file changed, 8 insertions(+), 22 deletions(-) diff --git a/src/routes/(console)/project-[region]-[project]/+layout.svelte b/src/routes/(console)/project-[region]-[project]/+layout.svelte index 9305c9f0a..e8490f1f7 100644 --- a/src/routes/(console)/project-[region]-[project]/+layout.svelte +++ b/src/routes/(console)/project-[region]-[project]/+layout.svelte @@ -152,7 +152,7 @@ class="project-layout__lock-icon" /> - + Project is currently blocked @@ -162,11 +162,13 @@ - {#if hasPremiumSupport} - - {:else} - - {/if} +
+ {#if hasPremiumSupport} + + {:else} + + {/if} +
{/if} @@ -241,22 +243,6 @@ display: block; } - :global(.project-layout__dialog .button) { - min-width: 10rem; - min-height: 2.75rem; - padding-inline: 1rem; - border-radius: 0.9rem; - background: color-mix(in srgb, var(--bgcolor-neutral-primary, #ffffff) 92%, transparent); - border: 1px solid color-mix(in srgb, var(--border-neutral, #d7d7db) 75%, transparent); - box-shadow: 0 2px 8px rgba(17, 24, 39, 0.04); - } - - :global(.project-layout__dialog .button .text) { - color: var(--fgcolor-neutral-secondary, #56565c); - font-size: 1rem; - font-weight: 500; - } - .layout-level-progress-bars { gap: 1rem; z-index: 100; From bb309de3096abd68774ceca00ff51271bd751995 Mon Sep 17 00:00:00 2001 From: Harsh Mahajan Date: Thu, 26 Mar 2026 16:23:51 +0530 Subject: [PATCH 16/73] fix paused project modal --- .../project-[region]-[project]/pausedProjectModal.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/(console)/project-[region]-[project]/pausedProjectModal.svelte b/src/routes/(console)/project-[region]-[project]/pausedProjectModal.svelte index 9e698b023..8c5fd1f17 100644 --- a/src/routes/(console)/project-[region]-[project]/pausedProjectModal.svelte +++ b/src/routes/(console)/project-[region]-[project]/pausedProjectModal.svelte @@ -75,7 +75,7 @@ } - + This project has been paused due to inactivity. From 87619eed7bf66737142cfa6fea334a26037cd706 Mon Sep 17 00:00:00 2001 From: Harsh Mahajan Date: Thu, 26 Mar 2026 16:42:17 +0530 Subject: [PATCH 17/73] fix: disbaled shortcuts whne blocked --- src/lib/commandCenter/commandCenter.svelte | 3 +-- src/lib/components/navbar.svelte | 1 + src/routes/(console)/project-[region]-[project]/+layout.svelte | 3 ++- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/lib/commandCenter/commandCenter.svelte b/src/lib/commandCenter/commandCenter.svelte index 91bf5dfe9..ae55da982 100644 --- a/src/lib/commandCenter/commandCenter.svelte +++ b/src/lib/commandCenter/commandCenter.svelte @@ -46,8 +46,7 @@ { callback: toggleCommandCenter, keys: ['k'], - ctrl: true, - forceEnable: true + ctrl: true }, { label: 'Toggle debug overlay', diff --git a/src/lib/components/navbar.svelte b/src/lib/components/navbar.svelte index bc1d6a7a1..ce5b64a2b 100644 --- a/src/lib/components/navbar.svelte +++ b/src/lib/components/navbar.svelte @@ -203,6 +203,7 @@ diff --git a/src/routes/(console)/project-[region]-[project]/+layout.svelte b/src/routes/(console)/project-[region]-[project]/+layout.svelte index e8490f1f7..e49558879 100644 --- a/src/routes/(console)/project-[region]-[project]/+layout.svelte +++ b/src/routes/(console)/project-[region]-[project]/+layout.svelte @@ -5,7 +5,7 @@ import { project, stats } from './store'; import { goto } from '$app/navigation'; - import { registerCommands, registerSearchers } from '$lib/commandCenter'; + import { disableCommands, registerCommands, registerSearchers } from '$lib/commandCenter'; import { bucketSearcher, @@ -39,6 +39,7 @@ export let data: LayoutData; $: isProjectBlocked = getIsProjectBlocked(data.project); + $: $disableCommands(isProjectBlocked); $: allOrgsHavePremiumSupport = $organizationList.teams.every( (team) => (team as Models.Organization).billingPlanDetails.premiumSupport ); From 06adf0a41fa5d8e39d44c03f4d4c04f138bb3c5f Mon Sep 17 00:00:00 2001 From: Harsh Mahajan Date: Thu, 26 Mar 2026 17:00:27 +0530 Subject: [PATCH 18/73] added iscloud check for safety --- .../(console)/project-[region]-[project]/+layout.svelte | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/routes/(console)/project-[region]-[project]/+layout.svelte b/src/routes/(console)/project-[region]-[project]/+layout.svelte index e49558879..4f358189d 100644 --- a/src/routes/(console)/project-[region]-[project]/+layout.svelte +++ b/src/routes/(console)/project-[region]-[project]/+layout.svelte @@ -40,9 +40,11 @@ export let data: LayoutData; $: isProjectBlocked = getIsProjectBlocked(data.project); $: $disableCommands(isProjectBlocked); - $: allOrgsHavePremiumSupport = $organizationList.teams.every( - (team) => (team as Models.Organization).billingPlanDetails.premiumSupport - ); + $: allOrgsHavePremiumSupport = + isCloud && + ($organizationList?.teams ?? []).every( + (team) => (team as Models.Organization).billingPlanDetails?.premiumSupport === true + ); $: hasPremiumSupport = $currentPlan?.premiumSupport ?? allOrgsHavePremiumSupport ?? false; function contactSupport() { From 9a02248df10006ecd8c0abe59bf3672cacfc9554 Mon Sep 17 00:00:00 2001 From: Harsh Mahajan Date: Fri, 27 Mar 2026 11:34:29 +0530 Subject: [PATCH 19/73] fix: upload inv ux and pagination --- src/lib/components/cardGrid.svelte | 30 +++++++++++-- .../variables/importVariablesModal.svelte | 44 ++++++++++++------- .../settings/updateInstallations.svelte | 17 ++++++- .../uploadVariablesModal.svelte | 40 +++++++++++++---- 4 files changed, 101 insertions(+), 30 deletions(-) diff --git a/src/lib/components/cardGrid.svelte b/src/lib/components/cardGrid.svelte index d378a9c66..c45d112fe 100644 --- a/src/lib/components/cardGrid.svelte +++ b/src/lib/components/cardGrid.svelte @@ -8,8 +8,8 @@ - - +
+ {#if $$slots.default} @@ -17,12 +17,12 @@ {/if} -
+
- +
{#if $$slots.actions && !hideFooter} @@ -34,3 +34,25 @@ {/if} + + diff --git a/src/lib/components/variables/importVariablesModal.svelte b/src/lib/components/variables/importVariablesModal.svelte index db776b05d..9368d4d4f 100644 --- a/src/lib/components/variables/importVariablesModal.svelte +++ b/src/lib/components/variables/importVariablesModal.svelte @@ -6,6 +6,7 @@ import { IconInfo } from '@appwrite.io/pink-icons-svelte'; import { Icon, Layout, Selector, Tooltip, Typography, Upload } from '@appwrite.io/pink-svelte'; import { parse } from '$lib/helpers/envfile'; + import { removeFile } from '$lib/helpers/files'; export let show = false; export let variables: Partial[]; @@ -13,6 +14,15 @@ let files: FileList; let error: string; let secret = false; + $: filesList = files?.length + ? Array.from(files).map((file) => ({ + ...file, + name: file.name, + size: file.size, + extension: file.type, + removable: true + })) + : []; async function handleSubmit() { try { @@ -52,28 +62,32 @@ - - + + Drag and drop files here or click to upload - - - - + + + + Only .env files allowed + - - Only .env files allowed - - + - + Up to 100 variables allowed + {#if files?.length} + (files = removeFile(e.detail, files))} /> + {/if} {#if variables?.length > 0} This action can create and update variables but can not delete them. diff --git a/src/routes/(console)/project-[region]-[project]/settings/updateInstallations.svelte b/src/routes/(console)/project-[region]-[project]/settings/updateInstallations.svelte index 8cb2cb9f8..5d61cf6a4 100644 --- a/src/routes/(console)/project-[region]-[project]/settings/updateInstallations.svelte +++ b/src/routes/(console)/project-[region]-[project]/settings/updateInstallations.svelte @@ -84,7 +84,7 @@ {#if total > 0} - +
Add installation - +
+ + diff --git a/src/routes/(console)/project-[region]-[project]/uploadVariablesModal.svelte b/src/routes/(console)/project-[region]-[project]/uploadVariablesModal.svelte index 93ef85bb9..5cf19fe9d 100644 --- a/src/routes/(console)/project-[region]-[project]/uploadVariablesModal.svelte +++ b/src/routes/(console)/project-[region]-[project]/uploadVariablesModal.svelte @@ -15,6 +15,7 @@ Upload } from '@appwrite.io/pink-svelte'; import { parse } from '$lib/helpers/envfile'; + import { removeFile } from '$lib/helpers/files'; export let show = false; export let variableList: Models.VariableList; @@ -33,6 +34,15 @@ let files: FileList; let secret = false; let error: string; + $: filesList = files?.length + ? Array.from(files).map((file) => ({ + ...file, + name: file.name, + size: file.size, + extension: file.type, + removable: true + })) + : []; async function handleSubmit() { try { @@ -87,21 +97,33 @@ - - + + Drag and drop files here or click to upload - - - - + + + + Only .env files allowed + - Only .env files allowed - + - Up to 100 variables allowed + + Up to 100 variables allowed + + {#if files?.length} + (files = removeFile(e.detail, files))} /> + {/if} {#if variableList.total > 0} From efd7cdb6e2c1b03185ffb8ac11974cf634ba1df3 Mon Sep 17 00:00:00 2001 From: Harsh Mahajan Date: Fri, 27 Mar 2026 11:39:55 +0530 Subject: [PATCH 20/73] fix: title --- .../project-[region]-[project]/createVariableModal.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/(console)/project-[region]-[project]/createVariableModal.svelte b/src/routes/(console)/project-[region]-[project]/createVariableModal.svelte index 0cf5fa213..13f9544d2 100644 --- a/src/routes/(console)/project-[region]-[project]/createVariableModal.svelte +++ b/src/routes/(console)/project-[region]-[project]/createVariableModal.svelte @@ -49,7 +49,7 @@ + title={`Create ${isGlobal ? 'global' : 'environment'} variables`}> Set the environment variables or secret keys that will be passed to {!isGlobal From 141121f3f13f6022cadd407e2beac53e23ccaa9f Mon Sep 17 00:00:00 2001 From: Harsh Mahajan Date: Fri, 27 Mar 2026 12:17:30 +0530 Subject: [PATCH 21/73] fixed table in mobile view --- .../variables/environmentVariables.svelte | 23 +++++++++++------ .../updateVariables.svelte | 25 ++++++++++++------- 2 files changed, 32 insertions(+), 16 deletions(-) diff --git a/src/lib/components/variables/environmentVariables.svelte b/src/lib/components/variables/environmentVariables.svelte index 8b4edb8c3..0e6cb6ec9 100644 --- a/src/lib/components/variables/environmentVariables.svelte +++ b/src/lib/components/variables/environmentVariables.svelte @@ -31,6 +31,7 @@ import DeleteVariableModal from './deleteVariableModal.svelte'; import UpdateVariableModal from './updateVariableModal.svelte'; import { Click, trackEvent } from '$lib/actions/analytics'; + import { isSmallViewport } from '$lib/stores/viewport'; const DOCS_LINKS: Record = { site: 'https://appwrite.io/docs/products/sites/develop#accessing-environment-variables', @@ -62,19 +63,27 @@ const createSource = $derived(analyticsCreateSource || analyticsSource); const docsLink = $derived(DOCS_LINKS[productLabel]); - const tableColumns = [ - { id: 'key', width: { min: 300 } }, - { id: 'value', width: { min: 280 } }, - { id: 'actions', width: 40 } - ]; + const tableColumns = $derived( + $isSmallViewport + ? [ + { id: 'key', width: { min: 360 } }, + { id: 'value', width: { min: 220 } }, + { id: 'actions', width: 40 } + ] + : [ + { id: 'key', width: { min: 300 } }, + { id: 'value', width: { min: 280 } }, + { id: 'actions', width: 40 } + ] + ); Set up environment variables to securely manage keys and settings for your project. - - + +
- {#if progressCard} + {#if progressCard && project} (showHeader = !value)); - page.subscribe(({ url }) => { - $showSubNavigation = url.searchParams.get('openNavbar') === 'true'; + $: { + const url = page.url; + showSubNavigation.set(url.searchParams.get('openNavbar') === 'true'); clearTimeout(timeoutId); if (url.pathname.includes('project-')) { @@ -156,7 +151,7 @@ } else { showContentTransition = false; } - }); + } // reactive blocks $: sideSize = $hasSubNavigation ? ($isNarrow ? '17rem' : '25rem') : '12.5rem'; @@ -196,7 +191,7 @@ $: state = $isSidebarOpen ? 'open' : 'closed'; - $: subNavigation = $page.data.subNavigation; + $: subNavigation = page.data.subNavigation; $: shouldRenderSidebar = !$isNewWizardStatusOpen && showSideNavigation && !$showOnboardingAnimation; @@ -257,9 +252,9 @@ class:icons-content={state === 'icons' && activeProject} class:no-sidebar={!hasSidebarSpace}>
- {#if $page.data?.header} + {#if page.data?.header}
- +
{/if} From c6a0726e89d0cc2531cf49ab52fa777365488fe3 Mon Sep 17 00:00:00 2001 From: Harsh Mahajan <127186841+HarshMN2345@users.noreply.github.com> Date: Fri, 27 Mar 2026 14:30:20 +0530 Subject: [PATCH 26/73] Apply suggestions from code review Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> --- src/routes/(console)/+layout.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/(console)/+layout.svelte b/src/routes/(console)/+layout.svelte index 4b61cfb23..dfda1d5f9 100644 --- a/src/routes/(console)/+layout.svelte +++ b/src/routes/(console)/+layout.svelte @@ -342,7 +342,7 @@ -{#if $feedback.show} +{#if $feedback.show && $isSmallViewport} {/if} From d8f43d51f0d001af76e153a34b9adc0d01617806 Mon Sep 17 00:00:00 2001 From: Harsh Mahajan Date: Fri, 27 Mar 2026 14:40:34 +0530 Subject: [PATCH 27/73] fix: svelte errors --- src/routes/(console)/+layout.svelte | 1 + 1 file changed, 1 insertion(+) diff --git a/src/routes/(console)/+layout.svelte b/src/routes/(console)/+layout.svelte index dfda1d5f9..47a5c0689 100644 --- a/src/routes/(console)/+layout.svelte +++ b/src/routes/(console)/+layout.svelte @@ -44,6 +44,7 @@ import { UsageRates } from '$lib/components/billing'; import { canSeeProjects } from '$lib/stores/roles'; import { BottomModalAlert } from '$lib/components'; + import { isSmallViewport } from '$lib/stores/viewport'; import { IconAnnotation, IconBookOpen, From 3f8ca05f7ea947fe8d4f98900afe1aa561d1eef7 Mon Sep 17 00:00:00 2001 From: Harsh Mahajan Date: Fri, 27 Mar 2026 16:33:18 +0530 Subject: [PATCH 28/73] feat:env var pagination --- .../project-[region]-[project]/+layout.svelte | 2 + .../rawVariableEditor.svelte | 229 +++++++++++++++--- .../settings/+page.svelte | 4 + .../settings/+page.ts | 34 ++- .../updateVariables.svelte | 70 +++++- .../uploadVariablesModal.svelte | 79 ++++-- .../variablesImportBox.svelte | 193 +++++++++++++++ .../variablesOperation.ts | 40 +++ 8 files changed, 588 insertions(+), 63 deletions(-) create mode 100644 src/routes/(console)/project-[region]-[project]/variablesImportBox.svelte create mode 100644 src/routes/(console)/project-[region]-[project]/variablesOperation.ts diff --git a/src/routes/(console)/project-[region]-[project]/+layout.svelte b/src/routes/(console)/project-[region]-[project]/+layout.svelte index 4f358189d..0f49a7a36 100644 --- a/src/routes/(console)/project-[region]-[project]/+layout.svelte +++ b/src/routes/(console)/project-[region]-[project]/+layout.svelte @@ -1,5 +1,6 @@ @@ -138,10 +255,10 @@

- (tab = 'env')} active={tab === 'env'}> + changeTab('env')} active={tab === 'env'}> ENV - (tab = 'json')} active={tab === 'json'}> + changeTab('json')} active={tab === 'json'}> JSON @@ -151,23 +268,53 @@ {:else if tab === 'json'} {/if} + {#if totalEntries > EDITOR_PAGE_LIMIT} + +

+ Variables {pageOffset + 1}-{Math.min( + pageOffset + EDITOR_PAGE_LIMIT, + totalEntries + )} of {totalEntries} +

+ + + + +
+ {/if} - + - + +
diff --git a/src/routes/(console)/project-[region]-[project]/settings/+page.svelte b/src/routes/(console)/project-[region]-[project]/settings/+page.svelte index 2738caf61..a49c3fbfc 100644 --- a/src/routes/(console)/project-[region]-[project]/settings/+page.svelte +++ b/src/routes/(console)/project-[region]-[project]/settings/+page.svelte @@ -97,6 +97,10 @@ {sdkDeleteVariable} isGlobal variableList={data.variables} + allVariableList={data.allVariables} + backendPagination + variablesOffset={data.variablesOffset} + variablesLimit={data.limit} project={data.project} analyticsSource="project_settings" /> diff --git a/src/routes/(console)/project-[region]-[project]/settings/+page.ts b/src/routes/(console)/project-[region]-[project]/settings/+page.ts index 33685ac28..31f719abe 100644 --- a/src/routes/(console)/project-[region]-[project]/settings/+page.ts +++ b/src/routes/(console)/project-[region]-[project]/settings/+page.ts @@ -8,10 +8,36 @@ export const load: PageLoad = async ({ depends, url, params }) => { depends(Dependencies.PROJECT_INSTALLATIONS); const limit = PAGE_LIMIT; const offset = Number(url.searchParams.get('offset') ?? 0); + const variablesOffset = Number(url.searchParams.get('variablesOffset') ?? 0); + const projectSdk = sdk.forProject(params.region, params.project); - const [variables, installations] = await Promise.all([ - sdk.forProject(params.region, params.project).projectApi.listVariables(), - sdk.forProject(params.region, params.project).vcs.listInstallations({ + async function listAllVariables() { + const allVariables = []; + let nextOffset = 0; + let total = 0; + + do { + const response = await projectSdk.projectApi.listVariables({ + queries: [Query.limit(limit), Query.offset(nextOffset)] + }); + + allVariables.push(...response.variables); + total = response.total; + nextOffset += response.variables.length; + } while (nextOffset < total); + + return { + total, + variables: allVariables + }; + } + + const [variables, allVariables, installations] = await Promise.all([ + projectSdk.projectApi.listVariables({ + queries: [Query.limit(limit), Query.offset(variablesOffset)] + }), + listAllVariables(), + projectSdk.vcs.listInstallations({ queries: [Query.limit(limit), Query.offset(offset)] }) ]); @@ -19,7 +45,9 @@ export const load: PageLoad = async ({ depends, url, params }) => { return { limit, offset, + variablesOffset, variables, + allVariables, installations }; }; diff --git a/src/routes/(console)/project-[region]-[project]/updateVariables.svelte b/src/routes/(console)/project-[region]-[project]/updateVariables.svelte index a2052f86a..168aad860 100644 --- a/src/routes/(console)/project-[region]-[project]/updateVariables.svelte +++ b/src/routes/(console)/project-[region]-[project]/updateVariables.svelte @@ -4,6 +4,7 @@ import { Button } from '$lib/elements/forms'; import { CardGrid, Empty, Output, PaginationInline } from '$lib/components'; import UploadVariables from './uploadVariablesModal.svelte'; + import { variablesOperation, type VariablesOperationItem } from './variablesOperation'; import { goto, invalidate } from '$app/navigation'; import { Click, Submit, trackError, trackEvent } from '$lib/actions/analytics'; import { Dependencies } from '$lib/constants'; @@ -43,6 +44,7 @@ export let project: Models.Project; export let variableList: Models.VariableList; + export let allVariableList: Models.VariableList | undefined = undefined; export let globalVariableList: Models.VariableList | undefined = undefined; export let analyticsSource = ''; export let isGlobal: boolean; @@ -59,6 +61,9 @@ ) => Promise; export let sdkDeleteVariable: (variableId: string) => Promise; export let product: 'function' | 'site' = 'function'; + export let backendPagination = false; + export let variablesOffset = 0; + export let variablesLimit = 10; let selectedVar: Models.Variable = null; let showVariablesUpload = false; @@ -71,6 +76,9 @@ let deleteError: string; let offset = 0; const limit = 10; + function handleVariablesImportStatus(detail: VariablesOperationItem) { + variablesOperation.set(detail); + } async function handleVariableCreated(event: CustomEvent) { const variables = event.detail; @@ -140,10 +148,25 @@ } async function handleVariableDeleted() { + const deleteId = selectedVar.$id; + try { + variablesOperation.set({ + id: deleteId, + count: 1, + mode: 'delete', + status: 'deleting' + }); + await sdkDeleteVariable(selectedVar.$id); showDeleteModal = false; selectedVar = null; + variablesOperation.set({ + id: deleteId, + count: 1, + mode: 'delete', + status: 'completed' + }); addNotification({ type: 'success', message: `${project.name} ${ @@ -153,6 +176,13 @@ trackEvent(Submit.VariableDelete); } catch (error) { deleteError = error.message; + variablesOperation.set({ + id: deleteId, + count: 1, + mode: 'delete', + status: 'failed', + error: error.message + }); trackError(error, Submit.VariableDelete); } } @@ -260,8 +290,13 @@ }) : []; + $: editorVariableList = allVariableList ?? variableList; + $: displayedVariables = backendPagination + ? variableList.variables + : variableList.variables.slice(offset, offset + limit); + $: hasConflictOnPage = globalVariableList - ? variableList.variables.slice(offset, offset + limit).filter((variable) => { + ? displayedVariables.filter((variable) => { return globalVariableList.variables.find((globalVariable) => { return variable.key === globalVariable.key; }); @@ -279,6 +314,17 @@ { id: 'value', width: { min: 200, max: 400 } }, { id: 'actions', width: 50 } ]; + + async function handleVariablesPageChange() { + const nextUrl = new URL(page.url); + + nextUrl.searchParams.set('variablesOffset', String(variablesOffset)); + + await goto(nextUrl, { + keepFocus: true, + noScroll: true + }); + } @@ -364,7 +410,7 @@ Value - {#each variableList.variables.slice(offset, offset + limit) as variable} + {#each displayedVariables as variable} {@const isConflicting = globalVariableList @@ -461,10 +507,19 @@ {/each} - {#if sum > limit} + {#if sum > (backendPagination ? variablesLimit : limit)}

Total variables: {sum}

- + {#if backendPagination} + + {:else} + + {/if}
{/if} @@ -504,7 +559,7 @@ {sdkCreateVariable} {sdkUpdateVariable} {sdkDeleteVariable} - {variableList} + variableList={editorVariableList} bind:showEditor={showEditorModal} /> {/if} @@ -524,8 +579,9 @@ + variableList={editorVariableList} + bind:show={showVariablesUpload} + onStatusChange={handleVariablesImportStatus} /> {/if} {#if showDeleteModal} diff --git a/src/routes/(console)/project-[region]-[project]/uploadVariablesModal.svelte b/src/routes/(console)/project-[region]-[project]/uploadVariablesModal.svelte index 5cf19fe9d..edbb7613c 100644 --- a/src/routes/(console)/project-[region]-[project]/uploadVariablesModal.svelte +++ b/src/routes/(console)/project-[region]-[project]/uploadVariablesModal.svelte @@ -16,6 +16,7 @@ } from '@appwrite.io/pink-svelte'; import { parse } from '$lib/helpers/envfile'; import { removeFile } from '$lib/helpers/files'; + import type { VariablesOperationItem } from './variablesOperation'; export let show = false; export let variableList: Models.VariableList; @@ -30,10 +31,12 @@ value: string, secret?: boolean ) => Promise; + export let onStatusChange: (detail: VariablesOperationItem) => void = () => {}; let files: FileList; let secret = false; let error: string; + let isSubmitting = false; $: filesList = files?.length ? Array.from(files).map((file) => ({ ...file, @@ -45,6 +48,9 @@ : []; async function handleSubmit() { + let importId = ''; + let uploadCount = 0; + try { if (!files?.length) { throw new Error('No file selected'); @@ -64,27 +70,70 @@ } } + const filteredEntries = entries.filter(([, value]) => !!value); + + if (!filteredEntries.length) { + throw new Error('No variables found'); + } + + if (filteredEntries.length > 100) { + throw new Error('Please upload a file with fewer than 100 environment variables.'); + } + + importId = crypto.randomUUID(); + uploadCount = filteredEntries.length; + isSubmitting = true; + + onStatusChange({ + id: importId, + count: uploadCount, + mode: 'import', + status: 'uploading' + }); + + show = false; + await Promise.all( - entries - .filter(([, value]) => !!value) - .map(([key, value]) => { - const found = variableList.variables.find( - (variable) => variable.key === key - ); - return found - ? sdkUpdateVariable(found.$id, key, value, secret) - : sdkCreateVariable(key, value, secret); - }) + filteredEntries.map(([key, value]) => { + const found = variableList.variables.find((variable) => variable.key === key); + return found + ? sdkUpdateVariable(found.$id, key, value, secret) + : sdkCreateVariable(key, value, secret); + }) ); + onStatusChange({ + id: importId, + count: uploadCount, + mode: 'import', + status: 'completed' + }); + addNotification({ type: 'success', message: `Variables have been uploaded.` }); - - show = false; } catch (e) { error = e.message; + + if (importId) { + onStatusChange({ + id: importId, + count: uploadCount, + mode: 'import', + status: 'failed', + error + }); + } + + if (!show) { + addNotification({ + type: 'error', + message: error + }); + } + } finally { + isSubmitting = false; } } @@ -138,7 +187,9 @@ bind:checked={secret} description="If selected, you and your team won't be able to read the values after creation." /> - - + + diff --git a/src/routes/(console)/project-[region]-[project]/variablesImportBox.svelte b/src/routes/(console)/project-[region]-[project]/variablesImportBox.svelte new file mode 100644 index 000000000..8314b7fa4 --- /dev/null +++ b/src/routes/(console)/project-[region]-[project]/variablesImportBox.svelte @@ -0,0 +1,193 @@ + + +{#if currentItem} + +
+
+

+ + {currentItem.mode === 'delete' + ? 'Deleting variables' + : 'Importing variables'} + +

+ + +
+ +
+
+
    +
  • +
    +
    + {text(currentItem)} +
    +
    +
    + {#if currentItem.status === 'failed'} + + + + There was an issue + {currentItem.mode === 'delete' + ? ' deleting' + : ' importing'} + variables. + {#if currentItem.error} + (showDetails = true)}> + View details + + {/if} + + + {/if} +
    +
  • +
+ {#if $variablesOperation.length > 1} + + + {offset + 1} of {$variablesOperation.length} + + + + {/if} +
+
+
+
+{/if} + + + + + + + + diff --git a/src/routes/(console)/project-[region]-[project]/variablesOperation.ts b/src/routes/(console)/project-[region]-[project]/variablesOperation.ts new file mode 100644 index 000000000..2f45df2a5 --- /dev/null +++ b/src/routes/(console)/project-[region]-[project]/variablesOperation.ts @@ -0,0 +1,40 @@ +import { writable } from 'svelte/store'; + +export type VariablesOperationItem = { + id: string; + count: number; + mode: 'import' | 'delete'; + status: 'uploading' | 'deleting' | 'completed' | 'failed'; + error?: string; +}; + +function createVariablesOperation() { + const { subscribe, update, set } = writable([]); + + return { + subscribe, + set: (item: VariablesOperationItem) => + update((items) => { + const index = items.findIndex((current) => current.id === item.id); + + if (index !== -1) { + const next = [...items]; + next[index] = item; + return next; + } + + return [item, ...items]; + }), + clear: (id?: string) => + update((items) => { + if (!id) { + return []; + } + + return items.filter((item) => item.id !== id); + }), + reset: () => set([]) + }; +} + +export const variablesOperation = createVariablesOperation(); From 384dd8b8d61c16e480ba748cc54a932e8b4b2ad9 Mon Sep 17 00:00:00 2001 From: Harsh Mahajan Date: Fri, 27 Mar 2026 16:47:25 +0530 Subject: [PATCH 29/73] fix: repo card --- src/lib/components/git/repositoryCard.svelte | 48 +++++++++++++------- 1 file changed, 32 insertions(+), 16 deletions(-) diff --git a/src/lib/components/git/repositoryCard.svelte b/src/lib/components/git/repositoryCard.svelte index cfcc28df9..0c64432c5 100644 --- a/src/lib/components/git/repositoryCard.svelte +++ b/src/lib/components/git/repositoryCard.svelte @@ -13,34 +13,50 @@ - - + + - + - {repository.name} + {repository.name} - + - Last updated: {toLocaleDateTime(repository.pushedAt)} + + Last updated: {toLocaleDateTime(repository.pushedAt)} + + + + • - - - {repository.organization} + href={`https://github.com/${repository.organization}`}> + {repository.organization} + + + From 3912824d95711c90e72da29e671b6fc167fafdff Mon Sep 17 00:00:00 2001 From: Harsh Mahajan Date: Fri, 27 Mar 2026 17:17:54 +0530 Subject: [PATCH 30/73] The stale reopen bug fix --- .../settings/+page.svelte | 1 - .../settings/+page.ts | 26 +------- .../updateVariables.svelte | 61 +++++++++++++++++-- 3 files changed, 58 insertions(+), 30 deletions(-) diff --git a/src/routes/(console)/project-[region]-[project]/settings/+page.svelte b/src/routes/(console)/project-[region]-[project]/settings/+page.svelte index a49c3fbfc..bed3bb541 100644 --- a/src/routes/(console)/project-[region]-[project]/settings/+page.svelte +++ b/src/routes/(console)/project-[region]-[project]/settings/+page.svelte @@ -97,7 +97,6 @@ {sdkDeleteVariable} isGlobal variableList={data.variables} - allVariableList={data.allVariables} backendPagination variablesOffset={data.variablesOffset} variablesLimit={data.limit} diff --git a/src/routes/(console)/project-[region]-[project]/settings/+page.ts b/src/routes/(console)/project-[region]-[project]/settings/+page.ts index 31f719abe..321f06ada 100644 --- a/src/routes/(console)/project-[region]-[project]/settings/+page.ts +++ b/src/routes/(console)/project-[region]-[project]/settings/+page.ts @@ -10,33 +10,10 @@ export const load: PageLoad = async ({ depends, url, params }) => { const offset = Number(url.searchParams.get('offset') ?? 0); const variablesOffset = Number(url.searchParams.get('variablesOffset') ?? 0); const projectSdk = sdk.forProject(params.region, params.project); - - async function listAllVariables() { - const allVariables = []; - let nextOffset = 0; - let total = 0; - - do { - const response = await projectSdk.projectApi.listVariables({ - queries: [Query.limit(limit), Query.offset(nextOffset)] - }); - - allVariables.push(...response.variables); - total = response.total; - nextOffset += response.variables.length; - } while (nextOffset < total); - - return { - total, - variables: allVariables - }; - } - - const [variables, allVariables, installations] = await Promise.all([ + const [variables, installations] = await Promise.all([ projectSdk.projectApi.listVariables({ queries: [Query.limit(limit), Query.offset(variablesOffset)] }), - listAllVariables(), projectSdk.vcs.listInstallations({ queries: [Query.limit(limit), Query.offset(offset)] }) @@ -47,7 +24,6 @@ export const load: PageLoad = async ({ depends, url, params }) => { offset, variablesOffset, variables, - allVariables, installations }; }; diff --git a/src/routes/(console)/project-[region]-[project]/updateVariables.svelte b/src/routes/(console)/project-[region]-[project]/updateVariables.svelte index 168aad860..6ef2bc04c 100644 --- a/src/routes/(console)/project-[region]-[project]/updateVariables.svelte +++ b/src/routes/(console)/project-[region]-[project]/updateVariables.svelte @@ -1,6 +1,6 @@ + +{#if $organization?.$id && !dismissed} + + + Starting April 22nd, realtime usage (connections, messages, and bandwidth) will be + charged based on your plan's rates. Review your usage to avoid unexpected charges. + + + + + +{/if} diff --git a/src/lib/components/billing/usageRates.svelte b/src/lib/components/billing/usageRates.svelte index 0db4ac04e..b1a4774b2 100644 --- a/src/lib/components/billing/usageRates.svelte +++ b/src/lib/components/billing/usageRates.svelte @@ -67,8 +67,7 @@ {/each} {#each Object.entries(org.billingPlanDetails.usage) as [key, usage]} {@const limit = getPlanLimit(key)} - {@const show = limit !== false} - {#if show} + {#if limit !== false} {usage.name} @@ -82,6 +81,18 @@ {/if} + {:else if usage.price > 0} + + {usage.name} + Pay-as-you-go + {#if !isFree} + + {formatCurrency(usage.price)}/{abbreviateNumber( + usage.value + )}{usage.unit} + + {/if} + {/if} {/each} diff --git a/src/lib/stores/billing.ts b/src/lib/stores/billing.ts index 50e3a3071..1589dec05 100644 --- a/src/lib/stores/billing.ts +++ b/src/lib/stores/billing.ts @@ -191,6 +191,7 @@ export type PlanServices = | 'platforms' | 'realtime' | 'realtimeAddon' + | 'realtimeMessages' | 'storage' | 'storageAddon' | 'teams' @@ -274,6 +275,7 @@ export function checkForUsageFees(plan: string, id: PlanServices) { case 'users': case 'executions': case 'realtime': + case 'realtimeMessages': return true; default: diff --git a/src/routes/(console)/organization-[organization]/+layout.ts b/src/routes/(console)/organization-[organization]/+layout.ts index 692f1b37e..a4e7a401f 100644 --- a/src/routes/(console)/organization-[organization]/+layout.ts +++ b/src/routes/(console)/organization-[organization]/+layout.ts @@ -8,6 +8,7 @@ import Breadcrumbs from './breadcrumbs.svelte'; import Header from './header.svelte'; import { headerAlert } from '$lib/stores/headerAlert'; import ProjectsAtRisk from '$lib/components/billing/alerts/projectsAtRisk.svelte'; +import RealtimePricing from '$lib/components/billing/alerts/realtimePricing.svelte'; import { get } from 'svelte/store'; import { preferences } from '$lib/stores/preferences'; import { defaultRoles, defaultScopes } from '$lib/constants'; @@ -64,6 +65,15 @@ export const load: LayoutLoad = async ({ params, depends, parent }) => { loadAvailableRegions(params.organization) ]); + if (isCloud && new Date() < new Date('2026-04-22')) { + headerAlert.add({ + show: true, + component: RealtimePricing, + id: 'realtimePricing', + importance: 1 + }); + } + return { header: Header, breadcrumbs: Breadcrumbs, diff --git a/src/routes/(console)/organization-[organization]/billing/planSummary.svelte b/src/routes/(console)/organization-[organization]/billing/planSummary.svelte index fa8e582d1..8c3c651e6 100644 --- a/src/routes/(console)/organization-[organization]/billing/planSummary.svelte +++ b/src/routes/(console)/organization-[organization]/billing/planSummary.svelte @@ -361,6 +361,27 @@ getResource(resources, 'GBHours'), currentPlan?.GBHours ), + createResourceRow( + 'realtime', + 'Realtime connections', + getResource(resources, 'realtime'), + currentPlan?.realtime + ), + createResourceRow( + 'realtime-messages', + 'Realtime messages', + getResource(resources, 'realtimeMessages'), + currentPlan?.realtimeMessages + ), + createRow({ + id: 'realtime-bandwidth', + label: 'Realtime bandwidth', + resource: getResource(resources, 'realtimeBandwidth'), + usageFormatter: ({ value }) => + humanFileSize(value).value + humanFileSize(value).unit, + priceFormatter: ({ amount }) => formatCurrency(amount), + includeProgress: false + }), createRow({ id: 'sms', label: 'Phone OTP', diff --git a/src/routes/(console)/organization-[organization]/usage/[[invoice]]/+page.svelte b/src/routes/(console)/organization-[organization]/usage/[[invoice]]/+page.svelte index cb59f3e69..4a743e4e7 100644 --- a/src/routes/(console)/organization-[organization]/usage/[[invoice]]/+page.svelte +++ b/src/routes/(console)/organization-[organization]/usage/[[invoice]]/+page.svelte @@ -34,6 +34,7 @@ const plan = data?.plan ?? undefined; $: projects = data.organizationUsage.projects; + $: orgUsage = data.organizationUsage; let usageProjects: Record = {}; @@ -423,6 +424,143 @@
+ + Realtime connections + Peak concurrent realtime connections across all projects in your organization. + + + {#if orgUsage.realtimeConnectionsTotal} + {@const current = orgUsage.realtimeConnectionsTotal} + {@const max = getServiceLimit('realtime', tier, plan)} + + [ + e.date, + e.value + ]) + ] + } + ]} /> + {#if projects?.length > 0} + + {/if} + {:else} + + + + No data to show + + + {/if} + + + + + Realtime messages + Total realtime messages sent to clients across all projects in your organization. + + + {#if orgUsage.realtimeMessagesTotal} + {@const current = orgUsage.realtimeMessagesTotal} + {@const max = getServiceLimit('realtimeMessages', tier, plan)} + + [e.date, e.value]) + ] + } + ]} /> + {#if projects?.length > 0} + + {/if} + {:else} + + + + No data to show + + + {/if} + + + + + Realtime bandwidth + Total realtime bandwidth consumed across all projects in your organization. + + + {#if orgUsage.realtimeBandwidthTotal} + {@const current = orgUsage.realtimeBandwidthTotal} + {@const currentHumanized = humanFileSize(current)} + + + humanFileSize(value).value + humanFileSize(value).unit + } + } + }} + series={[ + { + name: 'Realtime bandwidth', + data: [ + ...(orgUsage.realtimeBandwidth ?? []).map((e) => [e.date, e.value]) + ] + } + ]} /> + {#if projects?.length > 0} + + {/if} + {:else} + + + + No data to show + + + {/if} + + + Storage Calculated for all your files, deployments, builds, databases and backups. diff --git a/src/routes/(console)/organization-[organization]/usage/[[invoice]]/+page.ts b/src/routes/(console)/organization-[organization]/usage/[[invoice]]/+page.ts index 6c6f9425c..c5593e9d2 100644 --- a/src/routes/(console)/organization-[organization]/usage/[[invoice]]/+page.ts +++ b/src/routes/(console)/organization-[organization]/usage/[[invoice]]/+page.ts @@ -35,7 +35,13 @@ export const load: PageLoad = async ({ params, parent }) => { imageTransformations: null, imageTransformationsTotal: null, screenshotsGenerated: null, - screenshotsGeneratedTotal: null + screenshotsGeneratedTotal: null, + realtimeConnections: null, + realtimeConnectionsTotal: null, + realtimeMessages: null, + realtimeMessagesTotal: null, + realtimeBandwidth: null, + realtimeBandwidthTotal: null } }; } diff --git a/src/routes/(console)/organization-[organization]/usage/[[invoice]]/ProjectBreakdown.svelte b/src/routes/(console)/organization-[organization]/usage/[[invoice]]/ProjectBreakdown.svelte index 1531e8509..631bf679c 100644 --- a/src/routes/(console)/organization-[organization]/usage/[[invoice]]/ProjectBreakdown.svelte +++ b/src/routes/(console)/organization-[organization]/usage/[[invoice]]/ProjectBreakdown.svelte @@ -17,7 +17,10 @@ | 'databasesReads' | 'databasesWrites' | 'imageTransformations' - | 'screenshotsGenerated'; + | 'screenshotsGenerated' + | 'realtime' + | 'realtimeMessages' + | 'realtimeBandwidth'; type Estimate = 'authPhoneEstimate'; @@ -97,9 +100,12 @@ return formatNumberWithCommas(value); case 'executions': case 'users': + case 'realtime': + case 'realtimeMessages': return abbreviateNumber(value); case 'storage': case 'bandwidth': + case 'realtimeBandwidth': return humanFileSize(value).value + humanFileSize(value).unit; } } diff --git a/src/routes/(console)/project-[region]-[project]/settings/usage/[[invoice]]/+page.svelte b/src/routes/(console)/project-[region]-[project]/settings/usage/[[invoice]]/+page.svelte index 521d08acd..b6d535078 100644 --- a/src/routes/(console)/project-[region]-[project]/settings/usage/[[invoice]]/+page.svelte +++ b/src/routes/(console)/project-[region]-[project]/settings/usage/[[invoice]]/+page.svelte @@ -37,6 +37,12 @@ $: imageTransformationsTotal = data.usage.imageTransformationsTotal; $: screenshotsGenerated = data.usage.screenshotsGenerated; $: screenshotsGeneratedTotal = data.usage.screenshotsGeneratedTotal; + $: realtimeConnections = data.usage.realtimeConnections; + $: realtimeConnectionsTotal = data.usage.realtimeConnectionsTotal; + $: realtimeMessages = data.usage.realtimeMessages; + $: realtimeMessagesTotal = data.usage.realtimeMessagesTotal; + $: realtimeBandwidth = data.usage.realtimeBandwidth; + $: realtimeBandwidthTotal = data.usage.realtimeBandwidthTotal; $: dbReads = data.usage.databasesReads; $: dbWrites = data.usage.databasesWrites; @@ -444,6 +450,115 @@ {/if} + + Realtime connections + Peak concurrent realtime connections in your project. + + {#if realtimeConnections} + {@const current = formatNum(realtimeConnectionsTotal)} + + + {current} + + Connections + + [e.date, e.value])] + } + ]} /> + {:else} + + + + No data to show + + + {/if} + + + + Realtime messages + Total realtime messages sent to clients in your project. + + {#if realtimeMessages} + {@const current = formatNum(realtimeMessagesTotal)} + + + {current} + + Messages + + [e.date, e.value])] + } + ]} /> + {:else} + + + + No data to show + + + {/if} + + + + Realtime bandwidth + Total realtime bandwidth consumed in your project. + + {#if realtimeBandwidth} + {@const currentHumanized = humanFileSize(realtimeBandwidthTotal)} + + + {currentHumanized.value} + + {currentHumanized.unit} + + + humanFileSize(value).value + humanFileSize(value).unit + } + } + }} + series={[ + { + name: 'Realtime bandwidth', + data: [...realtimeBandwidth.map((e) => [e.date, e.value])] + } + ]} /> + {:else} + + + + No data to show + + + {/if} + + Phone OTP Calculated for all Phone OTP sent across your project. Resets at the start of each billing cycle.
From 7e5aa4c3a983080b8b77570c6cf503492ec01a82 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Sun, 29 Mar 2026 02:43:37 +0000 Subject: [PATCH 32/73] chore: update dependencies and refine type definitions in various components --- bun.lock | 13 ++++++++----- package.json | 7 +++++-- src/lib/components/git/repositories.svelte | 2 +- src/lib/components/git/selectRootModal.svelte | 4 ++-- .../repository-[repository]/+page.svelte | 2 +- .../functions/function-[function]/store.ts | 2 +- 6 files changed, 18 insertions(+), 12 deletions(-) diff --git a/bun.lock b/bun.lock index ceb6654fd..6bd01b5fb 100644 --- a/bun.lock +++ b/bun.lock @@ -27,7 +27,7 @@ "flatted": "^3.4.2", "ignore": "^6.0.2", "nanoid": "^5.1.7", - "nanotar": "^0.1.1", + "nanotar": "^0.3.0", "pretty-bytes": "^6.1.1", "remarkable": "^2.0.1", "svelte-confetti": "^1.4.0", @@ -77,11 +77,14 @@ }, }, "overrides": { + "brace-expansion": ">=5.0.5", + "cookie": "^0.7.0", "flatted": "^3.4.2", "immutable": "^5.1.5", "minimatch": "10.2.3", "picomatch": "^2.3.2", "vite": "npm:rolldown-vite@latest", + "yaml": "^1.10.3", }, "packages": { "@adobe/css-tools": ["@adobe/css-tools@4.4.4", "", {}, "sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg=="], @@ -606,7 +609,7 @@ "binary-extensions": ["binary-extensions@2.3.0", "", {}, "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw=="], - "brace-expansion": ["brace-expansion@5.0.4", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg=="], + "brace-expansion": ["brace-expansion@5.0.5", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ=="], "braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="], @@ -658,7 +661,7 @@ "convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="], - "cookie": ["cookie@0.6.0", "", {}, "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw=="], + "cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="], "cron-parser": ["cron-parser@4.9.0", "", { "dependencies": { "luxon": "^3.2.1" } }, "sha512-p0SaNjrHOnQeR8/VnfGbmg9te2kfyYSQ7Sc/j/6DtPL3JQvKxmjO9TSjNFpujqV3vEYYBvNNvXSxzyksBWAx1Q=="], @@ -1088,7 +1091,7 @@ "nanoid": ["nanoid@5.1.7", "", { "bin": { "nanoid": "bin/nanoid.js" } }, "sha512-ua3NDgISf6jdwezAheMOk4mbE1LXjm1DfMUDMuJf4AqxLFK3ccGpgWizwa5YV7Yz9EpXwEaWoRXSb/BnV0t5dQ=="], - "nanotar": ["nanotar@0.1.1", "", {}, "sha512-AiJsGsSF3O0havL1BydvI4+wR76sKT+okKRwWIaK96cZUnXqH0uNBOsHlbwZq3+m2BR1VKqHDVudl3gO4mYjpQ=="], + "nanotar": ["nanotar@0.3.0", "", {}, "sha512-Kv2JYYiCzt16Kt5QwAc9BFG89xfPNBx+oQL4GQXD9nLqPkZBiNaqaCWtwnbk/q7UVsTYevvM1b0UF8zmEI4pCg=="], "natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="], @@ -1444,7 +1447,7 @@ "yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="], - "yaml": ["yaml@1.10.2", "", {}, "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg=="], + "yaml": ["yaml@1.10.3", "", {}, "sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA=="], "yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="], diff --git a/package.json b/package.json index e9b07e352..e096082b2 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,7 @@ "flatted": "^3.4.2", "ignore": "^6.0.2", "nanoid": "^5.1.7", - "nanotar": "^0.1.1", + "nanotar": "^0.3.0", "pretty-bytes": "^6.1.1", "remarkable": "^2.0.1", "svelte-confetti": "^1.4.0", @@ -91,8 +91,11 @@ "overrides": { "vite": "npm:rolldown-vite@latest", "minimatch": "10.2.3", + "brace-expansion": ">=5.0.5", "immutable": "^5.1.5", "flatted": "^3.4.2", - "picomatch": "^2.3.2" + "yaml": "^1.10.3", + "picomatch": "^2.3.2", + "cookie": "^0.7.0" } } diff --git a/src/lib/components/git/repositories.svelte b/src/lib/components/git/repositories.svelte index df2c80050..f251a4d7a 100644 --- a/src/lib/components/git/repositories.svelte +++ b/src/lib/components/git/repositories.svelte @@ -138,7 +138,7 @@ $repositories.repositories = product === 'functions' - ? (result as unknown as Models.ProviderRepositoryRuntimeList) + ? (result as unknown as { runtimeProviderRepositories: (Models.ProviderRepository & { runtime: string })[] }) .runtimeProviderRepositories : (result as unknown as Models.ProviderRepositoryFrameworkList) .frameworkProviderRepositories; //TODO: remove forced cast after backend fixes diff --git a/src/lib/components/git/selectRootModal.svelte b/src/lib/components/git/selectRootModal.svelte index a121239cb..22437ef88 100644 --- a/src/lib/components/git/selectRootModal.svelte +++ b/src/lib/components/git/selectRootModal.svelte @@ -5,7 +5,7 @@ import { iconPath } from '$lib/stores/app'; import { sdk } from '$lib/stores/sdk'; import { installation, repository } from '$lib/stores/vcs'; - import { VCSDetectionType, type Models } from '@appwrite.io/console'; + import { VCSDetectionType } from '@appwrite.io/console'; import DirectoryPicker from '$lib/components/git/DirectoryPicker.svelte'; import { writable } from 'svelte/store'; @@ -113,7 +113,7 @@ const iconName = product === 'sites' ? detection.framework - : (detection as unknown as Models.DetectionRuntime).runtime; + : (detection as unknown as { runtime: string }).runtime; const resolved = resolveIconUrl(iconName); iconCache.set(path, resolved); return resolved; diff --git a/src/routes/(console)/project-[region]-[project]/functions/create-function/repository-[repository]/+page.svelte b/src/routes/(console)/project-[region]-[project]/functions/create-function/repository-[repository]/+page.svelte index 7993e419d..ddd449789 100644 --- a/src/routes/(console)/project-[region]-[project]/functions/create-function/repository-[repository]/+page.svelte +++ b/src/routes/(console)/project-[region]-[project]/functions/create-function/repository-[repository]/+page.svelte @@ -84,7 +84,7 @@ installationId: data.installation.$id, providerRepositoryId: page.params.repository, type: VCSDetectionType.Runtime - })) as unknown as Models.DetectionRuntime; /* SDK return type is wrong atm */ + })) as unknown as { entrypoint: string; commands: string; runtime: string; variables: { name: string; value: string }[] }; /* SDK return type is wrong atm */ entrypoint = detections.entrypoint; buildCommand = detections.commands; diff --git a/src/routes/(console)/project-[region]-[project]/functions/function-[function]/store.ts b/src/routes/(console)/project-[region]-[project]/functions/function-[function]/store.ts index 42bf293ce..e45c815a4 100644 --- a/src/routes/(console)/project-[region]-[project]/functions/function-[function]/store.ts +++ b/src/routes/(console)/project-[region]-[project]/functions/function-[function]/store.ts @@ -21,7 +21,7 @@ export const repositories: Writable<{ repositories: | Models.ProviderRepository[] | Models.ProviderRepositoryFramework[] - | Models.ProviderRepositoryRuntime[]; + | (Models.ProviderRepository & { runtime: string })[]; }> = writable({ search: '', installationId: '', From c7aee6426f24e42474849a826939731e2d967019 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Sun, 29 Mar 2026 02:47:37 +0000 Subject: [PATCH 33/73] format --- src/lib/components/git/repositories.svelte | 9 +++++++-- .../create-function/repository-[repository]/+page.svelte | 7 ++++++- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/lib/components/git/repositories.svelte b/src/lib/components/git/repositories.svelte index f251a4d7a..8104efd5b 100644 --- a/src/lib/components/git/repositories.svelte +++ b/src/lib/components/git/repositories.svelte @@ -138,8 +138,13 @@ $repositories.repositories = product === 'functions' - ? (result as unknown as { runtimeProviderRepositories: (Models.ProviderRepository & { runtime: string })[] }) - .runtimeProviderRepositories + ? ( + result as unknown as { + runtimeProviderRepositories: (Models.ProviderRepository & { + runtime: string; + })[]; + } + ).runtimeProviderRepositories : (result as unknown as Models.ProviderRepositoryFrameworkList) .frameworkProviderRepositories; //TODO: remove forced cast after backend fixes $repositories.total = result.total; diff --git a/src/routes/(console)/project-[region]-[project]/functions/create-function/repository-[repository]/+page.svelte b/src/routes/(console)/project-[region]-[project]/functions/create-function/repository-[repository]/+page.svelte index ddd449789..eb06a98fa 100644 --- a/src/routes/(console)/project-[region]-[project]/functions/create-function/repository-[repository]/+page.svelte +++ b/src/routes/(console)/project-[region]-[project]/functions/create-function/repository-[repository]/+page.svelte @@ -84,7 +84,12 @@ installationId: data.installation.$id, providerRepositoryId: page.params.repository, type: VCSDetectionType.Runtime - })) as unknown as { entrypoint: string; commands: string; runtime: string; variables: { name: string; value: string }[] }; /* SDK return type is wrong atm */ + })) as unknown as { + entrypoint: string; + commands: string; + runtime: string; + variables: { name: string; value: string }[]; + }; /* SDK return type is wrong atm */ entrypoint = detections.entrypoint; buildCommand = detections.commands; From 3dd288fd5d1ace5cea5240dc5bc63560f8a91a71 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 29 Mar 2026 05:38:57 +0000 Subject: [PATCH 34/73] Fix database sidebar not appearing on client-side navigation The subNavigation variable was being reactively assigned from page.data in both shell.svelte and navigation.svelte, connected via bind:. This competing reactive ownership caused the value to not update properly during client-side navigation (only on full page refresh). Made shell.svelte the single source of truth by removing the duplicate reactive assignment from navigation.svelte and switching from bind: to one-way prop passing. https://claude.ai/code/session_012QCz2xVatAtJ3Q6FSp1cB4 --- src/lib/layout/navigation.svelte | 3 --- src/lib/layout/shell.svelte | 4 ++-- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/lib/layout/navigation.svelte b/src/lib/layout/navigation.svelte index 7568db77c..3d65b7842 100644 --- a/src/lib/layout/navigation.svelte +++ b/src/lib/layout/navigation.svelte @@ -1,11 +1,8 @@ - + -
{tooltipContent}
+
{tooltipContent}
diff --git a/src/routes/(console)/account/organizations/+page.svelte b/src/routes/(console)/account/organizations/+page.svelte index 2ed7d57dc..4d748e1e1 100644 --- a/src/routes/(console)/account/organizations/+page.svelte +++ b/src/routes/(console)/account/organizations/+page.svelte @@ -17,6 +17,10 @@ import type { Models } from '@appwrite.io/console'; import { daysLeftInTrial, billingIdToPlan } from '$lib/stores/billing'; import { toLocaleDate } from '$lib/helpers/date'; + import { + BODY_TOOLTIP_MAX_WIDTH, + BODY_TOOLTIP_WRAPPER_STYLE + } from '$lib/helpers/tooltipContent'; import { goto } from '$app/navigation'; import { Icon, Tooltip, Typography } from '@appwrite.io/pink-svelte'; import { IconPlus } from '@appwrite.io/pink-icons-svelte'; @@ -132,29 +136,30 @@ {#await planName} {:then name} - + - +
You are limited to 1 free organization per account - +
{/await} {/if} {/if} {#if isOrganizationOnTrial(organization)} - +
- {`Your trial ends on ${toLocaleDate( +
+ {`Your trial ends on ${toLocaleDate( organization.billingStartDate - )}. ${$daysLeftInTrial} days remaining.`} + )}. ${$daysLeftInTrial} days remaining.`} +
{/if} diff --git a/src/routes/(console)/organization-[organization]/+page.svelte b/src/routes/(console)/organization-[organization]/+page.svelte index b022f8f87..7e8582095 100644 --- a/src/routes/(console)/organization-[organization]/+page.svelte +++ b/src/routes/(console)/organization-[organization]/+page.svelte @@ -35,6 +35,10 @@ } from '@appwrite.io/pink-icons-svelte'; import type { PageProps } from './$types'; import { getPlatformInfo } from '$lib/helpers/platform'; + import { + BODY_TOOLTIP_MAX_WIDTH, + BODY_TOOLTIP_WRAPPER_STYLE + } from '$lib/helpers/tooltipContent'; import CreateProjectCloud from './createProjectCloud.svelte'; import { regions as regionsStore } from '$lib/stores/organization'; @@ -147,16 +151,16 @@ {#if $canWriteProjects} {#if projectCreationDisabled && reachedProjectLimit} - +
- +
You have reached your limit of {projectsLimit} projects. - +
{:else}
-
+
{!( organization?.billingPlanDetails?.addons?.seats?.supported ?? true diff --git a/src/routes/(console)/organization-[organization]/members/+page.svelte b/src/routes/(console)/organization-[organization]/members/+page.svelte index cdd4c9851..d422cfb81 100644 --- a/src/routes/(console)/organization-[organization]/members/+page.svelte +++ b/src/routes/(console)/organization-[organization]/members/+page.svelte @@ -7,6 +7,10 @@ import { Submit, trackError, trackEvent } from '$lib/actions/analytics'; import Upgrade from '$lib/components/roles/upgrade.svelte'; import { getServiceLimit, readOnly, getRoleLabel } from '$lib/stores/billing'; + import { + BODY_TOOLTIP_MAX_WIDTH, + BODY_TOOLTIP_WRAPPER_STYLE + } from '$lib/helpers/tooltipContent'; import { addNotification } from '$lib/stores/notifications'; import { currentPlan, newMemberModal, organization } from '$lib/stores/organization'; import { isOwner } from '$lib/stores/roles'; @@ -78,7 +82,10 @@ Members - +
Invite
-
+
{!supportsMembers ? 'Upgrade to add more members' : `You've reached the members limit for the ${$organization?.billingPlanDetails.name} plan`} diff --git a/src/routes/(console)/project-[region]-[project]/databases/+page.svelte b/src/routes/(console)/project-[region]-[project]/databases/+page.svelte index 6140f5e2e..f78f44b83 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/+page.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/+page.svelte @@ -18,6 +18,10 @@ import EmptySearch from '$lib/components/emptySearch.svelte'; import { isServiceLimited } from '$lib/stores/billing'; import { organization } from '$lib/stores/organization'; + import { + BODY_TOOLTIP_MAX_WIDTH, + BODY_TOOLTIP_WRAPPER_STYLE_PRELINE + } from '$lib/helpers/tooltipContent'; import { resolveRoute } from '$lib/stores/navigation'; @@ -61,7 +65,7 @@ view={data.view} searchPlaceholder="Search by name or ID"> {#if $canWriteDatabases} - +
-
+
You have reached the maximum number of databases for your plan.
diff --git a/src/routes/(console)/project-[region]-[project]/functions/+page.svelte b/src/routes/(console)/project-[region]-[project]/functions/+page.svelte index bcf39b756..27f3d057a 100644 --- a/src/routes/(console)/project-[region]-[project]/functions/+page.svelte +++ b/src/routes/(console)/project-[region]-[project]/functions/+page.svelte @@ -16,6 +16,10 @@ import { isServiceLimited } from '$lib/stores/billing'; import { organization } from '$lib/stores/organization'; import { wizard } from '$lib/stores/wizard'; + import { + BODY_TOOLTIP_MAX_WIDTH, + BODY_TOOLTIP_WRAPPER_STYLE_PRELINE + } from '$lib/helpers/tooltipContent'; import { parseExpression } from 'cron-parser'; import { onMount } from 'svelte'; @@ -101,7 +105,7 @@ - +
-
+
You have reached the maximum number of functions for your plan.
diff --git a/src/routes/(console)/project-[region]-[project]/functions/function-[function]/executions/+page.svelte b/src/routes/(console)/project-[region]-[project]/functions/function-[function]/executions/+page.svelte index b1b9e7f6e..8cce6b5c1 100644 --- a/src/routes/(console)/project-[region]-[project]/functions/function-[function]/executions/+page.svelte +++ b/src/routes/(console)/project-[region]-[project]/functions/function-[function]/executions/+page.svelte @@ -2,6 +2,10 @@ import { invalidate } from '$app/navigation'; import { Empty, EmptyFilter, PaginationWithLimit } from '$lib/components'; import { Dependencies } from '$lib/constants'; + import { + BODY_TOOLTIP_MAX_WIDTH, + BODY_TOOLTIP_WRAPPER_STYLE_PRELINE + } from '$lib/helpers/tooltipContent'; import { Button } from '$lib/elements/forms'; import { Container, ResponsiveContainerHeader } from '$lib/layout'; import { realtime } from '$lib/stores/sdk'; @@ -27,7 +31,7 @@ - +
- +
Execution cannot be created because there is no active deployment. - +
@@ -63,7 +67,7 @@ event="empty_documentation" size="s" ariaLabel="create execution">Documentation - +
- +
Execution cannot be created because there is no active deployment. - +
diff --git a/src/routes/(console)/project-[region]-[project]/overview/platforms/action.svelte b/src/routes/(console)/project-[region]-[project]/overview/platforms/action.svelte index f08d2f0cc..740f792eb 100644 --- a/src/routes/(console)/project-[region]-[project]/overview/platforms/action.svelte +++ b/src/routes/(console)/project-[region]-[project]/overview/platforms/action.svelte @@ -14,13 +14,17 @@ import { isServiceLimited } from '$lib/stores/billing'; import { organization } from '$lib/stores/organization'; import { page } from '$app/state'; + import { + BODY_TOOLTIP_MAX_WIDTH, + BODY_TOOLTIP_WRAPPER_STYLE_PRELINE + } from '$lib/helpers/tooltipContent'; $: isLimited = isServiceLimited('platforms', $organization, page.data.platforms.total); {#if $canWritePlatforms} {#if isLimited} - +
-
+
You have reached the maximum number of platforms for your plan in a project.
diff --git a/src/routes/(console)/project-[region]-[project]/settings/webhooks/+page.svelte b/src/routes/(console)/project-[region]-[project]/settings/webhooks/+page.svelte index 4a2f324ae..3667ce422 100644 --- a/src/routes/(console)/project-[region]-[project]/settings/webhooks/+page.svelte +++ b/src/routes/(console)/project-[region]-[project]/settings/webhooks/+page.svelte @@ -18,6 +18,10 @@ import { goto } from '$app/navigation'; import { isServiceLimited } from '$lib/stores/billing'; import { organization } from '$lib/stores/organization'; + import { + BODY_TOOLTIP_MAX_WIDTH, + BODY_TOOLTIP_WRAPPER_STYLE_PRELINE + } from '$lib/helpers/tooltipContent'; export let data: PageData; @@ -39,7 +43,7 @@ {#if $canWriteWebhooks} - +
-
+
You have reached the maximum number of webhooks for your plan.
diff --git a/src/routes/(console)/project-[region]-[project]/settings/webhooks/create/+page.svelte b/src/routes/(console)/project-[region]-[project]/settings/webhooks/create/+page.svelte index 3c95e0fed..672093e9e 100644 --- a/src/routes/(console)/project-[region]-[project]/settings/webhooks/create/+page.svelte +++ b/src/routes/(console)/project-[region]-[project]/settings/webhooks/create/+page.svelte @@ -49,8 +49,11 @@ `${base}/project-${page.params.region}-${page.params.project}/settings/webhooks/${webhook.$id}` ); } catch (error) { - trackError(error.message, Submit.DomainCreate); - throw new Error(error.message); + addNotification({ + type: 'error', + message: error.message + }); + trackError(error, Submit.WebhookCreate); } } diff --git a/src/routes/(console)/project-[region]-[project]/settings/webhooks/create/step1.svelte b/src/routes/(console)/project-[region]-[project]/settings/webhooks/create/step1.svelte index e4595151e..274d11472 100644 --- a/src/routes/(console)/project-[region]-[project]/settings/webhooks/create/step1.svelte +++ b/src/routes/(console)/project-[region]-[project]/settings/webhooks/create/step1.svelte @@ -13,6 +13,7 @@ label="POST URL" id="url" placeholder="https://example.com/callback" + helper="Must be https (or http) and a public domain — localhost and private URLs are not accepted on Appwrite Cloud." bind:value={url} required /> diff --git a/src/routes/(console)/project-[region]-[project]/sites/site-[site]/+page.svelte b/src/routes/(console)/project-[region]-[project]/sites/site-[site]/+page.svelte index 45a17fc3b..2d8db89b0 100644 --- a/src/routes/(console)/project-[region]-[project]/sites/site-[site]/+page.svelte +++ b/src/routes/(console)/project-[region]-[project]/sites/site-[site]/+page.svelte @@ -10,6 +10,10 @@ import { realtime } from '$lib/stores/sdk'; import { invalidate } from '$app/navigation'; import { Dependencies } from '$lib/constants'; + import { + BODY_TOOLTIP_MAX_WIDTH, + BODY_TOOLTIP_WRAPPER_STYLE + } from '$lib/helpers/tooltipContent'; import { onMount } from 'svelte'; import { page } from '$app/state'; import { base } from '$app/paths'; @@ -44,7 +48,9 @@ Visit {/if} - +
- +
Rollback is possible only if there is a deployment that is ready and was active. - +
{/snippet} diff --git a/src/routes/(console)/project-[region]-[project]/storage/+page.svelte b/src/routes/(console)/project-[region]-[project]/storage/+page.svelte index aa629e200..bc3f408da 100644 --- a/src/routes/(console)/project-[region]-[project]/storage/+page.svelte +++ b/src/routes/(console)/project-[region]-[project]/storage/+page.svelte @@ -21,6 +21,10 @@ import { isServiceLimited } from '$lib/stores/billing'; import type { PageProps } from './$types'; import { organization } from '$lib/stores/organization'; + import { + BODY_TOOLTIP_MAX_WIDTH, + BODY_TOOLTIP_WRAPPER_STYLE_PRELINE + } from '$lib/helpers/tooltipContent'; let { data }: PageProps = $props(); @@ -44,7 +48,7 @@ view={data.view} searchPlaceholder="Search by name or ID"> {#if $canWriteBuckets} - +
-
+
You have reached the maximum number of buckets for your plan.
From 68ee7a8edb7c3117214f8b0f9ce53612d1e9dc89 Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Mon, 30 Mar 2026 08:34:44 +0530 Subject: [PATCH 43/73] render deprecated api key scopes --- src/lib/constants.ts | 27 ++++-- .../overview/(components)/keyDetails.svelte | 6 +- .../overview/api-keys/scopes.svelte | 92 +++++-------------- 3 files changed, 44 insertions(+), 81 deletions(-) diff --git a/src/lib/constants.ts b/src/lib/constants.ts index 2cb52605e..6c28da666 100644 --- a/src/lib/constants.ts +++ b/src/lib/constants.ts @@ -155,12 +155,15 @@ export const defaultRoles: string[] = ['owner']; // these are kept for backwards compatibility with keys and events that already exists. // for the new ones, we use the new terminology. -export const scopes: { +export type ScopeDefinition = { scope: string; description: string; category: string; icon: string; -}[] = [ + deprecated?: boolean; +}; + +export const scopes: ScopeDefinition[] = [ { scope: 'sessions.write', description: "Access to create, update and delete your project's sessions", @@ -207,13 +210,15 @@ export const scopes: { scope: 'collections.read', description: "Access to read your project's database collections", category: 'Database', - icon: 'database' + icon: 'database', + deprecated: true }, { scope: 'collections.write', description: "Access to create, update, and delete your project's database collections", category: 'Database', - icon: 'database' + icon: 'database', + deprecated: true }, { scope: 'tables.read', @@ -231,14 +236,16 @@ export const scopes: { scope: 'attributes.read', description: "Access to read your project's database collection's attributes", category: 'Database', - icon: 'database' + icon: 'database', + deprecated: true }, { scope: 'attributes.write', description: "Access to create, update, and delete your project's database collection's attributes", category: 'Database', - icon: 'database' + icon: 'database', + deprecated: true }, { scope: 'columns.read', @@ -268,13 +275,15 @@ export const scopes: { scope: 'documents.read', description: "Access to read your project's database documents", category: 'Database', - icon: 'database' + icon: 'database', + deprecated: true }, { scope: 'documents.write', description: "Access to create, update, and delete your project's database documents", category: 'Database', - icon: 'database' + icon: 'database', + deprecated: true }, { scope: 'rows.read', @@ -466,7 +475,7 @@ export const scopes: { } ]; -export const cloudOnlyBackupScopes = [ +export const cloudOnlyBackupScopes: ScopeDefinition[] = [ { scope: 'policies.read', description: 'Access to read your database backup policies', diff --git a/src/routes/(console)/project-[region]-[project]/overview/(components)/keyDetails.svelte b/src/routes/(console)/project-[region]-[project]/overview/(components)/keyDetails.svelte index 909e7a678..da2487f86 100644 --- a/src/routes/(console)/project-[region]-[project]/overview/(components)/keyDetails.svelte +++ b/src/routes/(console)/project-[region]-[project]/overview/(components)/keyDetails.svelte @@ -16,7 +16,6 @@ import { symmetricDifference } from '$lib/helpers/array'; import Scopes from '../api-keys/scopes.svelte'; import { InteractiveText, Layout, Typography } from '@appwrite.io/pink-svelte'; - import { getEffectiveScopes } from '../api-keys/scopes.svelte'; export let key: Models.DevKey | Models.Key; export let keyType: 'api' | 'dev' = 'api'; @@ -163,8 +162,6 @@ {#if isApiKey}
{@const apiKey = asApiKey(key)} - {@const apiKeyCorrectScopes = getEffectiveScopes(apiKey.scopes)} - {@const currentEffective = scopes ? getEffectiveScopes(scopes) : null} Scopes You can choose which permission scope to grant your application. It is a best practice @@ -178,8 +175,7 @@ diff --git a/src/routes/(console)/project-[region]-[project]/overview/api-keys/scopes.svelte b/src/routes/(console)/project-[region]-[project]/overview/api-keys/scopes.svelte index f314419f8..31b8352f3 100644 --- a/src/routes/(console)/project-[region]-[project]/overview/api-keys/scopes.svelte +++ b/src/routes/(console)/project-[region]-[project]/overview/api-keys/scopes.svelte @@ -31,29 +31,21 @@ import { Button } from '$lib/elements/forms'; import { symmetricDifference } from '$lib/helpers/array'; import { scopes as allScopes, cloudOnlyBackupScopes } from '$lib/constants'; - import { Accordion, Divider, Layout, Selector } from '@appwrite.io/pink-svelte'; + import { Accordion, Badge, Divider, Layout, Selector } from '@appwrite.io/pink-svelte'; import type { Scopes } from '@appwrite.io/console'; export let scopes: Scopes[]; - const baseFilteredScopes = allScopes.filter((scope) => { - const val = scope.scope; - if (!val) return false; - - const legacyPrefixes = ['collections.', 'attributes.', 'documents.']; - return !legacyPrefixes.some((prefix) => val.startsWith(prefix)); - }); - // insert cloud-only scopes right after databases.write - const databasesWriteIndex = baseFilteredScopes.findIndex((s) => s.scope === 'databases.write'); + const databasesWriteIndex = allScopes.findIndex((s) => s.scope === 'databases.write'); const filteredScopes = isCloud && databasesWriteIndex !== -1 ? [ - ...baseFilteredScopes.slice(0, databasesWriteIndex + 1), + ...allScopes.slice(0, databasesWriteIndex + 1), ...cloudOnlyBackupScopes, - ...baseFilteredScopes.slice(databasesWriteIndex + 1) + ...allScopes.slice(databasesWriteIndex + 1) ] - : baseFilteredScopes; + : allScopes; // include all scopes const scopeCatalog = new Set([ @@ -90,9 +82,8 @@ onMount(() => { scopes.forEach((scope) => { - const newerScope = toNewerScope(scope); - if (newerScope in activeScopes) { - activeScopes[newerScope] = true; + if (scope in activeScopes) { + activeScopes[scope] = true; } }); @@ -111,36 +102,11 @@ } } - function toNewerScope(scope: string): string { - for (const pair of compatPairs) { - if (scope.startsWith(pair.legacy)) { - return scope.replace(pair.legacy, pair.newer); - } - } - return scope; - } - - function getAllScopeVariants(scope: string): string[] { - const variants = new Set([scope]); - - for (const pair of compatPairs) { - if (scope.startsWith(pair.newer)) { - variants.add(scope.replace(pair.newer, pair.legacy)); - } else if (scope.startsWith(pair.legacy)) { - variants.add(scope.replace(pair.legacy, pair.newer)); - } - } - - return Array.from(variants); - } - function categoryState(category: string, s: string[]): boolean | 'indeterminate' { const scopesByCategory = filteredScopes.filter((n) => n.category === category); - - const activeInCategory = scopesByCategory.filter((scopeItem) => { - const newerScope = scopeItem.scope; - return s.some((scope) => toNewerScope(scope) === newerScope); - }); + const activeInCategory = scopesByCategory.filter((scopeItem) => + s.includes(scopeItem.scope as Scopes) + ); if (activeInCategory.length === 0) { return false; @@ -161,20 +127,9 @@ } function generateSyncedScopes(activeScopesObj: Record): Scopes[] { - const result = new Set(); - - Object.entries(activeScopesObj).forEach(([scope, isActive]) => { - if (isActive) { - const variants = getAllScopeVariants(scope); - variants.forEach((variant) => { - if (scopeCatalog.has(variant)) { - result.add(variant); - } - }); - } - }); - - return Array.from(result) as Scopes[]; + return Object.entries(activeScopesObj) + .filter(([scope, isActive]) => isActive && scopeCatalog.has(scope)) + .map(([scope]) => scope as Scopes); } $: { @@ -203,9 +158,7 @@ {@const checked = categoryState(category, scopes)} {@const isLastItem = index === categories.length - 1} {@const scopesLength = filteredScopes.filter( - (n) => - n.category === category && - scopes.some((scope) => toNewerScope(scope) === n.scope) + (n) => n.category === category && scopes.includes(n.scope as Scopes) ).length} onCategoryChange(event, category)}> {#each filteredScopes.filter((s) => s.category === category) as scope} - + + + {#if scope.deprecated} + + {/if} + {/each} From 043628cc25cb369cb7682619f4e9436651851eb8 Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Mon, 30 Mar 2026 08:45:28 +0530 Subject: [PATCH 44/73] address deprecated scope review feedback --- .../overview/(components)/table.svelte | 3 +-- .../project-[region]-[project]/overview/api-keys/scopes.svelte | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/routes/(console)/project-[region]-[project]/overview/(components)/table.svelte b/src/routes/(console)/project-[region]-[project]/overview/(components)/table.svelte index 535be9bd6..198397e94 100644 --- a/src/routes/(console)/project-[region]-[project]/overview/(components)/table.svelte +++ b/src/routes/(console)/project-[region]-[project]/overview/(components)/table.svelte @@ -11,7 +11,6 @@ import { Badge, Layout, Table } from '@appwrite.io/pink-svelte'; import DeleteBatch from './deleteBatch.svelte'; import { capitalize } from '$lib/helpers/string'; - import { getEffectiveScopes } from '../api-keys/scopes.svelte'; let { keyType = 'api', @@ -31,7 +30,7 @@ function getApiKeyScopeCount(key: Models.Key | Models.DevKey) { const apiKey = key as Models.Key; - return getEffectiveScopes(apiKey.scopes).length; + return apiKey.scopes.length; } function getExpiryDetails(key: Models.Key | Models.DevKey): { diff --git a/src/routes/(console)/project-[region]-[project]/overview/api-keys/scopes.svelte b/src/routes/(console)/project-[region]-[project]/overview/api-keys/scopes.svelte index 31b8352f3..1a4c8241c 100644 --- a/src/routes/(console)/project-[region]-[project]/overview/api-keys/scopes.svelte +++ b/src/routes/(console)/project-[region]-[project]/overview/api-keys/scopes.svelte @@ -120,7 +120,7 @@ function onCategoryChange(event: CustomEvent, category: Category) { if (event.detail === 'indeterminate') return; filteredScopes.forEach((s) => { - if (s.category === category) { + if (s.category === category && !s.deprecated) { activeScopes[s.scope] = event.detail; } }); From 8a7736c9718ebf660049664bbdc41d97b8a8417a Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Mon, 30 Mar 2026 08:47:43 +0530 Subject: [PATCH 45/73] improve deprecated scope accessibility --- .../project-[region]-[project]/overview/api-keys/scopes.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/(console)/project-[region]-[project]/overview/api-keys/scopes.svelte b/src/routes/(console)/project-[region]-[project]/overview/api-keys/scopes.svelte index 1a4c8241c..70608e2f0 100644 --- a/src/routes/(console)/project-[region]-[project]/overview/api-keys/scopes.svelte +++ b/src/routes/(console)/project-[region]-[project]/overview/api-keys/scopes.svelte @@ -173,7 +173,7 @@ {#if scope.deprecated} From 6784083bd5e95a7cbcf4a3a5d5fdd467c9560b38 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Mon, 30 Mar 2026 04:47:02 +0000 Subject: [PATCH 46/73] update endorcement date --- src/lib/components/billing/alerts/realtimePricing.svelte | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/components/billing/alerts/realtimePricing.svelte b/src/lib/components/billing/alerts/realtimePricing.svelte index 5529075df..f0ae6ff22 100644 --- a/src/lib/components/billing/alerts/realtimePricing.svelte +++ b/src/lib/components/billing/alerts/realtimePricing.svelte @@ -24,11 +24,11 @@ {#if $organization?.$id && !dismissed} - Starting April 22nd, realtime usage (connections, messages, and bandwidth) will be + Starting April 30th, realtime usage (connections, messages, and bandwidth) will be charged based on your plan's rates. Review your usage to avoid unexpected charges. From c587699a7b6c166a8d2cf6c247b8977fe573327e Mon Sep 17 00:00:00 2001 From: Prem Palanisamy Date: Mon, 30 Mar 2026 06:42:21 +0100 Subject: [PATCH 47/73] add JSON import/export for documentsdb, fix schemaless filter, enable documentsdb listing --- src/lib/components/filters/filters.svelte | 9 +- .../(entity)/helpers/sdk.ts | 6 +- .../collection-[collection]/+page.svelte | 82 +++++++--- .../export/+page.svelte | 148 ++++++++++++++++++ 4 files changed, 214 insertions(+), 31 deletions(-) create mode 100644 src/routes/(console)/project-[region]-[project]/databases/database-[database]/collection-[collection]/export/+page.svelte diff --git a/src/lib/components/filters/filters.svelte b/src/lib/components/filters/filters.svelte index 6a9e7d359..5848c5329 100644 --- a/src/lib/components/filters/filters.svelte +++ b/src/lib/components/filters/filters.svelte @@ -87,11 +87,16 @@ value || arrayValues.length) ) { + const columnsWithVirtual = + !schema && selectedColumn && !$columns.find((c) => c.id === selectedColumn) + ? [...$columns, { id: selectedColumn, title: selectedColumn, type: 'string' as const }] + : $columns; + // For distance operators, pass the distance as a separate parameter if (isDistanceOperator && distanceValue !== null && value !== null) { - addFilter($columns, selectedColumn, operatorKey, value, arrayValues, distanceValue); + addFilter(columnsWithVirtual, selectedColumn, operatorKey, value, arrayValues, distanceValue); } else { - addFilter($columns, selectedColumn, operatorKey, value, arrayValues); + addFilter(columnsWithVirtual, selectedColumn, operatorKey, value, arrayValues); } selectedColumn = null; value = null; diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(entity)/helpers/sdk.ts b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(entity)/helpers/sdk.ts index 71d3c5828..d10ec8ef7 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(entity)/helpers/sdk.ts +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(entity)/helpers/sdk.ts @@ -140,10 +140,8 @@ export function useDatabaseSdk( async list(params): Promise { const results = await Promise.all([ - baseSdk.tablesDB.list(params) - - // not available just yet! - // baseSdk.documentsDB.list(params), + baseSdk.tablesDB.list(params), + baseSdk.documentsDB.list(params) ]); return results.reduce( diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/collection-[collection]/+page.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/collection-[collection]/+page.svelte index af6e7c216..22c5b0792 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/collection-[collection]/+page.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/collection-[collection]/+page.svelte @@ -17,9 +17,13 @@ IconChevronUp, IconPlus, IconViewBoards, - IconRefresh + IconRefresh, + IconUpload, + IconDownload } from '@appwrite.io/pink-icons-svelte'; import { type Models } from '@appwrite.io/console'; + import { sdk } from '$lib/stores/sdk'; + import { goto } from '$app/navigation'; import { expandTabs, randomDataModalState, spreadsheetRenderKey } from '$database/store'; import { invalidate } from '$app/navigation'; import { hash } from '$lib/helpers/string'; @@ -50,36 +54,28 @@ let columnDisplayNameInput: ColumnDisplayNameInput | null = $state(null); const disableCreateDocument = $derived( - $noSqlDocument.isNew && ($noSqlDocument.hasDataChanged || $noSqlDocument.isDirty) + $isCollectionsJsonImportInProgress || + ($noSqlDocument.isNew && ($noSqlDocument.hasDataChanged || $noSqlDocument.isDirty)) ); - function createFilterableColumns(): Column[] { - return [ - { id: '$id', title: '$id', type: 'string' as ColumnType }, - { id: '$createdAt', title: '$createdAt', type: 'datetime' as ColumnType }, - { id: '$updatedAt', title: '$updatedAt', type: 'datetime' as ColumnType } - ]; - } - - function handleColumnToggle() { - // Force spreadsheet re-render when columns are toggled - spreadsheetRenderKey.set(hash(Date.now().toString())); + function getExportUrl() { + const queryParam = page.url.searchParams.get('query'); + const url = `${page.url.pathname}/export`; + return queryParam ? `${url}?query=${encodeURIComponent(queryParam)}` : url; } async function onSelect(file: Models.File, localFile = false) { $isCollectionsJsonImportInProgress = true; - console.log(file, localFile); - try { - /*await sdk + await sdk .forProject(page.params.region, page.params.project) .migrations.createJSONImport({ bucketId: file.bucketId, fileId: file.$id, resourceId: `${page.params.database}:${page.params.collection}`, internalFile: localFile - });*/ + }); addNotification({ type: 'success', @@ -98,6 +94,19 @@ } } + function createFilterableColumns(): Column[] { + return [ + { id: '$id', title: '$id', type: 'string' as ColumnType }, + { id: '$createdAt', title: '$createdAt', type: 'datetime' as ColumnType }, + { id: '$updatedAt', title: '$updatedAt', type: 'datetime' as ColumnType } + ]; + } + + function handleColumnToggle() { + // Force spreadsheet re-render when columns are toggled + spreadsheetRenderKey.set(hash(Date.now().toString())); + } + $effect(() => { filterColumns.set(createFilterableColumns()); }); @@ -147,13 +156,6 @@ direction="row" alignItems="center" justifyContent="flex-end"> - {#if !$isSmallViewport} - Save your current document before creating a new one + {$isCollectionsJsonImportInProgress + ? 'This action is disabled during import' + : 'Save your current document before creating a new one'} + + + Import JSON + + + + + Export JSON + + + + + + + + From 0735f178a881615115b0bd95c7a6ae97ba34fc62 Mon Sep 17 00:00:00 2001 From: Prem Palanisamy Date: Mon, 30 Mar 2026 11:43:48 +0100 Subject: [PATCH 48/73] split create index for documentsdb, free text attribute input for schemaless collections --- src/lib/components/filters/filters.svelte | 14 +- .../(entity)/views/indexes/view.svelte | 17 +- .../collection-[collection]/+page.svelte | 3 +- .../indexes/+page.svelte | 14 +- .../indexes/createIndex.svelte | 263 ++++++++++++++++++ 5 files changed, 302 insertions(+), 9 deletions(-) create mode 100644 src/routes/(console)/project-[region]-[project]/databases/database-[database]/collection-[collection]/indexes/createIndex.svelte diff --git a/src/lib/components/filters/filters.svelte b/src/lib/components/filters/filters.svelte index 5848c5329..52eae51f7 100644 --- a/src/lib/components/filters/filters.svelte +++ b/src/lib/components/filters/filters.svelte @@ -89,12 +89,22 @@ ) { const columnsWithVirtual = !schema && selectedColumn && !$columns.find((c) => c.id === selectedColumn) - ? [...$columns, { id: selectedColumn, title: selectedColumn, type: 'string' as const }] + ? [ + ...$columns, + { id: selectedColumn, title: selectedColumn, type: 'string' as const } + ] : $columns; // For distance operators, pass the distance as a separate parameter if (isDistanceOperator && distanceValue !== null && value !== null) { - addFilter(columnsWithVirtual, selectedColumn, operatorKey, value, arrayValues, distanceValue); + addFilter( + columnsWithVirtual, + selectedColumn, + operatorKey, + value, + arrayValues, + distanceValue + ); } else { addFilter(columnsWithVirtual, selectedColumn, operatorKey, value, arrayValues); } diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(entity)/views/indexes/view.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(entity)/views/indexes/view.svelte index a42c9e981..b977cfe34 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(entity)/views/indexes/view.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(entity)/views/indexes/view.svelte @@ -47,13 +47,17 @@ onCreateIndex, onDeleteIndexes, emptyIndexesSheetView, - emptyEntitiesSheetView + emptyEntitiesSheetView, + createIndexForm, + createIndexRef = $bindable() }: { entity: Entity; onCreateIndex: (index: CreateIndexesCallbackType) => Promise; onDeleteIndexes: (indexKeys: string[]) => Promise; emptyIndexesSheetView: Snippet<[() => void]>; emptyEntitiesSheetView?: Snippet<[() => void]>; + createIndexForm?: Snippet; + createIndexRef?: { create: () => Promise }; } = $props(); let showCreateIndex = $state(false); @@ -194,7 +198,7 @@ +
+ {:else} +
+ +
+ {/if} + + {/each} +
+ +
+ + + From e254b8f18d2980974d886c6354c5e135849676f9 Mon Sep 17 00:00:00 2001 From: Prem Palanisamy Date: Mon, 30 Mar 2026 12:22:58 +0100 Subject: [PATCH 49/73] use resolve() and Click tracking for export URL, consistent with tablesdb --- .../collection-[collection]/+page.svelte | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/collection-[collection]/+page.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/collection-[collection]/+page.svelte index 8828709b5..04b815f81 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/collection-[collection]/+page.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/collection-[collection]/+page.svelte @@ -24,6 +24,8 @@ import { type Models } from '@appwrite.io/console'; import { sdk } from '$lib/stores/sdk'; import { goto } from '$app/navigation'; + import { resolve } from '$app/paths'; + import { Click } from '$lib/actions/analytics'; import { expandTabs, randomDataModalState, spreadsheetRenderKey } from '$database/store'; import { invalidate } from '$app/navigation'; import { hash } from '$lib/helpers/string'; @@ -60,7 +62,15 @@ function getExportUrl() { const queryParam = page.url.searchParams.get('query'); - const url = `${page.url.pathname}/export`; + const url = resolve( + '/(console)/project-[region]-[project]/databases/database-[database]/collection-[collection]/export', + { + region: page.params.region, + project: page.params.project, + database: page.params.database, + collection: page.params.collection + } + ); return queryParam ? `${url}?query=${encodeURIComponent(queryParam)}` : url; } @@ -206,6 +216,7 @@ disabled={!data.documents.total || $isCollectionsJsonImportInProgress} on:click={() => { + trackEvent(Click.DatabaseExportCsv); goto(getExportUrl()); }}> From 36336c599015578fd22d027c3e5c86c4b4e13d57 Mon Sep 17 00:00:00 2001 From: Harsh Mahajan Date: Mon, 30 Mar 2026 17:38:29 +0530 Subject: [PATCH 50/73] fix errors due to sdk --- bun.lock | 4 ++-- package.json | 2 +- src/lib/components/git/repositories.svelte | 9 ++------- src/lib/components/git/selectRootModal.svelte | 6 +++--- .../create-function/repository-[repository]/+page.svelte | 7 +------ .../functions/function-[function]/store.ts | 2 +- .../repositories/repository-[repository]/+page.svelte | 7 ++++++- 7 files changed, 16 insertions(+), 21 deletions(-) diff --git a/bun.lock b/bun.lock index 6bd01b5fb..dd34a80e7 100644 --- a/bun.lock +++ b/bun.lock @@ -6,7 +6,7 @@ "name": "@appwrite/console", "dependencies": { "@ai-sdk/svelte": "^1.1.24", - "@appwrite.io/console": "https://pkg.vc/-/@appwrite/@appwrite.io/console@f151724", + "@appwrite.io/console": "https://pkg.vc/-/@appwrite/@appwrite.io/console@d223f36", "@appwrite.io/pink-icons": "0.25.0", "@appwrite.io/pink-icons-svelte": "https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@bfe7ce3", "@appwrite.io/pink-legacy": "^1.0.3", @@ -113,7 +113,7 @@ "@analytics/type-utils": ["@analytics/type-utils@0.6.4", "", {}, "sha512-Ou1gQxFakOWLcPnbFVsrPb8g1wLLUZYYJXDPjHkG07+5mustGs5yqACx42UAu4A6NszNN6Z5gGxhyH45zPWRxw=="], - "@appwrite.io/console": ["@appwrite.io/console@https://pkg.vc/-/@appwrite/@appwrite.io/console@f151724", { "dependencies": { "json-bigint": "1.0.0" } }], + "@appwrite.io/console": ["@appwrite.io/console@https://pkg.vc/-/@appwrite/@appwrite.io/console@d223f36", { "dependencies": { "json-bigint": "1.0.0" } }], "@appwrite.io/pink-icons": ["@appwrite.io/pink-icons@0.25.0", "", {}, "sha512-0O3i2oEuh5mWvjO80i+X6rbzrWLJ1m5wmv2/M3a1p2PyBJsFxN8xQMTEmTn3Wl/D26SsM7SpzbdW6gmfgoVU9Q=="], diff --git a/package.json b/package.json index e096082b2..588c69793 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,7 @@ }, "dependencies": { "@ai-sdk/svelte": "^1.1.24", - "@appwrite.io/console": "https://pkg.vc/-/@appwrite/@appwrite.io/console@f151724", + "@appwrite.io/console": "https://pkg.vc/-/@appwrite/@appwrite.io/console@d223f36", "@appwrite.io/pink-icons": "0.25.0", "@appwrite.io/pink-icons-svelte": "https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@bfe7ce3", "@appwrite.io/pink-legacy": "^1.0.3", diff --git a/src/lib/components/git/repositories.svelte b/src/lib/components/git/repositories.svelte index 8104efd5b..df2c80050 100644 --- a/src/lib/components/git/repositories.svelte +++ b/src/lib/components/git/repositories.svelte @@ -138,13 +138,8 @@ $repositories.repositories = product === 'functions' - ? ( - result as unknown as { - runtimeProviderRepositories: (Models.ProviderRepository & { - runtime: string; - })[]; - } - ).runtimeProviderRepositories + ? (result as unknown as Models.ProviderRepositoryRuntimeList) + .runtimeProviderRepositories : (result as unknown as Models.ProviderRepositoryFrameworkList) .frameworkProviderRepositories; //TODO: remove forced cast after backend fixes $repositories.total = result.total; diff --git a/src/lib/components/git/selectRootModal.svelte b/src/lib/components/git/selectRootModal.svelte index 22437ef88..d30665015 100644 --- a/src/lib/components/git/selectRootModal.svelte +++ b/src/lib/components/git/selectRootModal.svelte @@ -5,7 +5,7 @@ import { iconPath } from '$lib/stores/app'; import { sdk } from '$lib/stores/sdk'; import { installation, repository } from '$lib/stores/vcs'; - import { VCSDetectionType } from '@appwrite.io/console'; + import { VCSDetectionType, type Models } from '@appwrite.io/console'; import DirectoryPicker from '$lib/components/git/DirectoryPicker.svelte'; import { writable } from 'svelte/store'; @@ -112,8 +112,8 @@ const iconName = product === 'sites' - ? detection.framework - : (detection as unknown as { runtime: string }).runtime; + ? (detection as unknown as Models.DetectionFramework).framework + : (detection as unknown as Models.DetectionRuntime).runtime; const resolved = resolveIconUrl(iconName); iconCache.set(path, resolved); return resolved; diff --git a/src/routes/(console)/project-[region]-[project]/functions/create-function/repository-[repository]/+page.svelte b/src/routes/(console)/project-[region]-[project]/functions/create-function/repository-[repository]/+page.svelte index eb06a98fa..58d29ad20 100644 --- a/src/routes/(console)/project-[region]-[project]/functions/create-function/repository-[repository]/+page.svelte +++ b/src/routes/(console)/project-[region]-[project]/functions/create-function/repository-[repository]/+page.svelte @@ -84,12 +84,7 @@ installationId: data.installation.$id, providerRepositoryId: page.params.repository, type: VCSDetectionType.Runtime - })) as unknown as { - entrypoint: string; - commands: string; - runtime: string; - variables: { name: string; value: string }[]; - }; /* SDK return type is wrong atm */ + })) as unknown as Models.DetectionRuntime; entrypoint = detections.entrypoint; buildCommand = detections.commands; diff --git a/src/routes/(console)/project-[region]-[project]/functions/function-[function]/store.ts b/src/routes/(console)/project-[region]-[project]/functions/function-[function]/store.ts index e45c815a4..42bf293ce 100644 --- a/src/routes/(console)/project-[region]-[project]/functions/function-[function]/store.ts +++ b/src/routes/(console)/project-[region]-[project]/functions/function-[function]/store.ts @@ -21,7 +21,7 @@ export const repositories: Writable<{ repositories: | Models.ProviderRepository[] | Models.ProviderRepositoryFramework[] - | (Models.ProviderRepository & { runtime: string })[]; + | Models.ProviderRepositoryRuntime[]; }> = writable({ search: '', installationId: '', diff --git a/src/routes/(console)/project-[region]-[project]/sites/create-site/repositories/repository-[repository]/+page.svelte b/src/routes/(console)/project-[region]-[project]/sites/create-site/repositories/repository-[repository]/+page.svelte index 155503e0a..2e65230c0 100644 --- a/src/routes/(console)/project-[region]-[project]/sites/create-site/repositories/repository-[repository]/+page.svelte +++ b/src/routes/(console)/project-[region]-[project]/sites/create-site/repositories/repository-[repository]/+page.svelte @@ -75,7 +75,12 @@ type: VCSDetectionType.Framework, providerRootDirectory: rootDir }); - framework = data.frameworks.frameworks.find((f) => f.key === response.framework); + framework = data.frameworks.frameworks.find( + (f) => f.key === (response as unknown as Models.DetectionFramework).framework + ); + if (!framework) { + framework = data.frameworks.frameworks.find((f) => f.key === 'other'); + } adapter = framework?.adapters[0]; installCommand = adapter?.installCommand; buildCommand = adapter?.buildCommand; From 57a27a0578cce445b2c3ed66cb6995b3b30aec82 Mon Sep 17 00:00:00 2001 From: Harsh Mahajan <127186841+HarshMN2345@users.noreply.github.com> Date: Mon, 30 Mar 2026 17:43:56 +0530 Subject: [PATCH 51/73] Update src/routes/(console)/organization-[organization]/domains/domain-[domain]/createRecordModal.svelte Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> --- .../domains/domain-[domain]/createRecordModal.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/(console)/organization-[organization]/domains/domain-[domain]/createRecordModal.svelte b/src/routes/(console)/organization-[organization]/domains/domain-[domain]/createRecordModal.svelte index 5e80c9bde..628aa7f55 100644 --- a/src/routes/(console)/organization-[organization]/domains/domain-[domain]/createRecordModal.svelte +++ b/src/routes/(console)/organization-[organization]/domains/domain-[domain]/createRecordModal.svelte @@ -111,7 +111,7 @@ -
+
TTL defines how long DNS information is cached. Lower values update faster; higher values reduce server load.
From d7ee5ce0071ed1d84c6879b855533d6a780f7fc7 Mon Sep 17 00:00:00 2001 From: Harsh Mahajan <127186841+HarshMN2345@users.noreply.github.com> Date: Mon, 30 Mar 2026 17:49:54 +0530 Subject: [PATCH 52/73] Update createRecordModal.svelte --- .../domains/domain-[domain]/createRecordModal.svelte | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/routes/(console)/organization-[organization]/domains/domain-[domain]/createRecordModal.svelte b/src/routes/(console)/organization-[organization]/domains/domain-[domain]/createRecordModal.svelte index 628aa7f55..5e7ac0e56 100644 --- a/src/routes/(console)/organization-[organization]/domains/domain-[domain]/createRecordModal.svelte +++ b/src/routes/(console)/organization-[organization]/domains/domain-[domain]/createRecordModal.svelte @@ -11,6 +11,9 @@ import { IconInfo } from '@appwrite.io/pink-icons-svelte'; import { addNotification } from '$lib/stores/notifications'; import { Submit, trackError, trackEvent } from '$lib/actions/analytics'; + import { + BODY_TOOLTIP_WRAPPER_STYLE + } from '$lib/helpers/tooltipContent'; import { page } from '$app/state'; import { recordTypes } from './store'; import { Dependencies } from '$lib/constants'; From 7b4cbcfc232ef173bc8076bd29ac5b99a7e7cc54 Mon Sep 17 00:00:00 2001 From: Harsh Mahajan <127186841+HarshMN2345@users.noreply.github.com> Date: Mon, 30 Mar 2026 17:50:49 +0530 Subject: [PATCH 53/73] Update createRecordModal.svelte --- .../domains/domain-[domain]/createRecordModal.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/(console)/organization-[organization]/domains/domain-[domain]/createRecordModal.svelte b/src/routes/(console)/organization-[organization]/domains/domain-[domain]/createRecordModal.svelte index 5e7ac0e56..bb30c0ac8 100644 --- a/src/routes/(console)/organization-[organization]/domains/domain-[domain]/createRecordModal.svelte +++ b/src/routes/(console)/organization-[organization]/domains/domain-[domain]/createRecordModal.svelte @@ -12,7 +12,7 @@ import { addNotification } from '$lib/stores/notifications'; import { Submit, trackError, trackEvent } from '$lib/actions/analytics'; import { - BODY_TOOLTIP_WRAPPER_STYLE + BODY_TOOLTIP_WRAPPER_STYLE_PRELINE } from '$lib/helpers/tooltipContent'; import { page } from '$app/state'; import { recordTypes } from './store'; From 4e7e438dbc034fce9b8834a406ec442cee342f9a Mon Sep 17 00:00:00 2001 From: Harsh Mahajan Date: Mon, 30 Mar 2026 18:06:07 +0530 Subject: [PATCH 54/73] bun run format --- .../domains/domain-[domain]/createRecordModal.svelte | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/routes/(console)/organization-[organization]/domains/domain-[domain]/createRecordModal.svelte b/src/routes/(console)/organization-[organization]/domains/domain-[domain]/createRecordModal.svelte index bb30c0ac8..77e6e5772 100644 --- a/src/routes/(console)/organization-[organization]/domains/domain-[domain]/createRecordModal.svelte +++ b/src/routes/(console)/organization-[organization]/domains/domain-[domain]/createRecordModal.svelte @@ -11,9 +11,7 @@ import { IconInfo } from '@appwrite.io/pink-icons-svelte'; import { addNotification } from '$lib/stores/notifications'; import { Submit, trackError, trackEvent } from '$lib/actions/analytics'; - import { - BODY_TOOLTIP_WRAPPER_STYLE_PRELINE - } from '$lib/helpers/tooltipContent'; + import { BODY_TOOLTIP_WRAPPER_STYLE_PRELINE } from '$lib/helpers/tooltipContent'; import { page } from '$app/state'; import { recordTypes } from './store'; import { Dependencies } from '$lib/constants'; From de112e363c4c92fcf300b0f61bbd6a5bacbfba60 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Tue, 31 Mar 2026 15:45:02 +1300 Subject: [PATCH 55/73] (chore): add agents files --- AGENTS.md | 321 ++++++++++++++++++++++++++++++++++++------------------ CLAUDE.md | 1 + 2 files changed, 214 insertions(+), 108 deletions(-) create mode 100644 CLAUDE.md diff --git a/AGENTS.md b/AGENTS.md index 726ddb0b5..9e091bf2e 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,138 +1,243 @@ -# Appwrite Console - Copilot Instructions +# Appwrite Console -## Repository Overview +SvelteKit web dashboard for Appwrite. Manages projects, databases, functions, auth, storage, messaging, and sites. Static SPA (no SSR) served behind Nginx at `/console`. -Appwrite Console is the web-based GUI for the Appwrite backend-as-a-service platform. Single-page application built with -**Svelte 5 + SvelteKit 2**, **TypeScript** (not strict mode), **Vite 7**, tested with **Vitest + Playwright**. Package -manager/runtime: **Bun** (Node 20+ optional for tooling). ~1500 files with extensive component-based architecture. +## Commands -## Critical Build & Test Commands +All commands use **bun** (not pnpm/npm). -### Setup (REQUIRED before any commands) +| Command | Purpose | +| --------------------- | --------------------------------------------- | +| `bun dev` | Dev server (port 3000) | +| `bun run build` | Production build (custom `build.js` via Vite) | +| `bun check` | `svelte-kit sync && svelte-check` | +| `bun format` | Prettier write + cache | +| `bun run lint` | Prettier check + ESLint | +| `bun tests` | Unit + E2E | +| `bun test:unit` | Vitest (TZ=EST) | +| `bun test:unit-watch` | Vitest watch mode | +| `bun test:e2e` | Playwright | +| `bun run clean` | Remove node_modules, .svelte-kit, reinstall | -1. **Install Bun**: - - Linux & macOS: `curl -fsSL https://bun.sh/install | bash` - - Windows: `powershell -c "irm bun.sh/install.ps1 | iex"` -2. **Create .env**: `cp .env.example .env` (configure `PUBLIC_APPWRITE_ENDPOINT` and `PUBLIC_CONSOLE_MODE`) -3. **Configure network access** (if using GitHub Actions or restricted environments): - - Ensure firewall/proxy allows access to: `pkg.pr.new`, `pkg.vc`, `registry.npmjs.org` - - These domains are required for dependencies: `@appwrite.io/console`, `@appwrite.io/pink-icons-svelte`, - `@appwrite.io/pink-svelte` - - In GitHub Actions: Ensure Bun is installed and registry access is configured - - If network errors persist, check proxy settings: `npm config get proxy` and `npm config get https-proxy` -4. **Install dependencies**: `bun install --frozen-lockfile` (if pkg.pr.new/pkg.vc fail due to network restrictions, - installation may still succeed with cached versions) +**Always run before committing:** `bun run format && bun run check && bun run lint && bun run tests && bun run build` -### Development Commands +## CI checks (`.github/workflows/tests.yml`) -**Standard workflow**: `check` → `lint` → `test` → `build` (before committing) +`bun audit --audit-level high` -> `bun check` -> `bun lint` -> `bun test:unit` -> `bun run build`. Uses frozen lockfile. -- `bun run check` - TypeScript/Svelte validation (~30-60s) -- `bun run lint` - ESLint check (~10-20s) -- `bun run format` - Auto-fix Prettier formatting -- `bun run test` - Vitest unit tests with TZ=EST (~10-30s) -- `bun run build` - Production build via build.js (~60-120s) -- `bun run dev` - Dev server on port 3000 -- `bun run preview` - Preview build on port 4173 -- `bun run e2e` - Playwright tests (needs `bunx playwright install --with-deps chromium` first, ~120s+) +## Stack -**CI Pipeline** (`.github/workflows/tests.yml`): audit → install → check → lint → test → build +- **Framework:** SvelteKit 2 + Svelte 5, TypeScript (strict: false), `@sveltejs/adapter-static` +- **Bundler:** Vite 7 (overridden to `rolldown-vite`) +- **Design system:** `@appwrite.io/pink-svelte`, `@appwrite.io/pink-icons-svelte` +- **UI primitives:** Melt UI (`@melt-ui/svelte` with preprocessor) +- **API client:** `@appwrite.io/console` SDK (pinned to GitHub commit) +- **Code editing:** CodeMirror 6 +- **Charts:** ECharts 5 +- **3D:** Three.js via Threlte +- **Payments:** Stripe +- **AI:** Vercel AI SDK (`@ai-sdk/svelte`) +- **Testing:** Vitest + @testing-library/svelte (unit), Playwright (E2E) +- **Error tracking:** Sentry (`@sentry/sveltekit`) +- **Analytics:** Plausible + custom Growth endpoint -## Project Structure +## Architecture -``` -src/ -├── lib/ # Reusable logic ($lib alias) -│ ├── components/ # Feature components (billing, domains, permissions, etc.) -│ ├── elements/ # Basic UI elements -│ ├── helpers/ # Utility functions (array, date, string, etc.) -│ ├── stores/ # Svelte stores for state -│ ├── sdk/ # Appwrite SDK wrappers -│ └── constants.ts, flags.ts, system.ts -├── routes/ -│ ├── (console)/ # Auth-required routes -│ │ ├── organization-[organization]/ -│ │ └── project-[region]-[project]/ # databases, functions, messaging, storage -│ └── (public)/ # Public routes (login, register, auth callbacks) -├── themes/ # Theme definitions ($themes alias) -└── app.html, hooks.{client,server}.ts, service-worker.ts +### Route structure (`src/routes/`) + +SvelteKit file-based routing with layout groups: + +- `(public)/` -- unauthenticated routes: `(guest)/` (login, register), auth (OAuth, magic URL), invite, recover, card, functions/sites deploy, hackathon, templates +- `(console)/` -- authenticated console (projects, orgs, account, onboarding) +- `(authenticated)/` -- post-login flows (MFA, Git authorization) + +Dynamic segments: `project-[region]-[project]`, `organization-[organization]` + +### Route file conventions + +Each route can have: + +- `+page.svelte` -- page component +- `+page.ts` -- client-side load function +- `+layout.svelte` / `+layout.ts` -- layout wrappers +- `store.ts` -- route-scoped state +- Feature components colocated alongside (e.g. `table.svelte`, `create.svelte`) + +### Path aliases + +| Alias | Path | +| ----------- | ------------------------------------------------------------------------------- | +| `$lib` | `src/lib` (SvelteKit built-in) | +| `$routes` | `src/routes` | +| `$themes` | `src/themes` | +| `$database` | `src/routes/(console)/project-[region]-[project]/databases/database-[database]` | + +### Library (`src/lib/`) + +| Directory | Contents | +| ----------------- | ------------------------------------------------------------------------------------------ | +| `components/` | Feature components (billing, permissions, filters, etc.) -- barrel-exported via `index.ts` | +| `elements/forms/` | Form inputs (text, email, phone, OTP, file, geometry, etc.) | +| `elements/table/` | Table components | +| `layout/` | Shell, Container, Wizard, Breadcrumbs, Navigation -- barrel-exported via `index.ts` | +| `stores/` | Svelte stores for global state | +| `helpers/` | Utilities (array, date, object, numbers, string, validation) | +| `sdk/` | Custom SDK extensions (billing, usage, sources) | +| `actions/` | Svelte actions and analytics tracking | +| `charts/` | Chart visualization components -- barrel-exported via `index.ts` | +| `commandCenter/` | Command palette | +| `images/` | SVG assets (logos, illustrations, empty states) | +| `data/` | Static data (testimonials) | +| `profiles/` | CSS profiles and theming | +| `mock/` | Mock data for development | + +### Imports + +Components use **barrel exports** -- always import from the directory `index.ts`: + +```typescript +import { Card, Modal, Steps } from '$lib/components'; +import { Shell, Container } from '$lib/layout'; +import { InputText, Button, Form } from '$lib/elements/forms'; ``` -**SvelteKit conventions**: `+page.svelte` (component), `+page.ts` (data loader), `+layout.svelte` (wrapper), -`+error.svelte` (errors). Groups like `(console)` organize routes without affecting URLs. Dynamic params: `[param]`. +### Svelte 5 migration (in progress) -## Key Configuration +~500 files still use legacy Svelte 4 syntax, ~240 migrated to runes. **When touching a file, migrate it to runes if practical.** Don't mix syntaxes within a single component. -**svelte.config.js**: Adapter = static SPA (fallback: index.html), base path `/console`, aliases: `$lib`, `$routes`, -`$themes` -**vite.config.ts**: Dev port 3000 -**tsconfig.json**: Extends `.svelte-kit/tsconfig.json`, **NOT strict mode** (`strict: false`) -**eslint.config.js**: Flat config (ESLint 9+), many rules disabled (see TODOs) -**.prettierrc**: 4 spaces, single quotes, 100 char width, no trailing commas +Legacy (Svelte 4): -## Testing +```svelte + +``` -**Unit (Bun test)**: Tests in `src/lib/helpers/*.test.ts`, run with `TZ=EST` (timezone matters). Setup mocks SvelteKit ( -`$app/*`) in `bun-test-setup.ts` via `bunfig.toml`. -**E2E (Playwright)**: Tests in `e2e/journeys/*.spec.ts`, needs build+preview on port 4173, retries 3x, timeout 120s, -Chromium only. +Runes (Svelte 5 -- preferred for new and modified code): -## Common Pitfalls +```svelte + +``` -## Code Conventions +### SDK usage (`src/lib/stores/sdk.ts`) -- Imports: Use `$lib`, `$routes`, `$themes` aliases -- Components: PascalCase, in `src/lib/components/[feature]/` -- Helpers: Pure functions in `src/lib/helpers/` -- Types: Inline or `.d.ts`, not `.types.ts` files -- Comments: Minimal, use for TODOs or complex logic -- TypeScript: Not strict mode, `any` tolerated +Four client instances: `clientConsole` (console API), `scopedConsoleClient` (region-scoped console API, used by `forConsoleIn()`), `clientProject` (project API, admin mode), `clientRealtime` (realtime subscriptions). Region-aware endpoints with subdomain routing (fra., nyc., syd., sfo., sgp., tor.). -## Workflow +```typescript +import { sdk } from '$lib/stores/sdk'; -1. Run Appwrite backend locally (see [docs](https://appwrite.io/docs/advanced/self-hosting)) -2. Configure `.env` with backend endpoint -3. `bun install --frozen-lockfile` -4. `bun run dev` (hot reload on port 3000) -5. Before commit: `bun run check && bun run format && bun run lint && bun run test && bun run build` -6. **Take screenshots**: For any UI changes, capture screenshots and include them in the PR description or comments - before finalizing +// Console-level operations +await sdk.forConsole.account.get(); -## Required Pre-Completion Checklist +// Region-scoped console operations +await sdk.forConsoleIn(region).projects.get({ projectId }); -**CRITICAL**: Before finishing any work or marking a task complete, agents MUST run the following commands in order and -ensure all pass: +// Project-level operations (admin mode) +await sdk.forProject(region, projectId).tablesDB.listTables(); +``` -1. **`bun run format`** - Auto-fix all formatting issues -2. **`bun run check`** - Verify TypeScript/Svelte types (must show 0 errors, 0 warnings) -3. **`bun run lint`** - Check code style (ignore pre-existing issues in files you didn't modify) -4. **`bun run test`** - Run all unit tests (all tests must pass) -5. **`bun run build`** - Ensure production build succeeds +### Database types (feat-dedicated-db) -If any command fails: +The databases feature unifies four database backends behind a polymorph API (`$database/(entity)/helpers/sdk.ts`): -- **Format/Lint**: Run `bun run format` to auto-fix, then re-check -- **Type errors**: Fix all TypeScript errors in files you modified -- **Test failures**: Fix failing tests or ensure failures are unrelated to your changes -- **Build failures**: Debug and resolve build issues before proceeding +| Type | Entity | Field | Record | +| ------------- | ---------- | --------- | -------- | +| `tablesdb` | table | column | row | +| `documentsdb` | collection | attribute | document | +| `vectorsdb` | collection | attribute | document | +| `dedicateddb` | table | column | row | -**Never skip these checks** - they are mandatory quality gates before any work is considered complete. +- `useDatabaseSdk()` returns a unified interface regardless of backing type +- `useTerminology()` returns singular/plural names for the current database type +- DedicatedDB uses the `Compute` service (not Appwrite schema APIs) -- creates always-on PostgreSQL/MySQL instances via `compute.createDatabase()` with `Backend.Edge` -**Trust these instructions** - only search if incomplete/incorrect. See CONTRIBUTING.md for PR conventions. Use -`--frozen-lockfile` always. Docker builds: multi-stage, final image is nginx serving static files from `/console` path. +### Data loading + +Load functions declare dependencies for cache invalidation via `depends()`: + +```typescript +export const load: LayoutLoad = async ({ depends, parent, params }) => { + depends(Dependencies.DATABASE); + return { database: await sdk.forProject(...).tablesDB.get(...) }; +}; +``` + +Invalidate with `await invalidate(Dependencies.DATABASE)` after mutations. Dependency keys defined in `src/lib/constants.ts` as the `Dependencies` enum. + +### State management + +Stores in `src/lib/stores/` -- writable, derived, and "conservative" (selective update via `createConservative()` from `$lib/helpers/stores`) patterns. Key stores: `app`, `user`, `organization`, `projects`, `billing`, `wizard`, `notifications`, `sdk`. + +### Wizard pattern (`$lib/stores/wizard`) + +Modal wizard flow: `wizard.start(Component, media?, step?, props?)` to open, `wizard.hide()` to close. Methods: `setInterceptor(callback)` for async pre-step validation, `setNextDisabled(bool)` for flow control, `setStep(n)` / `updateStep(cb)` for navigation, `showCover(Component)` for overlays. + +### Notifications (`$lib/stores/notifications`) + +```typescript +import { addNotification } from '$lib/stores/notifications'; +addNotification({ type: 'error', message: error.message }); +``` + +Types: `'success' | 'error' | 'info' | 'warning'`. Auto-dismisses after 6s. Max 5 visible. + +### Analytics (`$lib/actions/analytics`) + +Plausible + custom Growth endpoint. Track events via `trackEvent(Click.* | Submit.*, data)` and errors via `trackError(exception, Submit.*)`. Respects `navigator.doNotTrack`. + +### Theming and modes + +Four theme variants in `src/themes/`: `light`, `dark`, `light-cloud`, `dark-cloud`. Resolved based on `isCloud` flag and user preference. Two modes (`src/lib/system.ts`): `cloud` and `self-hosted`, set via `PUBLIC_CONSOLE_MODE` env var. Gate cloud-only features with `isCloud`. + +## Code style + +- **Formatter:** Prettier -- 4 spaces, single quotes, no trailing commas, 100 char width, bracket same line +- **Prefer Svelte 5 runes** in new and modified code (`$props()`, `$state()`, `$derived()`, `$effect()`) +- Types from `@appwrite.io/console` SDK (`Models`, `Query`, enums) -- don't redefine what the SDK provides +- Error handling: try/catch with `addNotification()` for user-facing errors, `trackError()` for analytics +- Queries use the SDK's `Query` builder: `Query.equal()`, `Query.limit()`, `Query.offset()`, etc. +- Mark tech debt with `@todo` annotations, never `@fixme` +- Don't add new dependencies without consulting the team + +## Environment variables + +Set via `.env` (copy `.env.example`). All prefixed with `PUBLIC_` for SvelteKit: + +| Variable | Default | Purpose | +| ------------------------------------ | --------------------- | ------------------------------ | +| `PUBLIC_CONSOLE_MODE` | `self-hosted` | `cloud` or `self-hosted` | +| `PUBLIC_APPWRITE_ENDPOINT` | `http://localhost/v1` | API endpoint | +| `PUBLIC_APPWRITE_MULTI_REGION` | `false` | Multi-region support | +| `PUBLIC_STRIPE_KEY` | -- | Stripe public key (cloud only) | +| `PUBLIC_GROWTH_ENDPOINT` | -- | Analytics endpoint | +| `PUBLIC_CONSOLE_FEATURE_FLAGS` | -- | Feature flags | +| `PUBLIC_CONSOLE_EMAIL_VERIFICATION` | `false` | Require email verification | +| `PUBLIC_CONSOLE_MOCK_AI_SUGGESTIONS` | `true` | Mock AI in dev | + +## Common pitfalls + +- **Blank page in dev:** Disable ad blockers if seeing "Failed to fetch dynamically imported module" +- **OOM on build:** Set `NODE_OPTIONS=--max_old_space_size=8192` +- **Test failures:** Always use `bun run tests` (runs test:unit with TZ=EST, plus test:e2e), not `bun test` directly +- **TS errors not showing:** Run `bun run check` explicitly (dev server doesn't always surface them) +- **Format vs lint conflicts:** Run `bun run format` before `bun run lint` +- **Stale build:** Clear `.svelte-kit` if changes not reflected: `rm -rf .svelte-kit && bun run build` + +## Branch naming + +`TYPE-ISSUE_ID-DESCRIPTION` (e.g. `feat-548-add-backup-ui`). Types: feat, fix, doc, cicd, refactor. + +## Cross-repo context + +The `feat-dedicated-db` feature spans cloud, edge, and console. When modifying API contracts or response models, check the other repos for breaking changes. diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 000000000..43c994c2d --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1 @@ +@AGENTS.md From 66f52ce36cfc50a358a5435bae18d48a9b0d3aa4 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Tue, 31 Mar 2026 16:09:05 +1300 Subject: [PATCH 56/73] fix: address review bot comments on AGENTS.md and CLAUDE.md - Standardize `bun run` prefix across all commands table entries - Fix database types table: mark vectorsdb as not yet implemented, dedicateddb as cross-repo - Remove inaccurate DedicatedDB/Compute service claim from console docs - Add $bindable() usage context in Svelte 5 runes example - Note Dependencies enum has 66+ keys for cache invalidation - Mention isSelfHosted alongside isCloud for feature gating - Add explanatory comment in CLAUDE.md for @AGENTS.md syntax Co-Authored-By: Claude Opus 4.6 (1M context) --- AGENTS.md | 46 +++++++++++++++++++++++----------------------- CLAUDE.md | 1 + 2 files changed, 24 insertions(+), 23 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 9e091bf2e..69919acdf 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -4,20 +4,20 @@ SvelteKit web dashboard for Appwrite. Manages projects, databases, functions, au ## Commands -All commands use **bun** (not pnpm/npm). +All commands use **bun** (not pnpm/npm). Use `bun run diff --git a/src/routes/(console)/+layout.ts b/src/routes/(console)/+layout.ts index 8f628c5a6..a2058f17a 100644 --- a/src/routes/(console)/+layout.ts +++ b/src/routes/(console)/+layout.ts @@ -2,17 +2,17 @@ import { sdk } from '$lib/stores/sdk'; import { isCloud } from '$lib/system'; import type { LayoutLoad } from './$types'; import { Dependencies } from '$lib/constants'; -import { Platform, Query } from '@appwrite.io/console'; +import { AppwriteException, Platform, Query } from '@appwrite.io/console'; import { makePlansMap } from '$lib/helpers/billing'; import { plansInfo as plansInfoStore } from '$lib/stores/billing'; import { normalizeConsoleVariables } from '$lib/helpers/domains'; import { syncServerTime } from '$lib/helpers/fingerprint'; import { redirect } from '@sveltejs/kit'; import { resolve } from '$app/paths'; -import { isEmailVerificationEnabled } from '$lib/helpers/emailVerification'; +import { isEmailVerificationRequiredError } from '$lib/helpers/emailVerification'; -export const load: LayoutLoad = async ({ depends, parent, url }) => { - const { organizations, plansInfo, account } = await parent(); +export const load: LayoutLoad = async ({ depends, parent }) => { + const { organizations, plansInfo } = await parent(); depends(Dependencies.RUNTIMES); depends(Dependencies.CONSOLE_VARIABLES); @@ -41,22 +41,15 @@ export const load: LayoutLoad = async ({ depends, parent, url }) => { return response.json() as { version?: string }; }), sdk.forConsole.console.variables() - ]); - - const consoleVariables = normalizeConsoleVariables(rawConsoleVariables); - - if ( - isCloud && - account && - !account.emailVerification && - isEmailVerificationEnabled(consoleVariables) - ) { - const isVerifyEmailPage = url.pathname === resolve('/verify-email'); - - if (!isVerifyEmailPage) { + ]).catch((error) => { + if (error instanceof AppwriteException && isEmailVerificationRequiredError(error.type)) { redirect(303, resolve('/verify-email')); } - } + + throw error; + }); + + const consoleVariables = normalizeConsoleVariables(rawConsoleVariables); let fallbackPlansInfoArray = plansInfo; if (!fallbackPlansInfoArray) { @@ -81,6 +74,10 @@ export const load: LayoutLoad = async ({ depends, parent, url }) => { }) ).total; } catch (e) { + if (e instanceof AppwriteException && isEmailVerificationRequiredError(e.type)) { + redirect(303, resolve('/verify-email')); + } + projectsCount = 0; } } diff --git a/src/routes/+layout.ts b/src/routes/+layout.ts index 41d8c3e17..9db343ecb 100644 --- a/src/routes/+layout.ts +++ b/src/routes/+layout.ts @@ -8,12 +8,13 @@ import type { LayoutLoad } from './$types'; import { redirectTo } from './store'; import { resolve } from '$app/paths'; import type { Account } from '$lib/stores/user'; -import { type AppwriteException, Platform } from '@appwrite.io/console'; +import { AppwriteException, Platform } from '@appwrite.io/console'; import { isCloud } from '$lib/system'; import { checkPricingRefAndRedirect } from '$lib/helpers/pricingRedirect'; import { getTeamOrOrganizationList } from '$lib/stores/organization'; import { makePlansMap } from '$lib/helpers/billing'; import { plansInfo as plansInfoStore } from '$lib/stores/billing'; +import { isEmailVerificationRequiredError } from '$lib/helpers/emailVerification'; export const ssr = false; @@ -32,14 +33,33 @@ export const load: LayoutLoad = async ({ depends, url, route }) => { } if (account) { - const plansInfo = await getPlatformPlans(); - plansInfoStore.set(plansInfo); + try { + const [plansInfo, organizations] = await Promise.all([ + getPlatformPlans(), + getTeamOrOrganizationList() + ]); - return { - plansInfo, - account: account, - organizations: await getTeamOrOrganizationList() - }; + plansInfoStore.set(plansInfo); + + return { + plansInfo, + account: account, + organizations + }; + } catch (error) { + if ( + error instanceof AppwriteException && + isEmailVerificationRequiredError(error.type) + ) { + const verifyEmailUrl = resolve('/verify-email'); + + if (url.pathname !== verifyEmailUrl) { + redirect(303, withParams(verifyEmailUrl, url.searchParams)); + } + } + + throw error; + } } const isPublicRoute = route.id?.startsWith('/(public)'); @@ -57,6 +77,14 @@ export const load: LayoutLoad = async ({ depends, url, route }) => { redirect(303, withParams(mfaUrl, url.searchParams)); } + if (isEmailVerificationRequiredError(error.type)) { + const verifyEmailUrl = resolve('/verify-email'); + + if (url.pathname !== verifyEmailUrl) { + redirect(303, withParams(verifyEmailUrl, url.searchParams)); + } + } + if (!isPublicRoute) { if (isCloud) { checkPricingRefAndRedirect(url.searchParams, true); From 0e963cdfe4924a38a191b7a56c46186ba3140cdb Mon Sep 17 00:00:00 2001 From: Harsh Mahajan Date: Thu, 2 Apr 2026 14:04:48 +0530 Subject: [PATCH 67/73] remove llop --- src/routes/(console)/+layout.ts | 15 ++++++++++----- src/routes/(console)/verify-email/+page.ts | 9 +-------- 2 files changed, 11 insertions(+), 13 deletions(-) diff --git a/src/routes/(console)/+layout.ts b/src/routes/(console)/+layout.ts index a2058f17a..e80e95de2 100644 --- a/src/routes/(console)/+layout.ts +++ b/src/routes/(console)/+layout.ts @@ -11,7 +11,7 @@ import { redirect } from '@sveltejs/kit'; import { resolve } from '$app/paths'; import { isEmailVerificationRequiredError } from '$lib/helpers/emailVerification'; -export const load: LayoutLoad = async ({ depends, parent }) => { +export const load: LayoutLoad = async ({ depends, parent, url }) => { const { organizations, plansInfo } = await parent(); depends(Dependencies.RUNTIMES); @@ -19,6 +19,11 @@ export const load: LayoutLoad = async ({ depends, parent }) => { depends(Dependencies.ORGANIZATION); const { endpoint, project } = sdk.forConsole.client.config; + const verifyEmailUrl = resolve('/verify-email'); + const shouldRedirectToVerifyEmail = (error: unknown) => + error instanceof AppwriteException && + isEmailVerificationRequiredError(error.type) && + url.pathname !== verifyEmailUrl; const plansArrayPromise = plansInfo || !isCloud @@ -42,8 +47,8 @@ export const load: LayoutLoad = async ({ depends, parent }) => { }), sdk.forConsole.console.variables() ]).catch((error) => { - if (error instanceof AppwriteException && isEmailVerificationRequiredError(error.type)) { - redirect(303, resolve('/verify-email')); + if (shouldRedirectToVerifyEmail(error)) { + redirect(303, verifyEmailUrl); } throw error; @@ -74,8 +79,8 @@ export const load: LayoutLoad = async ({ depends, parent }) => { }) ).total; } catch (e) { - if (e instanceof AppwriteException && isEmailVerificationRequiredError(e.type)) { - redirect(303, resolve('/verify-email')); + if (shouldRedirectToVerifyEmail(e)) { + redirect(303, verifyEmailUrl); } projectsCount = 0; diff --git a/src/routes/(console)/verify-email/+page.ts b/src/routes/(console)/verify-email/+page.ts index f74bc448c..c97cca8dc 100644 --- a/src/routes/(console)/verify-email/+page.ts +++ b/src/routes/(console)/verify-email/+page.ts @@ -4,16 +4,9 @@ import type { PageLoad } from './$types'; import { Dependencies } from '$lib/constants'; import { sdk } from '$lib/stores/sdk'; import { addNotification } from '$lib/stores/notifications'; -import { isEmailVerificationEnabled } from '$lib/helpers/emailVerification'; export const load: PageLoad = async ({ parent, depends, url }) => { - const { account, consoleVariables } = await parent(); - - const emailVerificationEnabled = isEmailVerificationEnabled(consoleVariables); - - if (!emailVerificationEnabled) { - redirect(303, resolve('/')); - } + const { account } = await parent(); depends(Dependencies.ACCOUNT); From 6110c0c22bf146020b90ba3fe16123b4b93efeec Mon Sep 17 00:00:00 2001 From: Harsh Mahajan Date: Thu, 2 Apr 2026 14:27:44 +0530 Subject: [PATCH 68/73] fix --- src/hooks.client.ts | 5 ++- src/lib/helpers/emailVerification.ts | 30 +++++++++++++- src/routes/(console)/+error.svelte | 4 +- src/routes/(console)/+layout.ts | 58 ++++++++++++++++++++++++---- src/routes/+layout.ts | 48 ++++++++++++++++------- 5 files changed, 118 insertions(+), 27 deletions(-) diff --git a/src/hooks.client.ts b/src/hooks.client.ts index 4a6565025..44da3b907 100644 --- a/src/hooks.client.ts +++ b/src/hooks.client.ts @@ -1,6 +1,7 @@ import * as Sentry from '@sentry/sveltekit'; import { isCloud, isProd } from '$lib/system'; import { AppwriteException } from '@appwrite.io/console'; +import { isVerifyEmailRedirectError } from '$lib/helpers/emailVerification'; import type { HandleClientError } from '@sveltejs/kit'; Sentry.init({ @@ -12,7 +13,9 @@ Sentry.init({ }); export const handleError: HandleClientError = ({ error, message, status }) => { - console.error(error); + if (!isVerifyEmailRedirectError(error)) { + console.error(error); + } let type; if (error instanceof AppwriteException) { diff --git a/src/lib/helpers/emailVerification.ts b/src/lib/helpers/emailVerification.ts index 0ff81bf74..f4dba2417 100644 --- a/src/lib/helpers/emailVerification.ts +++ b/src/lib/helpers/emailVerification.ts @@ -1,6 +1,7 @@ -import type { Models } from '@appwrite.io/console'; +import { AppwriteException, type Models } from '@appwrite.io/console'; const EMAIL_VERIFICATION_REQUIRED_ERROR_TYPE = 'user_email_not_verified'; +const CONSOLE_ACCOUNT_VERIFICATION_REQUIRED_TYPE = 'console_account_verification_required'; export function isEmailVerificationEnabled( consoleVariables: Models.ConsoleVariables | undefined @@ -15,3 +16,30 @@ export function isEmailVerificationEnabled( export function isEmailVerificationRequiredError(type: string | undefined): boolean { return type === EMAIL_VERIFICATION_REQUIRED_ERROR_TYPE; } + +function matchesVerifyEmailGate(type: string | undefined, message: string | undefined): boolean { + if (isEmailVerificationRequiredError(type)) return true; + if (type === CONSOLE_ACCOUNT_VERIFICATION_REQUIRED_TYPE) return true; + if (message?.includes('Console account verification is required')) return true; + return false; +} + +/** + * Console APIs may return `user_email_not_verified`, `console_account_verification_required`, + * or a message-only error when email verification is enforced for the console account. + */ +export function isVerifyEmailRedirectError(error: unknown): boolean { + if (error instanceof AppwriteException) { + return matchesVerifyEmailGate(error.type, error.message); + } + if (typeof error === 'object' && error !== null && 'message' in error) { + const msg = (error as { message: unknown }).message; + if (typeof msg !== 'string') return false; + const typ = + 'type' in error && typeof (error as { type: unknown }).type === 'string' + ? (error as { type: string }).type + : undefined; + return matchesVerifyEmailGate(typ, msg); + } + return false; +} diff --git a/src/routes/(console)/+error.svelte b/src/routes/(console)/+error.svelte index 6bdd3495b..57691dd85 100644 --- a/src/routes/(console)/+error.svelte +++ b/src/routes/(console)/+error.svelte @@ -3,12 +3,12 @@ import { base, resolve } from '$app/paths'; import { page } from '$app/state'; import { Button } from '$lib/elements/forms'; - import { isEmailVerificationRequiredError } from '$lib/helpers/emailVerification'; + import { isVerifyEmailRedirectError } from '$lib/helpers/emailVerification'; import { Container } from '$lib/layout'; import { Typography } from '@appwrite.io/pink-svelte'; $effect(() => { - if (isEmailVerificationRequiredError(page.error.type)) { + if (isVerifyEmailRedirectError(page.error)) { goto(resolve('/verify-email'), { replaceState: true }); } }); diff --git a/src/routes/(console)/+layout.ts b/src/routes/(console)/+layout.ts index e80e95de2..2904bb5c6 100644 --- a/src/routes/(console)/+layout.ts +++ b/src/routes/(console)/+layout.ts @@ -1,29 +1,71 @@ import { sdk } from '$lib/stores/sdk'; import { isCloud } from '$lib/system'; import type { LayoutLoad } from './$types'; +import type { Account } from '$lib/stores/user'; import { Dependencies } from '$lib/constants'; -import { AppwriteException, Platform, Query } from '@appwrite.io/console'; +import { Platform, Query, type Models } from '@appwrite.io/console'; import { makePlansMap } from '$lib/helpers/billing'; import { plansInfo as plansInfoStore } from '$lib/stores/billing'; import { normalizeConsoleVariables } from '$lib/helpers/domains'; import { syncServerTime } from '$lib/helpers/fingerprint'; import { redirect } from '@sveltejs/kit'; import { resolve } from '$app/paths'; -import { isEmailVerificationRequiredError } from '$lib/helpers/emailVerification'; +import { isVerifyEmailRedirectError } from '$lib/helpers/emailVerification'; export const load: LayoutLoad = async ({ depends, parent, url }) => { - const { organizations, plansInfo } = await parent(); + const parentData = await parent(); + const { organizations, plansInfo } = parentData; + const account = parentData.account as Account | undefined; + + const { endpoint, project } = sdk.forConsole.client.config; + const verifyEmailUrl = resolve('/verify-email'); + + // While unverified, several console APIs (not only teams) may return 401; avoid failing the layout. + if (url.pathname === verifyEmailUrl && account && !account.emailVerification) { + depends(Dependencies.RUNTIMES); + depends(Dependencies.CONSOLE_VARIABLES); + depends(Dependencies.ORGANIZATION); + + const [preferences, rawConsoleVariables, versionData] = await Promise.all([ + sdk.forConsole.account.getPrefs(), + sdk.forConsole.console.variables().catch(() => ({}) as Models.ConsoleVariables), + fetch(`${endpoint}/health/version`, { + headers: { 'X-Appwrite-Project': project as string } + }) + .then(async (response) => { + const dateHeader = response.headers.get('Date'); + const parsed = dateHeader ? new Date(dateHeader).getTime() : NaN; + if (Number.isFinite(parsed)) { + syncServerTime(Math.floor(parsed / 1000)); + } + return response.json() as { version?: string }; + }) + .catch(() => null) + ]); + + const consoleVariables = normalizeConsoleVariables(rawConsoleVariables); + + plansInfoStore.set(plansInfo ?? null); + + return { + roles: [], + scopes: [], + preferences, + currentOrgId: undefined, + organizations, + consoleVariables, + allProjectsCount: 0, + plansInfo: plansInfo ?? null, + version: versionData?.version ?? null + }; + } depends(Dependencies.RUNTIMES); depends(Dependencies.CONSOLE_VARIABLES); depends(Dependencies.ORGANIZATION); - const { endpoint, project } = sdk.forConsole.client.config; - const verifyEmailUrl = resolve('/verify-email'); const shouldRedirectToVerifyEmail = (error: unknown) => - error instanceof AppwriteException && - isEmailVerificationRequiredError(error.type) && - url.pathname !== verifyEmailUrl; + isVerifyEmailRedirectError(error) && url.pathname !== verifyEmailUrl; const plansArrayPromise = plansInfo || !isCloud diff --git a/src/routes/+layout.ts b/src/routes/+layout.ts index 9db343ecb..0f566dc91 100644 --- a/src/routes/+layout.ts +++ b/src/routes/+layout.ts @@ -8,20 +8,24 @@ import type { LayoutLoad } from './$types'; import { redirectTo } from './store'; import { resolve } from '$app/paths'; import type { Account } from '$lib/stores/user'; -import { AppwriteException, Platform } from '@appwrite.io/console'; +import { AppwriteException, Platform, type Models } from '@appwrite.io/console'; import { isCloud } from '$lib/system'; import { checkPricingRefAndRedirect } from '$lib/helpers/pricingRedirect'; import { getTeamOrOrganizationList } from '$lib/stores/organization'; import { makePlansMap } from '$lib/helpers/billing'; import { plansInfo as plansInfoStore } from '$lib/stores/billing'; -import { isEmailVerificationRequiredError } from '$lib/helpers/emailVerification'; +import { isVerifyEmailRedirectError } from '$lib/helpers/emailVerification'; export const ssr = false; +const EMPTY_ORGANIZATIONS: Models.TeamList = { total: 0, teams: [] }; + export const load: LayoutLoad = async ({ depends, url, route }) => { depends(Dependencies.ACCOUNT); depends(Dependencies.ORGANIZATIONS); + const verifyEmailPath = resolve('/verify-email'); + const [account, error] = (await sdk.forConsole.account .get() .then((response) => [response, null]) @@ -33,6 +37,18 @@ export const load: LayoutLoad = async ({ depends, url, route }) => { } if (account) { + // `/v1/teams` (and org list on cloud) returns 401 until the console account is verified; + // do not call that API on this route while still unverified. + if (url.pathname === verifyEmailPath && !account.emailVerification) { + const plansInfo = await getPlatformPlans(); + plansInfoStore.set(plansInfo); + return { + plansInfo, + account, + organizations: EMPTY_ORGANIZATIONS + }; + } + try { const [plansInfo, organizations] = await Promise.all([ getPlatformPlans(), @@ -47,15 +63,19 @@ export const load: LayoutLoad = async ({ depends, url, route }) => { organizations }; } catch (error) { - if ( - error instanceof AppwriteException && - isEmailVerificationRequiredError(error.type) - ) { - const verifyEmailUrl = resolve('/verify-email'); - - if (url.pathname !== verifyEmailUrl) { - redirect(303, withParams(verifyEmailUrl, url.searchParams)); + if (isVerifyEmailRedirectError(error)) { + if (url.pathname !== verifyEmailPath) { + redirect(303, withParams(verifyEmailPath, url.searchParams)); } + + // Already on verify-email: do not rethrow; the teams API is blocked until verified. + const plansInfo = await getPlatformPlans(); + plansInfoStore.set(plansInfo); + return { + plansInfo, + account, + organizations: EMPTY_ORGANIZATIONS + }; } throw error; @@ -77,11 +97,9 @@ export const load: LayoutLoad = async ({ depends, url, route }) => { redirect(303, withParams(mfaUrl, url.searchParams)); } - if (isEmailVerificationRequiredError(error.type)) { - const verifyEmailUrl = resolve('/verify-email'); - - if (url.pathname !== verifyEmailUrl) { - redirect(303, withParams(verifyEmailUrl, url.searchParams)); + if (isVerifyEmailRedirectError(error)) { + if (url.pathname !== verifyEmailPath) { + redirect(303, withParams(verifyEmailPath, url.searchParams)); } } From 213a54ebeb5421495f57f01578bc3e3cfed7fda3 Mon Sep 17 00:00:00 2001 From: Harsh Mahajan Date: Thu, 2 Apr 2026 15:46:00 +0530 Subject: [PATCH 69/73] fix --- src/lib/stores/billing.ts | 35 +++++++++++++++++++++++++++++------ src/routes/+layout.ts | 6 +++--- 2 files changed, 32 insertions(+), 9 deletions(-) diff --git a/src/lib/stores/billing.ts b/src/lib/stores/billing.ts index 1589dec05..cdd3fc53b 100644 --- a/src/lib/stores/billing.ts +++ b/src/lib/stores/billing.ts @@ -78,10 +78,16 @@ export const showBudgetAlert = derived( ); function getPlansInfoStore(): BillingPlansMap | null { - return get(plansInfo) ?? get(page).data?.plansInfo ?? null; + return get(plansInfo) ?? get(page).data?.plansInfo ?? new Map(); } -function makeBillingPlan(billingPlanOrId: string | Models.BillingPlan): Models.BillingPlan { +function makeBillingPlan( + billingPlanOrId: string | Models.BillingPlan | null | undefined +): Models.BillingPlan | null { + if (!billingPlanOrId) { + return null; + } + return typeof billingPlanOrId === 'string' ? billingIdToPlan(billingPlanOrId) : billingPlanOrId; } @@ -89,21 +95,35 @@ export function getRoleLabel(role: string) { return roles.find((r) => r.value === role)?.label ?? role; } -export function isStarterPlan(billingPlanOrId: string | Models.BillingPlan): boolean { +export function isStarterPlan( + billingPlanOrId: string | Models.BillingPlan | null | undefined +): boolean { const billingPlan = makeBillingPlan(billingPlanOrId); return planHasGroup(billingPlan, BillingPlanGroup.Starter); } -export function canUpgrade(billingPlanOrId: string | Models.BillingPlan): boolean { +export function canUpgrade( + billingPlanOrId: string | Models.BillingPlan | null | undefined +): boolean { const billingPlan = makeBillingPlan(billingPlanOrId); + if (!billingPlan?.$id) { + return false; + } + const nextTier = getNextTierBillingPlan(billingPlan.$id); // defaults back to PRO, so adjust the check! return billingPlan.$id !== nextTier.$id; } -export function canDowngrade(billingPlanOrId: string | Models.BillingPlan): boolean { +export function canDowngrade( + billingPlanOrId: string | Models.BillingPlan | null | undefined +): boolean { const billingPlan = makeBillingPlan(billingPlanOrId); + if (!billingPlan?.$id) { + return false; + } + const nextTier = getPreviousTierBillingPlan(billingPlan.$id); // defaults back to Starter, so adjust the check! @@ -111,7 +131,7 @@ export function canDowngrade(billingPlanOrId: string | Models.BillingPlan): bool } export function planHasGroup( - billingPlanOrId: string | Models.BillingPlan, + billingPlanOrId: string | Models.BillingPlan | null | undefined, group: BillingPlanGroup ): boolean { const billingPlan = makeBillingPlan(billingPlanOrId); @@ -567,6 +587,9 @@ export function checkForMarkedForDeletion(org: Models.Organization) { export async function checkForMissingPaymentMethod() { const starterPlan = getBasePlanFromGroup(BillingPlanGroup.Starter); + if (!starterPlan?.$id) { + return; + } const orgs = await sdk.forConsole.organizations.list({ queries: [ diff --git a/src/routes/+layout.ts b/src/routes/+layout.ts index 0f566dc91..4ebbe0eee 100644 --- a/src/routes/+layout.ts +++ b/src/routes/+layout.ts @@ -39,8 +39,8 @@ export const load: LayoutLoad = async ({ depends, url, route }) => { if (account) { // `/v1/teams` (and org list on cloud) returns 401 until the console account is verified; // do not call that API on this route while still unverified. - if (url.pathname === verifyEmailPath && !account.emailVerification) { - const plansInfo = await getPlatformPlans(); + if (url.pathname === verifyEmailPath) { + const plansInfo = await getPlatformPlans().catch(() => null); plansInfoStore.set(plansInfo); return { plansInfo, @@ -69,7 +69,7 @@ export const load: LayoutLoad = async ({ depends, url, route }) => { } // Already on verify-email: do not rethrow; the teams API is blocked until verified. - const plansInfo = await getPlatformPlans(); + const plansInfo = await getPlatformPlans().catch(() => null); plansInfoStore.set(plansInfo); return { plansInfo, From fc43e009bed9dc948d9274726ccbbdea5660adfd Mon Sep 17 00:00:00 2001 From: Harsh Mahajan Date: Thu, 2 Apr 2026 17:23:18 +0530 Subject: [PATCH 70/73] remove code not needed --- src/lib/helpers/emailVerification.ts | 47 +++++++++------------------- 1 file changed, 15 insertions(+), 32 deletions(-) diff --git a/src/lib/helpers/emailVerification.ts b/src/lib/helpers/emailVerification.ts index f4dba2417..7fe96eb70 100644 --- a/src/lib/helpers/emailVerification.ts +++ b/src/lib/helpers/emailVerification.ts @@ -1,45 +1,28 @@ -import { AppwriteException, type Models } from '@appwrite.io/console'; +import { AppwriteException } from '@appwrite.io/console'; -const EMAIL_VERIFICATION_REQUIRED_ERROR_TYPE = 'user_email_not_verified'; -const CONSOLE_ACCOUNT_VERIFICATION_REQUIRED_TYPE = 'console_account_verification_required'; - -export function isEmailVerificationEnabled( - consoleVariables: Models.ConsoleVariables | undefined -): boolean { - if (!consoleVariables) { - return false; - } - - return String(consoleVariables._APP_CONSOLE_EMAIL_VERIFICATION) === 'true'; -} - -export function isEmailVerificationRequiredError(type: string | undefined): boolean { - return type === EMAIL_VERIFICATION_REQUIRED_ERROR_TYPE; -} - -function matchesVerifyEmailGate(type: string | undefined, message: string | undefined): boolean { - if (isEmailVerificationRequiredError(type)) return true; - if (type === CONSOLE_ACCOUNT_VERIFICATION_REQUIRED_TYPE) return true; - if (message?.includes('Console account verification is required')) return true; - return false; -} - -/** - * Console APIs may return `user_email_not_verified`, `console_account_verification_required`, - * or a message-only error when email verification is enforced for the console account. - */ +/** True when access is blocked until the console account email is verified. */ export function isVerifyEmailRedirectError(error: unknown): boolean { if (error instanceof AppwriteException) { - return matchesVerifyEmailGate(error.type, error.message); + return ( + error.type === 'user_email_not_verified' || + error.type === 'console_account_verification_required' || + (error.message?.includes('Console account verification is required') ?? false) + ); } - if (typeof error === 'object' && error !== null && 'message' in error) { + + if (error && typeof error === 'object' && 'message' in error) { const msg = (error as { message: unknown }).message; if (typeof msg !== 'string') return false; const typ = 'type' in error && typeof (error as { type: unknown }).type === 'string' ? (error as { type: string }).type : undefined; - return matchesVerifyEmailGate(typ, msg); + return ( + typ === 'user_email_not_verified' || + typ === 'console_account_verification_required' || + msg.includes('Console account verification is required') + ); } + return false; } From 740bdb4e247b6bc259ab704c36f79e28b8dc08ea Mon Sep 17 00:00:00 2001 From: Harsh Mahajan Date: Thu, 2 Apr 2026 17:32:17 +0530 Subject: [PATCH 71/73] address greptile comments --- src/routes/(console)/+error.svelte | 5 +++-- src/routes/(console)/+layout.ts | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/routes/(console)/+error.svelte b/src/routes/(console)/+error.svelte index 57691dd85..41863e61a 100644 --- a/src/routes/(console)/+error.svelte +++ b/src/routes/(console)/+error.svelte @@ -8,8 +8,9 @@ import { Typography } from '@appwrite.io/pink-svelte'; $effect(() => { - if (isVerifyEmailRedirectError(page.error)) { - goto(resolve('/verify-email'), { replaceState: true }); + const verifyEmailPath = resolve('/verify-email'); + if (isVerifyEmailRedirectError(page.error) && page.url.pathname !== verifyEmailPath) { + goto(verifyEmailPath, { replaceState: true }); } }); diff --git a/src/routes/(console)/+layout.ts b/src/routes/(console)/+layout.ts index 2904bb5c6..f40123b43 100644 --- a/src/routes/(console)/+layout.ts +++ b/src/routes/(console)/+layout.ts @@ -27,7 +27,7 @@ export const load: LayoutLoad = async ({ depends, parent, url }) => { depends(Dependencies.ORGANIZATION); const [preferences, rawConsoleVariables, versionData] = await Promise.all([ - sdk.forConsole.account.getPrefs(), + sdk.forConsole.account.getPrefs().catch(() => ({}) as Models.DefaultPreferences), sdk.forConsole.console.variables().catch(() => ({}) as Models.ConsoleVariables), fetch(`${endpoint}/health/version`, { headers: { 'X-Appwrite-Project': project as string } From 07659d38db58fd0a9b0d30e31b9f5ddeb8342d55 Mon Sep 17 00:00:00 2001 From: Harsh Mahajan Date: Thu, 2 Apr 2026 17:42:46 +0530 Subject: [PATCH 72/73] chore: remove promo --- src/lib/components/promos/imagine.svelte | 41 ---------------- src/lib/components/promos/imagine.svg | 61 ------------------------ src/routes/(console)/bottomAlerts.ts | 55 +-------------------- 3 files changed, 1 insertion(+), 156 deletions(-) delete mode 100644 src/lib/components/promos/imagine.svelte delete mode 100644 src/lib/components/promos/imagine.svg diff --git a/src/lib/components/promos/imagine.svelte b/src/lib/components/promos/imagine.svelte deleted file mode 100644 index ff316b51a..000000000 --- a/src/lib/components/promos/imagine.svelte +++ /dev/null @@ -1,41 +0,0 @@ - - -
-
- - - -
- - -
- - diff --git a/src/lib/components/promos/imagine.svg b/src/lib/components/promos/imagine.svg deleted file mode 100644 index 58e33f62c..000000000 --- a/src/lib/components/promos/imagine.svg +++ /dev/null @@ -1,61 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/routes/(console)/bottomAlerts.ts b/src/routes/(console)/bottomAlerts.ts index 1d0d86045..1ec0a224b 100644 --- a/src/routes/(console)/bottomAlerts.ts +++ b/src/routes/(console)/bottomAlerts.ts @@ -1,59 +1,6 @@ -import { isCloud } from '$lib/system'; import { isSameDay } from '$lib/helpers/date'; -import Imagine from '$lib/components/promos/imagine.svelte'; -import { - type BottomModalAlertItem, - setMobileSingleAlertLayout, - showBottomModalAlert -} from '$lib/stores/bottom-alerts'; -const SHOW_IMAGINE_PROMO = true; - -const listOfPromotions: BottomModalAlertItem[] = []; - -if (isCloud && SHOW_IMAGINE_PROMO) { - const imaginePromo: BottomModalAlertItem = { - id: 'modal:imagine.dev', - backgroundComponent: Imagine, - title: 'Introducing Imagine', - message: 'The most complete AI builder to date', - importance: 8, - scope: 'everywhere', - plan: 'free', - cta: { - text: 'Try it now', - color: { - light: '#FFFFFF', - dark: '#000000' - }, - background: { - light: '#000000', - dark: '#FFFFFF' - }, - backgroundHover: { - light: '#333333', - dark: '#CCCCCC' - }, - link: () => 'https://imagine.dev', - external: true, - hideOnClick: true - }, - show: true - }; - - listOfPromotions.push(imaginePromo); -} - -export function addBottomModalAlerts() { - listOfPromotions.forEach((promotion) => showBottomModalAlert(promotion)); - - // only for imagine! - if (listOfPromotions.length > 0) { - const imaginePromo = listOfPromotions[0]; - const { cta, title, message } = imaginePromo; - setMobileSingleAlertLayout({ enabled: true, cta, title, message }); - } -} +export function addBottomModalAlerts() {} // use this for time based promo handling // noinspection JSUnusedGlobalSymbols From 2b17b80c6658589a4a58476e581ae7e2ded6406c Mon Sep 17 00:00:00 2001 From: Harsh Mahajan Date: Thu, 2 Apr 2026 17:45:08 +0530 Subject: [PATCH 73/73] remove shaders --- src/lib/components/promos/shader.svelte | 109 ------------ .../promos/threlte/shaders/noise.glsl | 166 ------------------ 2 files changed, 275 deletions(-) delete mode 100644 src/lib/components/promos/shader.svelte delete mode 100644 src/lib/components/promos/threlte/shaders/noise.glsl diff --git a/src/lib/components/promos/shader.svelte b/src/lib/components/promos/shader.svelte deleted file mode 100644 index 2f8ee715b..000000000 --- a/src/lib/components/promos/shader.svelte +++ /dev/null @@ -1,109 +0,0 @@ - - - - - - diff --git a/src/lib/components/promos/threlte/shaders/noise.glsl b/src/lib/components/promos/threlte/shaders/noise.glsl deleted file mode 100644 index dea577c65..000000000 --- a/src/lib/components/promos/threlte/shaders/noise.glsl +++ /dev/null @@ -1,166 +0,0 @@ -// -// psrddnoise3.glsl -// -// Authors: Stefan Gustavson (stefan.gustavson@gmail.com) -// and Ian McEwan (ijm567@gmail.com) -// Version 2021-12-02, published under the MIT license (see below) -// -// Copyright (c) 2021 Stefan Gustavson and Ian McEwan. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included -// in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL -// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. -// - -vec4 permute(vec4 i) { - vec4 im = mod(i, 289.0); - return mod(((im*34.0)+10.0)*im, 289.0); -} - -float psrddnoise(vec3 x, vec3 period, float alpha, out vec3 gradient, - out vec3 dg, out vec3 dg2) -{ - const mat3 M = mat3(0.0, 1.0, 1.0, - 1.0, 0.0, 1.0, - 1.0, 1.0, 0.0); - - const mat3 Mi = mat3(-0.5, 0.5, 0.5, - 0.5,-0.5, 0.5, - 0.5, 0.5,-0.5); - - vec3 uvw; - uvw = M * x; - - vec3 i0 = floor(uvw); - vec3 f0 = fract(uvw); - - vec3 g_ = step(f0.xyx, f0.yzz); - vec3 l_ = 1.0 - g_; - vec3 g = vec3(l_.z, g_.xy); - vec3 l = vec3(l_.xy, g_.z); - vec3 o1 = min( g, l ); - vec3 o2 = max( g, l ); - - vec3 i1 = i0 + o1; - vec3 i2 = i0 + o2; - vec3 i3 = i0 + vec3(1.0); - - vec3 v0, v1, v2, v3; - - v0 = Mi * i0; - v1 = Mi * i1; - v2 = Mi * i2; - v3 = Mi * i3; - - vec3 x0 = x - v0; - vec3 x1 = x - v1; - vec3 x2 = x - v2; - vec3 x3 = x - v3; - - if(any(greaterThan(period, vec3(0.0)))) { - vec4 vx = vec4(v0.x, v1.x, v2.x, v3.x); - vec4 vy = vec4(v0.y, v1.y, v2.y, v3.y); - vec4 vz = vec4(v0.z, v1.z, v2.z, v3.z); - if(period.x > 0.0) vx = mod(vx, period.x); - if(period.y > 0.0) vy = mod(vy, period.y); - if(period.z > 0.0) vz = mod(vz, period.z); - i0 = M * vec3(vx.x, vy.x, vz.x); - i1 = M * vec3(vx.y, vy.y, vz.y); - i2 = M * vec3(vx.z, vy.z, vz.z); - i3 = M * vec3(vx.w, vy.w, vz.w); - i0 = floor(i0 + 0.5); - i1 = floor(i1 + 0.5); - i2 = floor(i2 + 0.5); - i3 = floor(i3 + 0.5); - } - - vec4 hash = permute( permute( permute( - vec4(i0.z, i1.z, i2.z, i3.z )) - + vec4(i0.y, i1.y, i2.y, i3.y )) - + vec4(i0.x, i1.x, i2.x, i3.x )); - - vec4 theta = hash * 3.883222077; - vec4 sz = hash * -0.006920415 + 0.996539792; - vec4 psi = hash * 0.108705628 ; - - vec4 Ct = cos(theta); - vec4 St = sin(theta); - vec4 sz_prime = sqrt( 1.0 - sz*sz ); - - vec4 gx, gy, gz; - - if(alpha != 0.0) { - vec4 Sp = sin(psi); - vec4 Cp = cos(psi); - - vec4 px = Ct * sz_prime; - vec4 py = St * sz_prime; - vec4 pz = sz; - - vec4 Ctp = St*Sp - Ct*Cp; - vec4 qx = mix( Ctp*St, Sp, sz); - vec4 qy = mix(-Ctp*Ct, Cp, sz); - vec4 qz = -(py*Cp + px*Sp); - - vec4 Sa = vec4(sin(alpha)); - vec4 Ca = vec4(cos(alpha)); - - gx = Ca * px + Sa * qx; - gy = Ca * py + Sa * qy; - gz = Ca * pz + Sa * qz; - } - else { - gx = Ct * sz_prime; - gy = St * sz_prime; - gz = sz; - } - - vec3 g0 = vec3(gx.x, gy.x, gz.x); - vec3 g1 = vec3(gx.y, gy.y, gz.y); - vec3 g2 = vec3(gx.z, gy.z, gz.z); - vec3 g3 = vec3(gx.w, gy.w, gz.w); - - vec4 w = 0.5 - vec4(dot(x0,x0), dot(x1,x1), dot(x2,x2), dot(x3,x3)); - w = max(w, 0.0); - vec4 w2 = w * w; - vec4 w3 = w2 * w; - - vec4 gdotx = vec4(dot(g0,x0), dot(g1,x1), dot(g2,x2), dot(g3,x3)); - - float n = dot(w3, gdotx); - - vec4 dw = -6.0 * w2 * gdotx; - vec3 dn0 = w3.x * g0 + dw.x * x0; - vec3 dn1 = w3.y * g1 + dw.y * x1; - vec3 dn2 = w3.z * g2 + dw.z * x2; - vec3 dn3 = w3.w * g3 + dw.w * x3; - gradient = 39.5 * (dn0 + dn1 + dn2 + dn3); - - vec4 dw2 = 24.0 * w * gdotx; - vec3 dga0 = dw2.x * x0 * x0 - 6.0 * w2.x * (gdotx.x + 2.0 * g0 * x0); - vec3 dga1 = dw2.y * x1 * x1 - 6.0 * w2.y * (gdotx.y + 2.0 * g1 * x1); - vec3 dga2 = dw2.z * x2 * x2 - 6.0 * w2.z * (gdotx.z + 2.0 * g2 * x2); - vec3 dga3 = dw2.w * x3 * x3 - 6.0 * w2.w * (gdotx.w + 2.0 * g3 * x3); - dg = 35.0 * (dga0 + dga1 + dga2 + dga3); - vec3 dgb0 = dw2.x * x0 * x0.yzx - 6.0 * w2.x * (g0 * x0.yzx + g0.yzx * x0); - vec3 dgb1 = dw2.y * x1 * x1.yzx - 6.0 * w2.y * (g1 * x1.yzx + g1.yzx * x1); - vec3 dgb2 = dw2.z * x2 * x2.yzx - 6.0 * w2.z * (g2 * x2.yzx + g2.yzx * x2); - vec3 dgb3 = dw2.w * x3 * x3.yzx - 6.0 * w2.w * (g3 * x3.yzx + g3.yzx * x3); - dg2 = 39.5 * (dgb0 + dgb1 + dgb2 + dgb3); - - return 39.5 * n; -}