הערות טובות בכל מקום

תמונה שיווקית של Goodnotes שמציגה אישה שמשתמשת במוצר ב-iPad.

בשנתיים האחרונות, צוות המהנדסים של Goodnotes עבד על פרויקט שיביא את האפליקציה המצליחה לרישום הערות ב-iPad לפלטפורמות אחרות. מקרה לדוגמה שממחיש איך אפליקציית השנה ל-iPad לשנת 2022 הגיעה לאינטרנט, ל-ChromeOS, ל-Android ול-Windows, שמבוססים על טכנולוגיות אינטרנט ועל WebAssembly, שהשתמשו שוב באותו קוד Swift שהצוות עובד עליו במשך יותר מעשר שנים.

הלוגו של Goodnotes.

למה Goodnotes הגיע לאינטרנט, ל-Android ול-Windows

בשנת 2021, Google Goodnotes היה זמין רק כאפליקציה ל-iOS ול-iPad. צוות ההנדסה ב-Goodnotes נענה לאתגר טכני ענק: יצירת גרסה חדשה של Goodnotes, אך עבור מערכות הפעלה ופלטפורמות נוספות. המוצר צריך להיות תואם באופן מלא ולעבד את אותן הערות כמו באפליקציה ל-iOS. כל פתק שתועד מעל קובץ PDF או תמונה מצורפת צריך להיות זהה, ולהציג אותו בקווים שמופיעים באפליקציה ל-iOS. כל קו שמוסיפים צריך להיות זהה לזה שמשתמשי iOS יכולים ליצור, בלי קשר לכלי שהמשתמש השתמש בו – לדוגמה: עט, עט הדגשה, עט נובע, צורות או מחק.

תצוגה מקדימה של אפליקציית Goodnotes עם הערות ושרטוטים בכתב יד.

על סמך הדרישות והניסיון של צוות מהנדסי התוכנה, הצוות הגיע למסקנה במהירות ששימוש חוזר ב-codebase של Swift הוא דרך הפעולה הטובה ביותר, כי הוא כבר נכתב ונבדק היטב לאורך שנים רבות. אבל למה לא פשוט לנייד את אפליקציית iOS/iPad שכבר קיימת לפלטפורמה או לטכנולוגיה אחרת, כמו Flutter או Compose Multiplatform? כדי לעבור לפלטפורמה חדשה תצטרכו לכתוב שכתוב של קובצי Goodnote. כך יכול להיות שנתחיל במרוץ פיתוח בין אפליקציית iOS שכבר הוטמעה לבין אפליקציה שצריך לבנות מאפס, או שיהיה צורך לעצור את הפיתוח של האפליקציה הקיימת בזמן שבסיס הקוד החדש יזוהה. אם Goodnotes היה יכול להשתמש שוב בקוד Swift, הצוות היה יכול ליהנות מתכונות חדשות שצוות iOS הטמיע בזמן שהצוות בפלטפורמות השונות עבד על היסודות של האפליקציה ועל שוויון התכונות של פוטנציאל החשיפה.

המוצר כבר פתר מספר אתגרים מעניינים של iOS בהוספת תכונות כמו:

  • עיבוד ההערות.
  • סנכרון של מסמכים והערות.
  • פתרון התנגשויות בין הערות באמצעות סוגי נתונים משוכפלים ללא התנגשויות.
  • ניתוח נתונים להערכת מודל AI.
  • חיפוש תוכן והוספה של מסמכים לאינדקס.
  • חוויית גלילה ואנימציות בהתאמה אישית.
  • הצגת יישום המודל עבור כל שכבות ממשק המשתמש.

את כולם יהיה הרבה יותר קל להטמיע בפלטפורמות אחרות אם צוות המהנדסים יוכל לגרום ל-codebase של iOS לפעול באפליקציות ל-iOS ול-iPad, והפעיל אותו כחלק מפרויקט ש-Goodnotes יוכל לשלוח כאפליקציות ל-Windows, ל-Android או לאינטרנט.

סטאק התוכנות של Goodnotes

