From 92f524e9a9ef9086c8a980a8ef3c3f3b38614a92 Mon Sep 17 00:00:00 2001 From: AviArora02-commits Date: Mon, 20 Apr 2026 17:25:32 +0530 Subject: [PATCH 1/8] feat: add internal notes for collection attributes (#11945) --- src/lib/components/AttributeNote.svelte | 363 ++++++++++++++++++++++++ src/lib/stores/attributeNotes.ts | 127 +++++++++ 2 files changed, 490 insertions(+) create mode 100644 src/lib/components/AttributeNote.svelte create mode 100644 src/lib/stores/attributeNotes.ts diff --git a/src/lib/components/AttributeNote.svelte b/src/lib/components/AttributeNote.svelte new file mode 100644 index 0000000000..a0d781923a --- /dev/null +++ b/src/lib/components/AttributeNote.svelte @@ -0,0 +1,363 @@ + + +
+ {#if editing} +
+ +
+ Ctrl+Enter to save · Esc to cancel +
+ {#if note} + + {/if} + + +
+
+
+ {:else if note} + + {:else} + + {/if} +
+ + diff --git a/src/lib/stores/attributeNotes.ts b/src/lib/stores/attributeNotes.ts new file mode 100644 index 0000000000..79a3ba6cbe --- /dev/null +++ b/src/lib/stores/attributeNotes.ts @@ -0,0 +1,127 @@ +/** + * attributeNotes.ts + * + * Store for persisting developer notes on collection attributes/columns. + * Notes are stored in localStorage because the Appwrite API does not currently + * expose a `notes` field on attribute objects. This is a console-only feature. + * + * Storage key format: appwrite_attribute_notes + * Data shape: Record + * where key = `${databaseId}/${collectionId}/${attributeKey}` + * and value = the note text + * + * Issue: https://github.com/appwrite/appwrite/issues/11945 + */ + +import { writable } from 'svelte/store'; + +const STORAGE_KEY = 'appwrite_attribute_notes'; + +type NotesMap = Record; + +/** + * Build the compound key used to look up a note. + */ +export function buildNoteKey( + databaseId: string, + collectionId: string, + attributeKey: string +): string { + return `${databaseId}/${collectionId}/${attributeKey}`; +} + +/** + * Load notes map from localStorage, returning an empty object on any error. + */ +function loadFromStorage(): NotesMap { + try { + const raw = localStorage.getItem(STORAGE_KEY); + if (!raw) return {}; + return JSON.parse(raw) as NotesMap; + } catch { + return {}; + } +} + +/** + * Persist notes map to localStorage. + */ +function saveToStorage(notes: NotesMap): void { + try { + localStorage.setItem(STORAGE_KEY, JSON.stringify(notes)); + } catch { + // Silently ignore storage errors (e.g. private browsing quota) + } +} + +function createAttributeNotesStore() { + const { subscribe, set, update } = writable(loadFromStorage()); + + return { + subscribe, + + /** + * Get the note for a specific attribute. + */ + getNote(databaseId: string, collectionId: string, attributeKey: string): string { + const notes = loadFromStorage(); + return notes[buildNoteKey(databaseId, collectionId, attributeKey)] ?? ''; + }, + + /** + * Save or clear a note for a specific attribute. + */ + setNote( + databaseId: string, + collectionId: string, + attributeKey: string, + note: string + ): void { + update((notes) => { + const key = buildNoteKey(databaseId, collectionId, attributeKey); + const updated = { ...notes }; + if (note.trim()) { + updated[key] = note; + } else { + delete updated[key]; + } + saveToStorage(updated); + return updated; + }); + }, + + /** + * Delete a note for a specific attribute (e.g. when attribute is deleted). + */ + deleteNote(databaseId: string, collectionId: string, attributeKey: string): void { + update((notes) => { + const key = buildNoteKey(databaseId, collectionId, attributeKey); + const updated = { ...notes }; + delete updated[key]; + saveToStorage(updated); + return updated; + }); + }, + + /** + * Delete all notes for a collection (e.g. when collection is deleted). + */ + deleteCollectionNotes(databaseId: string, collectionId: string): void { + update((notes) => { + const prefix = `${databaseId}/${collectionId}/`; + const updated = Object.fromEntries( + Object.entries(notes).filter(([k]) => !k.startsWith(prefix)) + ); + saveToStorage(updated); + return updated; + }); + }, + + /** Re-sync from localStorage (useful after external changes). */ + reload(): void { + set(loadFromStorage()); + } + }; +} + +export const attributeNotes = createAttributeNotesStore(); From 1aa3fc8f075e36520468c596d0cbf19e3f8c2324 Mon Sep 17 00:00:00 2001 From: AviArora02-commits Date: Mon, 20 Apr 2026 22:33:53 +0530 Subject: [PATCH 2/8] fix(attributeNotes): validate JSON.parse result (Copilot) --- src/lib/stores/attributeNotes.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/lib/stores/attributeNotes.ts b/src/lib/stores/attributeNotes.ts index 79a3ba6cbe..6a8f9ae65e 100644 --- a/src/lib/stores/attributeNotes.ts +++ b/src/lib/stores/attributeNotes.ts @@ -33,11 +33,16 @@ export function buildNoteKey( /** * Load notes map from localStorage, returning an empty object on any error. */ +function isNotesMap(value: unknown): value is NotesMap { + return typeof value === 'object' && value !== null && !Array.isArray(value); +} + function loadFromStorage(): NotesMap { try { const raw = localStorage.getItem(STORAGE_KEY); if (!raw) return {}; - return JSON.parse(raw) as NotesMap; + const parsed: unknown = JSON.parse(raw); + return isNotesMap(parsed) ? parsed : {}; } catch { return {}; } From 1c26345f70334d447411823865b1bd282cfc6bc0 Mon Sep 17 00:00:00 2001 From: AviArora02-commits Date: Mon, 20 Apr 2026 22:38:27 +0530 Subject: [PATCH 3/8] fix(attributeNotes): add SSR safety using browser guard --- src/lib/stores/attributeNotes.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/lib/stores/attributeNotes.ts b/src/lib/stores/attributeNotes.ts index 6a8f9ae65e..20692e94e7 100644 --- a/src/lib/stores/attributeNotes.ts +++ b/src/lib/stores/attributeNotes.ts @@ -13,6 +13,7 @@ * Issue: https://github.com/appwrite/appwrite/issues/11945 */ +import { browser } from '$app/environment'; import { writable } from 'svelte/store'; const STORAGE_KEY = 'appwrite_attribute_notes'; @@ -38,6 +39,7 @@ function isNotesMap(value: unknown): value is NotesMap { } function loadFromStorage(): NotesMap { + if (!browser) return {}; try { const raw = localStorage.getItem(STORAGE_KEY); if (!raw) return {}; @@ -52,6 +54,7 @@ function loadFromStorage(): NotesMap { * Persist notes map to localStorage. */ function saveToStorage(notes: NotesMap): void { + if (!browser) return; try { localStorage.setItem(STORAGE_KEY, JSON.stringify(notes)); } catch { From c21867787b3022235f58e2e8be3578f04765af4e Mon Sep 17 00:00:00 2001 From: AviArora02-commits Date: Mon, 20 Apr 2026 22:42:01 +0530 Subject: [PATCH 4/8] fix(attributeNotes): ensure trimmed values are stored --- src/lib/stores/attributeNotes.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/lib/stores/attributeNotes.ts b/src/lib/stores/attributeNotes.ts index 20692e94e7..f0468b9688 100644 --- a/src/lib/stores/attributeNotes.ts +++ b/src/lib/stores/attributeNotes.ts @@ -88,8 +88,11 @@ function createAttributeNotesStore() { update((notes) => { const key = buildNoteKey(databaseId, collectionId, attributeKey); const updated = { ...notes }; - if (note.trim()) { - updated[key] = note; + + const trimmedNote = note.trim(); + + if (trimmedNote) { + updated[key] = trimmedNote; } else { delete updated[key]; } From bab8a86137989d1bccf28fcc14cfde78fbb1239f Mon Sep 17 00:00:00 2001 From: AviArora02-commits Date: Mon, 20 Apr 2026 22:45:54 +0530 Subject: [PATCH 5/8] fix(AttributeNote): make note reactive to store updates --- src/lib/components/AttributeNote.svelte | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/lib/components/AttributeNote.svelte b/src/lib/components/AttributeNote.svelte index a0d781923a..fdafaaa267 100644 --- a/src/lib/components/AttributeNote.svelte +++ b/src/lib/components/AttributeNote.svelte @@ -35,6 +35,17 @@ // Draft copy while the user types let draft = $state(note); + $effect(() => { + const nextNote = attributeNotes.getNote(databaseId, collectionId, attributeKey); + + note = nextNote; + + // Only overwrite draft if user is NOT editing + if (!editing) { + draft = nextNote; + } + }); + // Textarea ref for auto-focus let textareaRef = $state(null); From e159f9c2eca62ec560f744d8c6edc7e55c286aeb Mon Sep 17 00:00:00 2001 From: AviArora02-commits Date: Mon, 20 Apr 2026 22:50:30 +0530 Subject: [PATCH 6/8] fix(AttributeNote): update keyboard shortcut hint for macOS --- src/lib/components/AttributeNote.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/components/AttributeNote.svelte b/src/lib/components/AttributeNote.svelte index fdafaaa267..04cf79cb72 100644 --- a/src/lib/components/AttributeNote.svelte +++ b/src/lib/components/AttributeNote.svelte @@ -102,7 +102,7 @@ maxlength="500" onkeydown={handleKeydown}>
- Ctrl+Enter to save · Esc to cancel + Ctrl+Enter / Cmd+Enter to save · Esc to cancel
{#if note}