למרבה המזל, הייתה דרך להשתמש שוב בקוד Swift הקיים באינטרנט – WebAssembly (Wasm). חברת Goodnotes יצרה אב טיפוס באמצעות Wasm עם פרויקט הקוד הפתוח SwiftWasm שמתחזק על ידי הקהילה. בעזרת SwiftWasm, הצוות של Goodnotes יכול ליצור קובץ בינארי של Wasm באמצעות כל קוד Swift שכבר הוטמע. אפשר לכלול את הקובץ הבינארי הזה בדף אינטרנט שנשלח בתור Progressive Web App ל-Android, ל-Windows, ל-ChromeOS ולכל מערכת הפעלה אחרת.

רצף ההשקה של Goodnotes מתחיל ב-Chrome, לאחר מכן ב-Windows, אחריו ב-Android ובפלטפורמות אחרות כמו Linux, והכל על סמך ה-PWA.

המטרה הייתה להשיק את Goodnotes כ-PWA, ולאפשר את הפרסום בכל חנות של הפלטפורמה. בנוסף ל-Swift, שפת התכנות שכבר משמשת ל-iOS, ו-WebAssembly ששימשה להפעלת קוד Swift באינטרנט, בפרויקט נעשה שימוש בטכנולוגיות הבאות:

  • TypeScript: שפת התכנות הנפוצה ביותר לטכנולוגיות אינטרנט.
  • תגובה ו-webpack: ה-framework וה-bundler הפופולריים ביותר באינטרנט.
  • PWA ו-Service Workers: מפעילים ענקיים לפרויקט הזה כי הצוות יכול לשלוח את האפליקציה שלנו כאפליקציה אופליין שפועלת כמו כל אפליקציה אחרת ל-iOS, ואפשר להתקין אותה מהחנות או מהדפדפן עצמו.
  • PWABuilder: הפרויקט הראשי שבו Goodnotes משתמש כדי לארוז את ה-PWA לקובץ בינארי מקורי של Windows, כדי שהצוות יוכל להפיץ את האפליקציה שלנו מ-Microsoft Store.
  • פעילויות באינטרנט מהימנים: טכנולוגיית Android החשובה ביותר שבה משתמשים החברה כדי להפיץ את ה-PWA שלנו כאפליקציה מקורית.

סטאק התוכנות של Goodnotes כולל Swift, Wasm, React ו-PWA.

בתמונה הבאה אפשר לראות מה מוטמע באמצעות הגרסה הקלאסית של TypeScript ו-React, ומה מוטמע באמצעות SwiftWasm ו-Vanilla JavaScript , Swift ו-WebAssembly. בחלק הזה של הפרויקט נעשה שימוש ב-JSKit, ספריית JavaScript ליכולת פעולה הדדית עבור Swift ו-WebAssembly שהצוות משתמש בה כדי לטפל ב-DOM במסך העורך שלנו מקוד Swift במקרה הצורך, או אפילו להשתמש בכמה ממשקי API ספציפיים לדפדפן.

צילומי מסך של האפליקציה בנייד ובמחשב שבהם מוצגים אזורי השרטוט הספציפיים שנוצרו על ידי Wasm, והאזורים בממשק המשתמש שמבוססים על React.

למה כדאי להשתמש ב-Wasm והאינטרנט?

למרות ש-Wasm לא נתמך באופן רשמי על ידי Apple, הסיבה לכך היא שצוות המהנדסים של Goodnotes הרגיש שגישה זו היא ההחלטה הטובה ביותר:

  • שימוש חוזר ביותר מ-100 אלף שורות קוד.
  • היכולת להמשיך בפיתוח במוצר הליבה וגם לתרום לאפליקציות בפלטפורמות השונות.
  • הכוח של להגיע לכל פלטפורמה בהקדם האפשרי באמצעות תהליך פיתוח איטרטיבי.
  • יש לנו שליטה על עיבוד אותו מסמך בלי לשכפל את כל הלוגיקה העסקית, והצגת הבדלים בהטמעות שלנו.
  • ליהנות מכל שיפורי הביצועים שמתבצעים בכל פלטפורמה בו-זמנית (וכל תיקוני הבאגים שמוטמעים בכל פלטפורמה).

היה יסוד לשימוש חוזר ביותר מ-100 אלף שורות קוד, והלוגיקה העסקית שמטמיעה את צינור עיבוד הנתונים שלנו. במקביל, התאמת קוד ה-Swift לשרשראות כלים אחרות מאפשרת להם לעשות שימוש חוזר בקוד בפלטפורמות שונות בעתיד, לפי הצורך.

פיתוח מוצרים איטרטיבי

הצוות ביצע גישה איטרטיבית כדי להשיג משהו למשתמשים במהירות האפשרית. Goodnotes התחילו בגרסה לקריאה בלבד של המוצר שבה משתמשים יכלו לקבל כל מסמך משותף ולקרוא אותו מכל פלטפורמה. באמצעות קישור, הם יוכלו לגשת ולקרוא את אותם הערות שהם כתבו ממכשיר ה-iPad שלהם. השלב הבא נוסף בתכונות העריכה, כדי שהגרסאות בפלטפורמות השונות יהיו מקבילות לגרסה של iOS.

שני צילומי מסך של אפליקציה שמסמלים מעבר מקריאה בלבד למוצר המוצג במלואו.

פיתוח הגרסה הראשונה של המוצר לקריאה בלבד נמשך שישה חודשים. תשעה החודשים הבאים הוקדשו לקבוצה הראשונה של תכונות העריכה ולמסך ממשק המשתמש, שבו אפשר לבדוק את כל המסמכים שיצרתם או מישהו ששיתף איתכם. בנוסף, הודות ל-SwiftWasm Toolchain, קל יותר להעביר תכונות חדשות של פלטפורמת iOS לפרויקט בפלטפורמות שונות. לדוגמה, סוג חדש של עט נוצר ומיושם בקלות בפלטפורמות שונות, על ידי שימוש חוזר באלפי שורות קוד.

הייתה חוויה מדהימה לבניית הפרויקט הזה, ו-Goodnotes למדה ממנו הרבה. לכן הקטעים הבאים יתמקדו בנקודות טכניות מעניינות על פיתוח אתרים והשימוש ב-WebAssembly ובשפות כמו Swift.

מכשולים ראשוניים

העבודה על הפרויקט הזה הייתה מאתגרת מאוד מנקודות מבט רבות ושונות. המכשול הראשון שהצוות מצא היה קשור לשרשרת הכלים של SwiftWasm. ה-toolchain יכול היה הפעלה אדירה בשביל הצוות, אבל לא כל קוד ה-iOS תואם ל-Wasm. לדוגמה, לא ניתן היה לעשות שימוש חוזר בקוד שקשור ל-IO או לממשק משתמש, כמו הטמעת תצוגות, לקוחות API או גישה למסד הנתונים. לכן הצוות היה צריך להתחיל לגבש מחדש חלקים מסוימים באפליקציה כדי להשתמש בהם מחדש מהפתרון בפלטפורמות השונות. רוב אנשי הקשר שהצוות יצר היו ארגון מחדש של יחסי תלות מופשטים, כדי שמאוחר יותר הצוות יוכל להחליף אותם באמצעות החדרת תלות או אסטרטגיות דומות אחרות. קוד ה-iOS שילב במקור לוגיקה עסקית גולמית, שניתן היה להטמיע ב-Wasm, עם קוד שהיה אחראי לקלט/פלט ולממשק המשתמש, שלא ניתן היה להטמיע ב-Wasm כי אף אחד מהם לא תומך ב-Wasm. לכן היה צורך להטמיע מחדש את ה-IO ואת קוד ממשק המשתמש ב-TypeScript, ברגע שהלוגיקה העסקית של Swift הייתה מוכנה לשימוש חוזר בין פלטפורמות.

בעיות בביצועים שנפתרו

ברגע ש-Goodnotes התחילו לעבוד על העורך, הצוות זיהה כמה בעיות בחוויית העריכה, ומגבלות טכנולוגיות מאתגרות נוספו למפת הדרכים שלנו. הבעיה הראשונה הייתה קשורה לביצועים. JavaScript היא שפה עם שרשור אחד. המשמעות היא שיש בה מחסנית שיחות אחת וערימה אחת של זיכרון. הוא מפעיל את הקוד לפי הסדר, וצריך לסיים את הביצוע של קטע קוד לפני שעוברים לשלב הבא. זה סינכרוני, אבל לפעמים זה עלול להיות מזיק. לדוגמה, אם הפעולה נמשכת זמן מה או אם היא צריכה להמתין לפעולה כלשהי, היא מקפיאה בינתיים את הכול. זה בדיוק מה שהמהנדסים היו צריכים לפתור. הייתה בעיה לצוות בהערכת נתיבים ספציפיים ב-codebase שלנו שקשורים לשכבת העיבוד או לאלגוריתמים מורכבים אחרים, כי האלגוריתמים האלה היו סינכרוניים, וביצוע שלהם חסם את ה-thread הראשי. צוות Goodnotes כתב אותם מחדש כדי להפוך אותם למהירים יותר, וסידרב מחדש חלק מהם כדי שיהיו אסינכרוניים. הם גם הוסיפו אסטרטגיית תפוקה כדי שהאפליקציה תוכל לעצור את הפעלת האלגוריתם ולהמשיך אותה מאוחר יותר, וכך לאפשר לדפדפן לעדכן את ממשק המשתמש ולהימנע מהפסקות של פריימים. זו לא הייתה בעיה באפליקציה ל-iOS כי היא יכולה להשתמש בשרשורים ולהעריך את האלגוריתמים האלה ברקע, בזמן שה-thread הראשי ב-iOS מעדכן את ממשק המשתמש.

פתרון נוסף שצוות המהנדסים היה צריך לפתור היה העברה של ממשק משתמש שמבוסס על רכיבי HTML שמקושרים ל-DOM, לממשק משתמש של מסמך שמבוסס על אזור ציור במסך מלא. הפרויקט התחיל להציג את כל ההערות והתוכן שקשורים למסמך כחלק ממבנה ה-DOM באמצעות רכיבי HTML, כמו שכל דף אינטרנט אחר היה עושה, אבל בשלב מסוים הוא הועבר לאזור העריכה במסך מלא כדי לשפר את הביצועים במכשירים פשוטים על ידי צמצום הזמן שבו הדפדפן עובד על עדכוני DOM.

צוות מהנדסי התוכנה ציין את השינויים הבאים שהיו יכולים לפתור, אם היו כאלה, בתחילת הפרויקט.

  • מסירים את ה-thread הראשי מהר יותר על ידי שימוש תכוף בעובדי אינטרנט לביצוע אלגוריתמים כבדים.
  • מאז ההתחלה, כדאי להשתמש בפונקציות המיוצאות ובפונקציות שיובאו במקום בספריית הפעולות ההדדיות של JS-Swift, כדי לצמצם את ההשפעה של יציאה מההקשר של Wasm. הספרייה ההדדית של JavaScript עוזרת לקבל גישה ל-DOM או לדפדפן, אבל היא איטית יותר מהפונקציות המקוריות שיוצאו באמצעות Wasm.
  • חשוב לוודא שהקוד מאפשר את השימוש ב-OffscreenCanvas מתחת למכסה כדי שהאפליקציה תוכל להפחית את העומס מה-thread הראשי ולהעביר את כל השימוש ב-Canvas API ל-Web Worker שממקסם את ביצועי האפליקציות כשכותבים הערות.
  • כדאי להעביר את כל הביצוע שקשור ל-Wasm ל-worker או למאגר של עובדי אינטרנט, כדי שהאפליקציה תוכל לצמצם את עומס העבודה ב-thread הראשי.

הכלי לעריכת טקסט

בעיה מעניינת נוספת הייתה קשורה לכלי ספציפי אחד, עורך הטקסט. ההטמעה של הכלי הזה ב-iOS מבוססת על NSAttributedString – מערך כלים קטן שמשתמש ב-RTF מאחורי הקלעים. עם זאת, ההטמעה הזו לא תואמת ל-SwiftWasm ולכן הצוות בפלטפורמות השונות נאלץ ליצור תחילה מנתח בהתאמה אישית על סמך הדקדוק של RTF, ובהמשך להטמיע את חוויית העריכה על ידי טרנספורמציה של RTF ל-HTML, ולהפך. בינתיים, צוות iOS התחיל לעבוד על ההטמעה החדשה של הכלי הזה, שמחליף את השימוש ב-RTF במודל מותאם אישית. כך האפליקציה יכולה לייצג טקסט מעוצב באופן ידידותי לכל הפלטפורמות שחולקות את אותו קוד Swift.

עורך הטקסט של Goodnotes.

האתגר הזה היה אחת מהנקודות המעניינות ביותר במפת הדרכים של הפרויקט, כי הוא נפתר באופן חזרתי על סמך צורכי המשתמש. זו הייתה בעיה הנדסית שנפתרה באמצעות גישה ממוקדת משתמש, שבה הצוות היה צריך לשכתב חלק מהקוד כדי לעבד את הטקסט ולאפשר את עריכת הטקסט במהדורה השנייה.

גרסאות חוזרות

ההתפתחות של הפרויקט במהלך השנתיים האחרונות הייתה מדהימה. הצוות התחיל לעבוד על גרסה לקריאה בלבד של הפרויקט, וחודשים לאחר מכן שלח גרסה חדשה עם הרבה יכולות עריכה. כדי לפרסם שינויים בקוד לעיתים קרובות בסביבת הייצור, הצוות החליט להשתמש באופן נרחב בסימונים של תכונות. לכל גרסה, הצוות יכול להפעיל תכונות חדשות וגם לפרסם שינויי קוד שמטמיעים תכונות חדשות שהמשתמש יראה שבועות מאוחר יותר. עם זאת, יש משהו שהצוות חושב שהם יכולים לשפר! הם חושבים שהגדרה דינמית של מערכת דגלים על תכונות הייתה עוזרת לזרז את התהליך, כי היא תבטל את הצורך בפריסה מחדש כדי לשנות ערכי דגלים. כך ל-Goodnotes יש יותר גמישות וגם לזרז את הפריסה של התכונה החדשה, כי לא יהיה צורך לקשר את פריסת הפרויקט לגרסת המוצר.

עבודה במצב אופליין

אחת התכונות העיקריות שהצוות עבד עליהן היא תמיכה אופליין. היכולת לערוך מסמכים ולשנות אותם היא תכונה אחת שאפשר לצפות בכל אפליקציה כזו. עם זאת, התכונה הזו לא פשוטה כי Goodnotes תומך בשיתוף פעולה. המשמעות היא שכל השינויים שמבצעים משתמשים שונים במכשירים שונים צריכים להגיע בכל מכשיר, בלי לבקש מהמשתמשים לפתור אף התנגשות. תוכנית Goodnotes פתרה את הבעיה הזו מזמן באמצעות שימוש ב-CRDT. הודות לסוגי הנתונים המשוכפלים ללא התנגשויות, Goodnotes יכול לשלב את כל השינויים שבוצעו בכל מסמך על ידי כל משתמש ולמזג את השינויים ללא התנגשות מיזוג. השימוש ב-IndexedDB ובאחסון הזמין לדפדפני אינטרנט תרם רבות לחוויית השימוש במצב אופליין לשיתוף פעולה באינטרנט.

האפליקציה Goodnotes פועלת במצב אופליין.

בנוסף, פתיחת אפליקציית האינטרנט של Goodnotes מובילה לעלות הורדה ראשונית של כ-40MB בגלל הגודל הבינארי של Wasm. בהתחלה, הצוות של Goodnotes הסתמך רק על המטמון הרגיל של הדפדפן של ה-App Bundle עצמו ועל רוב נקודות הקצה של ה-API שבהן נעשה שימוש, אבל במבט לאחור יכול להיות שהוא הרוויח קודם לכן מממשקי ה-API וה-Service Workers המהימנים יותר. הצוות במקור נמנע מהמשימה הזו בגלל שהעריכו שהיא מורכבת, אבל בסופו של דבר הם הבינו שתיבת העבודה היא הרבה פחות מפחידה.

המלצות לשימוש ב-Swift באינטרנט

יש לכם אפליקציה ל-iOS שיש בה הרבה קודים ואתם רוצים להשתמש בה שוב? כדאי להתכונן, כי אתם עומדים להתחיל מסע מדהים. לפני שתתחילו, הנה כמה טיפים שעשויים לעניין אתכם.

  • בודקים באיזה קוד רוצים להשתמש שוב. אם הלוגיקה העסקית של האפליקציה מיושמת בצד השרת, סביר להניח שתרצו להשתמש שוב בקוד של ממשק המשתמש, ולא תוכלו להיעזר ב-Wasm כאן. הצוות בדק בקצרה את Tokamak, מסגרת שתואמת ל-SwiftUI לבניית אפליקציות דפדפן באמצעות WebAssembly, אבל היא לא מספיק בוגרת בהתאם לצרכים של האפליקציה. עם זאת, אם באפליקציה שלכם מיושמים לוגיקה עסקית או אלגוריתמים חזקים בקוד הלקוח, Wasm תהיה החברה הכי טובה שלכם.
  • צריך לוודא שה-codebase של Swift מוכן. דפוסי עיצוב תוכנה בשכבת ממשק המשתמש או בארכיטקטורות ספציפיות, שיוצרים הפרדה חזקה בין הלוגיקה של ממשק המשתמש לבין הלוגיקה העסקית שלכם, כי לא תוכלו להשתמש שוב בהטמעה של שכבת ממשק המשתמש. גם ארכיטקטורה נקייה או עקרונות ארכיטקטורה משושה יהיו בסיסיים, כי תצטרכו להחדיר ולספק יחסי תלות לכל הקוד שקשור ל-IO, וקל יותר לעשות זאת אם משתמשים בארכיטקטורות האלה, שבהן פרטי ההטמעה מוגדרים כהפשטות, והעיקרון של היפוך התלות מיושם.
  • Wasm לא מספק קוד של ממשק המשתמש. לכן, צריך להחליט באיזו מסגרת ממשק משתמש להשתמש עבור האינטרנט.
  • JSKit יעזור לכם לשלב את קוד ה-Swift עם JavaScript, אבל חשוב לזכור אם יש לכם נתיב פופולרי, החציה של גשר JS–Swift עלולה להיות יקרה ותצטרכו להחליף אותו בפונקציות מיוצאות. מידע נוסף על אופן הפעולה של JSKit מופיע במסמכי התיעוד הרשמיים ובפוסט Dynamic Member Lookup ב-Swift, פנינה נסתרת!.
  • האפשרות להשתמש שוב בארכיטקטורה תלויה בארכיטקטורה שבה האפליקציה משתמשת ובספריית מנגנוני ביצוע הקוד האסינכרוניים שבה אתם משתמשים. תבניות כמו MVVP או ארכיטקטורה קומפוזבילי יעזרו לכם להשתמש שוב במודלים של הצפיות ובחלק מהלוגיקה של ממשק המשתמש, בלי לשלב את ההטמעה ליחסי תלות של UIKit שאי אפשר להשתמש בהם עם Wasm. ייתכן ש-RXSwift וספריות אחרות לא יתאימו ל-Wasm, לכן חשוב לזכור זאת כי תצטרכו להשתמש ב-OpenCombine, ב-async/await ובשידורים חיים בקוד Swift של Goodnotes.
  • דחוס את הקובץ הבינארי של Wasm באמצעות gzip או brotli. חשוב לזכור שגודל הקובץ הבינארי יהיה די גדול עבור אפליקציות אינטרנט קלאסיות.
  • גם אם אפשר להשתמש ב-Wasm בלי PWA, חשוב לכלול לפחות Service Worker, גם אם אין באפליקציית האינטרנט מניפסט או אם אתם לא רוצים שהמשתמש יתקין אותו. קובץ השירות ישמור ויציג את הקובץ הבינארי של Wasm בחינם, ואת כל משאבי האפליקציות, כך שהמשתמש לא יצטרך להוריד אותם בכל פעם שהוא פותח את הפרויקט.
  • חשוב לזכור שתהליך הגיוס יכול להיות קשה מהצפוי. ייתכן שתצטרכו להעסיק מפתחי אינטרנט חזקים עם ניסיון מסוים ב-Swift או מפתחי Swift חזקים עם ניסיון מסוים באינטרנט. אם אפשר למצוא מהנדסים כלליים עם ידע מסוים בשתי הפלטפורמות, זה יהיה נהדר

מסקנות

יצירה של פרויקט אינטרנט באמצעות סטאק תוכנות מורכב בזמן עבודה על מוצר מלא באתגרים היא חוויה מדהימה. זה יהיה קשה, אבל לגמרי שווה את זה. בזמן ש-Goodnotes לא הייתה אפשרות לפרסם גרסה ל-Windows, ל-Android, ל-ChromeOS ולאינטרנט, היא הייתה צריכה לעבוד על תכונות חדשות לאפליקציה ל-iOS בלי לנקוט גישה זו. הודות לסטאק התוכנות הזה וצוות המהנדסים של Goodnotes, Goodnotes נמצא עכשיו בכל מקום, והצוות מוכן להמשיך לעבוד על האתגרים הבאים! למידע נוסף על הפרויקט, תוכלו לצפות בהרצאה שצוות Goodnotes נתן ב-NSספרד 2023. אל תשכחו לנסות את Goodnotes באינטרנט!