From e1e39229725579d1a99cfefa3d2ae5e0e5359e71 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 22 Feb 2026 19:08:36 +0000 Subject: [PATCH 1/5] Update contributors list [skip ci] --- .all-contributorsrc | 459 +++++++++++++++++++++++--------------------- 1 file changed, 240 insertions(+), 219 deletions(-) diff --git a/.all-contributorsrc b/.all-contributorsrc index 87bac44a8..e428359d9 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -125,13 +125,6 @@ "profile": "https://github.com/jaysonzlin", "contributions": [] }, - { - "login": "andreamurillomtz", - "name": "Andrea", - "avatar_url": "https://avatars.githubusercontent.com/andreamurillomtz", - "profile": "https://github.com/andreamurillomtz", - "contributions": [] - }, { "login": "sophiacho1", "name": "Sophia Cho", @@ -139,6 +132,13 @@ "profile": "https://github.com/sophiacho1", "contributions": [] }, + { + "login": "andreamurillomtz", + "name": "Andrea", + "avatar_url": "https://avatars.githubusercontent.com/andreamurillomtz", + "profile": "https://github.com/andreamurillomtz", + "contributions": [] + }, { "login": "alxrod", "name": "Alex Rodriguez", @@ -181,6 +181,13 @@ "profile": "https://github.com/colbybanbury", "contributions": [] }, + { + "login": "GabrielAmazonas", + "name": "Gabriel Amazonas", + "avatar_url": "https://avatars.githubusercontent.com/GabrielAmazonas", + "profile": "https://github.com/GabrielAmazonas", + "contributions": [] + }, { "login": "mmaz", "name": "Mark Mazumder", @@ -189,10 +196,10 @@ "contributions": [] }, { - "login": "GabrielAmazonas", - "name": "Gabriel Amazonas", - "avatar_url": "https://avatars.githubusercontent.com/GabrielAmazonas", - "profile": "https://github.com/GabrielAmazonas", + "login": "ma3mool", + "name": "Abdulrahman Mahmoud", + "avatar_url": "https://avatars.githubusercontent.com/ma3mool", + "profile": "https://github.com/ma3mool", "contributions": [] }, { @@ -209,13 +216,6 @@ "profile": "https://github.com/srivatsankrishnan", "contributions": [] }, - { - "login": "ma3mool", - "name": "Abdulrahman Mahmoud", - "avatar_url": "https://avatars.githubusercontent.com/ma3mool", - "profile": "https://github.com/ma3mool", - "contributions": [] - }, { "login": "aptl26", "name": "Aghyad Deeb", @@ -223,13 +223,6 @@ "profile": "https://github.com/aptl26", "contributions": [] }, - { - "login": "arnaumarin", - "name": "marin-llobet", - "avatar_url": "https://avatars.githubusercontent.com/arnaumarin", - "profile": "https://github.com/arnaumarin", - "contributions": [] - }, { "login": "James-QiuHaoran", "name": "Haoran Qiu", @@ -238,17 +231,10 @@ "contributions": [] }, { - "login": "AditiR-42", - "name": "Aditi Raju", - "avatar_url": "https://avatars.githubusercontent.com/AditiR-42", - "profile": "https://github.com/AditiR-42", - "contributions": [] - }, - { - "login": "MichaelSchnebly", - "name": "Michael Schnebly", - "avatar_url": "https://avatars.githubusercontent.com/MichaelSchnebly", - "profile": "https://github.com/MichaelSchnebly", + "login": "arnaumarin", + "name": "marin-llobet", + "avatar_url": "https://avatars.githubusercontent.com/arnaumarin", + "profile": "https://github.com/arnaumarin", "contributions": [] }, { @@ -259,10 +245,10 @@ "contributions": [] }, { - "login": "ELSuitorHarvard", - "name": "ELSuitorHarvard", - "avatar_url": "https://avatars.githubusercontent.com/ELSuitorHarvard", - "profile": "https://github.com/ELSuitorHarvard", + "login": "MichaelSchnebly", + "name": "Michael Schnebly", + "avatar_url": "https://avatars.githubusercontent.com/MichaelSchnebly", + "profile": "https://github.com/MichaelSchnebly", "contributions": [] }, { @@ -273,17 +259,10 @@ "contributions": [] }, { - "login": "kaiM0ves", - "name": "kaiM0ves", - "avatar_url": "https://avatars.githubusercontent.com/kaiM0ves", - "profile": "https://github.com/kaiM0ves", - "contributions": [] - }, - { - "login": "jared-ni", - "name": "Jared Ni", - "avatar_url": "https://avatars.githubusercontent.com/jared-ni", - "profile": "https://github.com/jared-ni", + "login": "ELSuitorHarvard", + "name": "ELSuitorHarvard", + "avatar_url": "https://avatars.githubusercontent.com/ELSuitorHarvard", + "profile": "https://github.com/ELSuitorHarvard", "contributions": [] }, { @@ -293,6 +272,27 @@ "profile": "https://github.com/oishib", "contributions": [] }, + { + "login": "jared-ni", + "name": "Jared Ni", + "avatar_url": "https://avatars.githubusercontent.com/jared-ni", + "profile": "https://github.com/jared-ni", + "contributions": [] + }, + { + "login": "kaiM0ves", + "name": "kaiM0ves", + "avatar_url": "https://avatars.githubusercontent.com/kaiM0ves", + "profile": "https://github.com/kaiM0ves", + "contributions": [] + }, + { + "login": "AditiR-42", + "name": "Aditi Raju", + "avatar_url": "https://avatars.githubusercontent.com/AditiR-42", + "profile": "https://github.com/AditiR-42", + "contributions": [] + }, { "login": "eimlav", "name": "Eimhin Laverty", @@ -300,20 +300,6 @@ "profile": "https://github.com/eimlav", "contributions": [] }, - { - "login": "leo47007", - "name": "Yu-Shun Hsiao", - "avatar_url": "https://avatars.githubusercontent.com/leo47007", - "profile": "https://github.com/leo47007", - "contributions": [] - }, - { - "login": "jaywonchung", - "name": "Jae-Won Chung", - "avatar_url": "https://avatars.githubusercontent.com/jaywonchung", - "profile": "https://github.com/jaywonchung", - "contributions": [] - }, { "login": "BaeHenryS", "name": "Henry Bae", @@ -321,6 +307,20 @@ "profile": "https://github.com/BaeHenryS", "contributions": [] }, + { + "login": "jaywonchung", + "name": "Jae-Won Chung", + "avatar_url": "https://avatars.githubusercontent.com/jaywonchung", + "profile": "https://github.com/jaywonchung", + "contributions": [] + }, + { + "login": "leo47007", + "name": "Yu-Shun Hsiao", + "avatar_url": "https://avatars.githubusercontent.com/leo47007", + "profile": "https://github.com/leo47007", + "contributions": [] + }, { "login": "AndreaMattiaGaravagno", "name": "AndreaMattiaGaravagno", @@ -329,10 +329,31 @@ "contributions": [] }, { - "login": "pongtr", - "name": "Pong Trairatvorakul", - "avatar_url": "https://avatars.githubusercontent.com/pongtr", - "profile": "https://github.com/pongtr", + "login": "arbass22", + "name": "Andrew Bass", + "avatar_url": "https://avatars.githubusercontent.com/arbass22", + "profile": "https://github.com/arbass22", + "contributions": [] + }, + { + "login": "aryatschand", + "name": "Arya Tschand", + "avatar_url": "https://avatars.githubusercontent.com/aryatschand", + "profile": "https://github.com/aryatschand", + "contributions": [] + }, + { + "login": "ShvetankPrakash", + "name": "Shvetank Prakash", + "avatar_url": "https://avatars.githubusercontent.com/ShvetankPrakash", + "profile": "https://github.com/ShvetankPrakash", + "contributions": [] + }, + { + "login": "jianqingdu", + "name": "jianqingdu", + "avatar_url": "https://avatars.githubusercontent.com/jianqingdu", + "profile": "https://github.com/jianqingdu", "contributions": [] }, { @@ -349,41 +370,6 @@ "profile": "https://github.com/harvard-edge/cs249r_book/graphs/contributors", "contributions": [] }, - { - "login": "marcozennaro", - "name": "Marco Zennaro", - "avatar_url": "https://avatars.githubusercontent.com/marcozennaro", - "profile": "https://github.com/marcozennaro", - "contributions": [] - }, - { - "login": "jianqingdu", - "name": "jianqingdu", - "avatar_url": "https://avatars.githubusercontent.com/jianqingdu", - "profile": "https://github.com/jianqingdu", - "contributions": [] - }, - { - "login": "ShvetankPrakash", - "name": "Shvetank Prakash", - "avatar_url": "https://avatars.githubusercontent.com/ShvetankPrakash", - "profile": "https://github.com/ShvetankPrakash", - "contributions": [] - }, - { - "login": "aryatschand", - "name": "Arya Tschand", - "avatar_url": "https://avatars.githubusercontent.com/aryatschand", - "profile": "https://github.com/aryatschand", - "contributions": [] - }, - { - "login": "arbass22", - "name": "Andrew Bass", - "avatar_url": "https://avatars.githubusercontent.com/arbass22", - "profile": "https://github.com/arbass22", - "contributions": [] - }, { "login": "jzhou1318", "name": "Jennifer Zhou", @@ -399,24 +385,24 @@ "contributions": [] }, { - "login": "Fatima Shah", - "name": "Fatima Shah", - "avatar_url": "https://www.gravatar.com/avatar/468ef35acc69f3266efd700992daa369?d=identicon&s=100", - "profile": "https://github.com/harvard-edge/cs249r_book/graphs/contributors", + "login": "marcozennaro", + "name": "Marco Zennaro", + "avatar_url": "https://avatars.githubusercontent.com/marcozennaro", + "profile": "https://github.com/marcozennaro", "contributions": [] }, { - "login": "taunoe", - "name": "Tauno Erik", - "avatar_url": "https://avatars.githubusercontent.com/taunoe", - "profile": "https://github.com/taunoe", + "login": "pongtr", + "name": "Pong Trairatvorakul", + "avatar_url": "https://avatars.githubusercontent.com/pongtr", + "profile": "https://github.com/pongtr", "contributions": [] }, { - "login": "serco425", - "name": "Sercan Ayg\u00fcn", - "avatar_url": "https://avatars.githubusercontent.com/serco425", - "profile": "https://github.com/serco425", + "login": "alex-oesterling", + "name": "Alex Oesterling", + "avatar_url": "https://avatars.githubusercontent.com/alex-oesterling", + "profile": "https://github.com/alex-oesterling", "contributions": [] }, { @@ -426,6 +412,13 @@ "profile": "https://github.com/harvard-edge/cs249r_book/graphs/contributors", "contributions": [] }, + { + "login": "Fatima Shah", + "name": "Fatima Shah", + "avatar_url": "https://www.gravatar.com/avatar/468ef35acc69f3266efd700992daa369?d=identicon&s=100", + "profile": "https://github.com/harvard-edge/cs249r_book/graphs/contributors", + "contributions": [] + }, { "login": "vitasam", "name": "The Random DIY", @@ -433,20 +426,6 @@ "profile": "https://github.com/vitasam", "contributions": [] }, - { - "login": "TheHiddenLayer", - "name": "TheHiddenLayer", - "avatar_url": "https://avatars.githubusercontent.com/TheHiddenLayer", - "profile": "https://github.com/TheHiddenLayer", - "contributions": [] - }, - { - "login": "FinAminToastCrunch", - "name": "Fin Amin", - "avatar_url": "https://avatars.githubusercontent.com/FinAminToastCrunch", - "profile": "https://github.com/FinAminToastCrunch", - "contributions": [] - }, { "login": "BunningsWarehouseOfficial", "name": "Kristian Rado\u0161", @@ -454,34 +433,6 @@ "profile": "https://github.com/BunningsWarehouseOfficial", "contributions": [] }, - { - "login": "BrunoScaglione", - "name": "Bruno Scaglione", - "avatar_url": "https://avatars.githubusercontent.com/BrunoScaglione", - "profile": "https://github.com/BrunoScaglione", - "contributions": [] - }, - { - "login": "Allen-Kuang", - "name": "Allen-Kuang", - "avatar_url": "https://avatars.githubusercontent.com/Allen-Kuang", - "profile": "https://github.com/Allen-Kuang", - "contributions": [] - }, - { - "login": "alex-oesterling", - "name": "Alex Oesterling", - "avatar_url": "https://avatars.githubusercontent.com/alex-oesterling", - "profile": "https://github.com/alex-oesterling", - "contributions": [] - }, - { - "login": "gnodipac886", - "name": "gnodipac886", - "avatar_url": "https://avatars.githubusercontent.com/gnodipac886", - "profile": "https://github.com/gnodipac886", - "contributions": [] - }, { "login": "Gjain234", "name": "Gauri Jain", @@ -490,17 +441,10 @@ "contributions": [] }, { - "login": "aethernavshulkraven-allain", - "name": "\u0905\u0930\u0928\u0935 \u0936\u0941\u0915\u094d\u0932\u093e | Arnav Shukla", - "avatar_url": "https://avatars.githubusercontent.com/aethernavshulkraven-allain", - "profile": "https://github.com/aethernavshulkraven-allain", - "contributions": [] - }, - { - "login": "KarthikDani", - "name": "Karthik Dani", - "avatar_url": "https://avatars.githubusercontent.com/KarthikDani", - "profile": "https://github.com/KarthikDani", + "login": "taunoe", + "name": "Tauno Erik", + "avatar_url": "https://avatars.githubusercontent.com/taunoe", + "profile": "https://github.com/taunoe", "contributions": [] }, { @@ -510,6 +454,69 @@ "profile": "https://github.com/RinZ27", "contributions": [] }, + { + "login": "TheHiddenLayer", + "name": "TheHiddenLayer", + "avatar_url": "https://avatars.githubusercontent.com/TheHiddenLayer", + "profile": "https://github.com/TheHiddenLayer", + "contributions": [] + }, + { + "login": "Allen-Kuang", + "name": "Allen-Kuang", + "avatar_url": "https://avatars.githubusercontent.com/Allen-Kuang", + "profile": "https://github.com/Allen-Kuang", + "contributions": [] + }, + { + "login": "FinAminToastCrunch", + "name": "Fin Amin", + "avatar_url": "https://avatars.githubusercontent.com/FinAminToastCrunch", + "profile": "https://github.com/FinAminToastCrunch", + "contributions": [] + }, + { + "login": "BrunoScaglione", + "name": "Bruno Scaglione", + "avatar_url": "https://avatars.githubusercontent.com/BrunoScaglione", + "profile": "https://github.com/BrunoScaglione", + "contributions": [] + }, + { + "login": "serco425", + "name": "Sercan Ayg\u00fcn", + "avatar_url": "https://avatars.githubusercontent.com/serco425", + "profile": "https://github.com/serco425", + "contributions": [] + }, + { + "login": "gnodipac886", + "name": "gnodipac886", + "avatar_url": "https://avatars.githubusercontent.com/gnodipac886", + "profile": "https://github.com/gnodipac886", + "contributions": [] + }, + { + "login": "YangZhou1997", + "name": "Yang Zhou", + "avatar_url": "https://avatars.githubusercontent.com/YangZhou1997", + "profile": "https://github.com/YangZhou1997", + "contributions": [] + }, + { + "login": "aethernavshulkraven-allain", + "name": "\u0905\u0930\u0928\u0935 \u0936\u0941\u0915\u094d\u0932\u093e | Arnav Shukla", + "avatar_url": "https://avatars.githubusercontent.com/aethernavshulkraven-allain", + "profile": "https://github.com/aethernavshulkraven-allain", + "contributions": [] + }, + { + "login": "arighosh05", + "name": "Aritra Ghosh", + "avatar_url": "https://avatars.githubusercontent.com/arighosh05", + "profile": "https://github.com/arighosh05", + "contributions": [] + }, { "login": "XaicuL", "name": "JEON HYUNJUN(Luciano)", @@ -517,6 +524,20 @@ "profile": "https://github.com/XaicuL", "contributions": [] }, + { + "login": "KarthikDani", + "name": "Karthik Dani", + "avatar_url": "https://avatars.githubusercontent.com/KarthikDani", + "profile": "https://github.com/KarthikDani", + "contributions": [] + }, + { + "login": "Pratham-ja", + "name": "Pratham Chaudhary", + "avatar_url": "https://avatars.githubusercontent.com/Pratham-ja", + "profile": "https://github.com/Pratham-ja", + "contributions": [] + }, { "login": "Jahnic-kb", "name": "Jahnic Beck", @@ -524,13 +545,6 @@ "profile": "https://github.com/Jahnic-kb", "contributions": [] }, - { - "login": "AbenezerKb", - "name": "Abenezer Angamo", - "avatar_url": "https://avatars.githubusercontent.com/AbenezerKb", - "profile": "https://github.com/AbenezerKb", - "contributions": [] - }, { "login": "BravoBaldo", "name": "Baldassarre Cesarano", @@ -538,6 +552,20 @@ "profile": "https://github.com/BravoBaldo", "contributions": [] }, + { + "login": "AbenezerKb", + "name": "Abenezer Angamo", + "avatar_url": "https://avatars.githubusercontent.com/AbenezerKb", + "profile": "https://github.com/AbenezerKb", + "contributions": [] + }, + { + "login": "abigailswallow", + "name": "abigailswallow", + "avatar_url": "https://avatars.githubusercontent.com/abigailswallow", + "profile": "https://github.com/abigailswallow", + "contributions": [] + }, { "login": "adil-mubashir-ch", "name": "Adil Mubashir Chaudhry", @@ -545,13 +573,6 @@ "profile": "https://github.com/adil-mubashir-ch", "contributions": [] }, - { - "login": "YangZhou1997", - "name": "Yang Zhou", - "avatar_url": "https://avatars.githubusercontent.com/YangZhou1997", - "profile": "https://github.com/YangZhou1997", - "contributions": [] - }, { "login": "cursoragent", "name": "Cursor Agent", @@ -566,13 +587,6 @@ "profile": "https://github.com/bilgeacun", "contributions": [] }, - { - "login": "arighosh05", - "name": "Aritra Ghosh", - "avatar_url": "https://avatars.githubusercontent.com/arighosh05", - "profile": "https://github.com/arighosh05", - "contributions": [] - }, { "login": "atcheng2", "name": "Andy Cheng", @@ -581,17 +595,10 @@ "contributions": [] }, { - "login": "abigailswallow", - "name": "abigailswallow", - "avatar_url": "https://avatars.githubusercontent.com/abigailswallow", - "profile": "https://github.com/abigailswallow", - "contributions": [] - }, - { - "login": "emmanuel2406", - "name": "Emmanuel Rassou", - "avatar_url": "https://avatars.githubusercontent.com/emmanuel2406", - "profile": "https://github.com/emmanuel2406", + "login": "salmanmkc", + "name": "Salman Chishti", + "avatar_url": "https://avatars.githubusercontent.com/salmanmkc", + "profile": "https://github.com/salmanmkc", "contributions": [] }, { @@ -601,6 +608,20 @@ "profile": "https://github.com/happyappledog", "contributions": [] }, + { + "login": "emmanuel2406", + "name": "Emmanuel Rassou", + "avatar_url": "https://avatars.githubusercontent.com/emmanuel2406", + "profile": "https://github.com/emmanuel2406", + "contributions": [] + }, + { + "login": "jasonlyik", + "name": "Jason Yik", + "avatar_url": "https://avatars.githubusercontent.com/jasonlyik", + "profile": "https://github.com/jasonlyik", + "contributions": [] + }, { "login": "jessicaquaye", "name": "Jessica Quaye", @@ -608,6 +629,13 @@ "profile": "https://github.com/jessicaquaye", "contributions": [] }, + { + "login": "skmur", + "name": "Sonia Murthy", + "avatar_url": "https://avatars.githubusercontent.com/skmur", + "profile": "https://github.com/skmur", + "contributions": [] + }, { "login": "sjohri20", "name": "Shreya Johri", @@ -622,20 +650,6 @@ "profile": "https://github.com/pipme", "contributions": [] }, - { - "login": "jasonlyik", - "name": "Jason Yik", - "avatar_url": "https://avatars.githubusercontent.com/jasonlyik", - "profile": "https://github.com/jasonlyik", - "contributions": [] - }, - { - "login": "snuggs", - "name": "Snuggs", - "avatar_url": "https://avatars.githubusercontent.com/snuggs", - "profile": "https://github.com/snuggs", - "contributions": [] - }, { "login": "swilcock0", "name": "Sam Wilcock", @@ -644,10 +658,10 @@ "contributions": [] }, { - "login": "skmur", - "name": "Sonia Murthy", - "avatar_url": "https://avatars.githubusercontent.com/skmur", - "profile": "https://github.com/skmur", + "login": "snuggs", + "name": "Snuggs", + "avatar_url": "https://avatars.githubusercontent.com/snuggs", + "profile": "https://github.com/snuggs", "contributions": [] }, { @@ -713,6 +727,13 @@ "profile": "https://github.com/harvard-edge/cs249r_book/graphs/contributors", "contributions": [] }, + { + "login": "Pratham-ja", + "name": "Pratham-ja", + "avatar_url": "https://www.gravatar.com/avatar/438310e71ca943b41e92065c2dbbddb6?d=identicon&s=100", + "profile": "https://github.com/harvard-edge/cs249r_book/graphs/contributors", + "contributions": [] + }, { "login": "Edward Jin", "name": "Edward Jin", From e5d83952654e6112f90f618b5fe3c0aa24b86ae3 Mon Sep 17 00:00:00 2001 From: Aditya Mulik Date: Wed, 25 Feb 2026 05:24:01 -0500 Subject: [PATCH 2/5] The multiplication process example is corrected --- tinytorch/src/01_tensor/01_tensor.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tinytorch/src/01_tensor/01_tensor.py b/tinytorch/src/01_tensor/01_tensor.py index c031e2ad2..3af3c4868 100644 --- a/tinytorch/src/01_tensor/01_tensor.py +++ b/tinytorch/src/01_tensor/01_tensor.py @@ -967,11 +967,11 @@ Data Samples × Projection Matrix = Projected Data ``` Matrix Multiplication Process: A (2×3) B (3×2) C (2×2) - ┌ ┐ ┌ ┐ ┌ ┐ - │ 1 2 3 │ │ 7 8 │ │ 1×7+2×9+3×1 │ ┌ ┐ - │ │ × │ 9 1 │ = │ │ = │ 28 16 │ - │ 4 5 6 │ │ 1 2 │ │ 4×7+5×9+6×1 │ │ 79 49 │ - └ ┘ └ ┘ └ ┘ └ ┘ + ┌ ┐ ┌ ┐ ┌ ┐ + │ 1 2 3 │ │ 7 8 │ │ 1×7+2×9+3×1 1*8+2*1+3*2 │ ┌ ┐ + │ │ × │ 9 1 │ = │ │ = │ 28 16 │ + │ 4 5 6 │ │ 1 2 │ │ 4×7+5×9+6×1 4*8+1*5+6*2 │ │ 79 49 │ + └ ┘ └ ┘ └ ┘ └ ┘ Computation Breakdown: C[0,0] = A[0,:] · B[:,0] = [1,2,3] · [7,9,1] = 1×7 + 2×9 + 3×1 = 28 From 130d3cc133fd330238711410575849f9f2f3cc5a Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 25 Feb 2026 13:09:27 +0000 Subject: [PATCH 3/5] docs: add @adityamulik as tinytorch contributor for doc --- README.md | 1 + tinytorch/.all-contributorsrc | 9 +++++++++ tinytorch/README.md | 1 + 3 files changed, 11 insertions(+) diff --git a/README.md b/README.md index a3fffd7d6..600084bc7 100644 --- a/README.md +++ b/README.md @@ -471,6 +471,7 @@ Thanks goes to these wonderful people who have contributed to making this resour harishb00a
harishb00a

✍️ Pastor Soto
Pastor Soto

✍️ Salman Chishti
Salman Chishti

🧑‍💻 + Aditya Mulik
Aditya Mulik

✍️ diff --git a/tinytorch/.all-contributorsrc b/tinytorch/.all-contributorsrc index 2886fe432..de7a89ffc 100644 --- a/tinytorch/.all-contributorsrc +++ b/tinytorch/.all-contributorsrc @@ -234,6 +234,15 @@ "contributions": [ "code" ] + }, + { + "login": "adityamulik", + "name": "Aditya Mulik", + "avatar_url": "https://avatars.githubusercontent.com/u/10626835?v=4", + "profile": "https://github.com/adityamulik", + "contributions": [ + "doc" + ] } ] } diff --git a/tinytorch/README.md b/tinytorch/README.md index d8db763aa..4b002fc18 100644 --- a/tinytorch/README.md +++ b/tinytorch/README.md @@ -295,6 +295,7 @@ Thanks to these wonderful people who helped improve TinyTorch! Salman Chishti
Salman Chishti

🧑‍💻 + Aditya Mulik
Aditya Mulik

✍️ From 5ae6b3bd5ff8bd0cbb7fe045198a429bbd2f0388 Mon Sep 17 00:00:00 2001 From: kai Date: Fri, 27 Feb 2026 01:26:24 -0500 Subject: [PATCH 4/5] updated calendar to pull from real cal, account deletions enabled --- tinytorch/site/extra/community/app.js | 5 +- tinytorch/site/extra/community/events.html | 300 +++++++++++++++--- .../site/extra/community/modules/profile.js | 90 +++++- .../site/extra/community/modules/styles.js | 27 ++ tinytorch/site/extra/community/modules/ui.js | 26 +- 5 files changed, 395 insertions(+), 53 deletions(-) diff --git a/tinytorch/site/extra/community/app.js b/tinytorch/site/extra/community/app.js index 00f0a0298..9b55e7e61 100644 --- a/tinytorch/site/extra/community/app.js +++ b/tinytorch/site/extra/community/app.js @@ -2,7 +2,7 @@ import { injectStyles } from './modules/styles.js'; import { renderLayout, updateNavState } from './modules/ui.js?v=3'; import { getSession } from './modules/state.js?v=2'; import { openModal, closeModal, handleToggle, handleAuth, handleLogout, setMode, verifySession, signInWithSocial, supabase } from './modules/auth.js?v=3'; -import { openProfileModal, closeProfileModal, handleProfileUpdate, geocodeAndSetCoordinates, checkAndAutoUpdateLocation } from './modules/profile.js'; +import { openProfileModal, closeProfileModal, handleProfileUpdate, geocodeAndSetCoordinates, checkAndAutoUpdateLocation, setupProfileDeleteEvents } from './modules/profile.js'; import { setupCameraEvents } from './modules/camera.js'; import { getBasePath } from './modules/config.js'; @@ -32,6 +32,9 @@ import { getBasePath } from './modules/config.js'; }); } + // Initialize profile events + setupProfileDeleteEvents(); + // 2.6 Check for Supabase Session & Verify const checkProfile = async (session) => { if (!session || window.location.pathname.includes('profile_setup')) return; diff --git a/tinytorch/site/extra/community/events.html b/tinytorch/site/extra/community/events.html index f04fdd1b1..cbd9ec8dd 100644 --- a/tinytorch/site/extra/community/events.html +++ b/tinytorch/site/extra/community/events.html @@ -27,7 +27,7 @@ margin-bottom: 50px; width: 95%; max-width: 1200px; - background: rgba(255, 255, 255, 0.95); + background: #fff; /* Solid white background */ border: 1px solid #333; box-shadow: 8px 8px 0px rgba(0,0,0,0.1); padding: 40px; @@ -150,9 +150,11 @@ .legend { margin-top: 20px; display: flex; - gap: 20px; + flex-direction: column; + gap: 15px; font-size: 0.8rem; font-family: 'Verdana', sans-serif; + align-items: flex-start; } .legend-item { @@ -167,6 +169,104 @@ border-radius: 2px; } + /* Modal Styles */ + .modal-overlay { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba(0, 0, 0, 0.7); + display: none; + justify-content: center; + align-items: center; + z-index: 2000; + backdrop-filter: blur(4px); + } + + .modal-content { + background: #fff; + width: 90%; + max-width: 500px; + border: 2px solid #333; + box-shadow: 10px 10px 0px #ff6600; + padding: 30px; + position: relative; + animation: modalSlide 0.3s ease-out; + } + + @keyframes modalSlide { + from { transform: translateY(20px); opacity: 0; } + to { transform: translateY(0); opacity: 1; } + } + + .modal-close { + position: absolute; + top: 10px; + right: 15px; + font-size: 24px; + cursor: pointer; + font-weight: bold; + } + + .modal-title { + font-size: 1.4rem; + margin-bottom: 15px; + color: #ff6600; + border-bottom: 2px dashed #eee; + padding-bottom: 10px; + text-transform: uppercase; + } + + .modal-details { + font-family: 'Verdana', sans-serif; + font-size: 0.9rem; + line-height: 1.6; + } + + .modal-row { + margin-bottom: 12px; + } + + .modal-label { + font-weight: bold; + color: #555; + display: block; + font-size: 0.75rem; + text-transform: uppercase; + } + + .show-more { + font-size: 0.7rem; + color: #ff6600; + cursor: pointer; + font-weight: bold; + text-align: center; + display: block; + margin-top: 2px; + text-decoration: underline; + } + + .btn-google { + display: block; + width: 100%; + text-align: center; + background: #ff6600; + color: white !important; + padding: 12px; + text-decoration: none; + font-weight: bold; + margin-top: 25px; + border: 2px solid #333; + box-shadow: 4px 4px 0px #333; + transition: all 0.1s; + } + + .btn-google:hover { + transform: translate(-2px, -2px); + box-shadow: 6px 6px 0px #333; + } + /* Responsive */ @media (max-width: 700px) { .day { min-height: 60px; } @@ -202,17 +302,45 @@
-
-
- General Events +
+
+
+ General Events +
+
+
+ Historical Dates +
+
+
+ Community +
-
-
- Historical Dates -
- +
+ + + @@ -222,49 +350,111 @@ const monthNames = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]; - const allEvents = [ - // December 2025 events (based on user's current context) + // Static historical or community events + const staticEvents = [ { date: new Date(2025, 11, 1), description: 'Tiny Torch Community is born', type: 'community' }, { date: new Date(2025, 11, 4), description: 'NeuralIPS 2025 Kickoff', type: 'general' }, { date: new Date(2025, 11, 8), description: 'Tiny Torch merged with MLSYSBOOK.ai', type: 'community' }, { date: new Date(2025, 11, 9), description: 'Tiny Torch official launch', type: 'community' }, { date: new Date(2025, 11, 10), description: 'Ada Lovelace Birthday (1815)', type: 'historical' }, - { date: new Date(2025, 11, 17), description: 'MLSYSBOOK 10k GitHub Stars Celebration via Edge AI Foundation', type: 'community' }, + { date: new Date(2025, 11, 17), description: 'MLSYSBOOK 10k GitHub Stars', type: 'community' }, { date: new Date(2025, 11, 25), description: 'AI Winter Solstice', type: 'general' }, - // Additional AI known dates (historical or general for other months) - { date: new Date(2022, 10, 30), description: 'ChatGPT Launched', type: 'historical' }, // November 30, 2022 - { date: new Date(1956, 7, 1), description: 'Dartmouth Conference (Birth of AI)', type: 'historical' }, // August 1, 1956 - { date: new Date(1997, 4, 11), description: 'Deep Blue vs Garry Kasparov', type: 'historical' }, // May 11, 1997 - { date: new Date(2016, 2, 9), description: 'AlphaGo vs Lee Sedol', type: 'historical' }, // March 9, 2016 - { date: new Date(2025, 0, 15), description: 'Future AI Summit', type: 'general' }, // January 15, 2025 - { date: new Date(2025, 1, 20), description: 'Quantum AI Workshop', type: 'community' }, // February 20, 2025 - { date: new Date(2025, 8, 1), description: 'Generative AI Conference', type: 'general' }, // Sep 1, 2025 - { date: new Date(2025, 9, 26), description: 'AI Ethics Panel', type: 'community' } // Oct 26, 2025 + { date: new Date(2022, 10, 30), description: 'ChatGPT Launched', type: 'historical' }, + { date: new Date(1956, 7, 1), description: 'Dartmouth Conference', type: 'historical' }, + { date: new Date(1997, 4, 11), description: 'Deep Blue vs Kasparov', type: 'historical' }, + { date: new Date(2016, 2, 9), description: 'AlphaGo vs Lee Sedol', type: 'historical' } ]; - let currentYear = 2025; // Initial year (from context: Dec 1, 2025) - let currentMonth = 11; // Initial month (December is 11) + let fetchedEvents = []; + + let today = new Date(); + if (today.getFullYear() < 2026) { + today = new Date(2026, 1, 27); + } + + let currentYear = today.getFullYear(); + let currentMonth = today.getMonth(); + + async function fetchExternalEvents(year, month) { + const timeMin = new Date(year, month, 1).toISOString(); + const timeMax = new Date(year, month + 1, 1).toISOString(); + const url = `https://ai-engineering-home.vercel.app/api/calendar?timeMin=${timeMin}&timeMax=${timeMax}`; + + try { + const response = await fetch(url); + if (!response.ok) throw new Error('API request failed'); + const data = await response.json(); + return (data.events || []).map(event => ({ + date: new Date(event.start), + endDate: new Date(event.end), + description: event.summary, + fullDescription: event.description, + location: event.location, + type: 'general', + link: event.htmlLink + })); + } catch (error) { + console.error('Error fetching calendar events:', error); + return []; + } + } + + function openEventModal(event) { + const modal = document.getElementById('eventModal'); + document.getElementById('modalTitle').textContent = event.description; + + const startStr = event.date.toLocaleString([], { dateStyle: 'long', timeStyle: 'short' }); + const endStr = event.endDate ? ' to ' + event.endDate.toLocaleTimeString([], { timeStyle: 'short' }) : ''; + document.getElementById('modalWhen').textContent = startStr + endStr; + + const locRow = document.getElementById('locationRow'); + if (event.location) { + locRow.style.display = 'block'; + document.getElementById('modalWhere').textContent = event.location; + } else { + locRow.style.display = 'none'; + } + + const descRow = document.getElementById('descriptionRow'); + if (event.fullDescription) { + descRow.style.display = 'block'; + document.getElementById('modalDescription').innerHTML = event.fullDescription.replace(/\n/g, '
'); + } else { + descRow.style.display = 'none'; + } + + const linkBtn = document.getElementById('modalLink'); + if (event.link) { + linkBtn.style.display = 'inline-block'; + linkBtn.href = event.link; + } else { + linkBtn.style.display = 'none'; + } + + modal.style.display = 'flex'; + } function getDaysInMonth(year, month) { return new Date(year, month + 1, 0).getDate(); } function getFirstDayOfMonth(year, month) { - // 0 = Sunday, 1 = Monday, etc. return new Date(year, month, 1).getDay(); } - function renderCalendar() { + async function renderCalendar() { const currentMonthYearSpan = document.getElementById('currentMonthYear'); const calendarBody = document.getElementById('calendar-body'); currentMonthYearSpan.textContent = `${monthNames[currentMonth]} ${currentYear}`; - calendarBody.innerHTML = ''; // Clear previous days + + fetchedEvents = await fetchExternalEvents(currentYear, currentMonth); + + calendarBody.innerHTML = ''; const daysInMonth = getDaysInMonth(currentYear, currentMonth); - const firstDay = getFirstDayOfMonth(currentYear, currentMonth); // 0 for Sunday, 1 for Monday + const firstDay = getFirstDayOfMonth(currentYear, currentMonth); - // Fill leading empty days (from previous month) const prevMonthDays = getDaysInMonth(currentYear, currentMonth - 1); for (let i = 0; i < firstDay; i++) { const dayDiv = document.createElement('div'); @@ -273,35 +463,51 @@ calendarBody.appendChild(dayDiv); } - // Fill current month days for (let i = 1; i <= daysInMonth; i++) { const dayDiv = document.createElement('div'); dayDiv.classList.add('day'); dayDiv.innerHTML = `
${i}
`; - // Add events for this day - const dayEvents = allEvents.filter(event => + const combinedEvents = [...staticEvents, ...fetchedEvents]; + const dayEvents = combinedEvents.filter(event => event.date.getFullYear() === currentYear && event.date.getMonth() === currentMonth && event.date.getDate() === i ); - dayEvents.forEach(event => { + const maxVisible = 2; + dayEvents.slice(0, maxVisible).forEach(event => { const eventMarker = document.createElement('div'); eventMarker.classList.add('event-marker'); eventMarker.textContent = event.description; - if (event.type) { - eventMarker.classList.add(event.type); - } + if (event.type) eventMarker.classList.add(event.type); + eventMarker.onclick = (e) => { + e.stopPropagation(); + openEventModal(event); + }; dayDiv.appendChild(eventMarker); }); + if (dayEvents.length > maxVisible) { + const more = document.createElement('div'); + more.classList.add('show-more'); + more.textContent = `+ ${dayEvents.length - maxVisible} more`; + more.onclick = (e) => { + // For simplicity, just show the third event in the modal + // or could list them all. Here we'll just open the modal for the first one + // and let the user navigate or list them in the modal. + // Better: open the modal with a list if clicked "more". + // For now, let's just make the whole day clickable if there's more. + openEventModal(dayEvents[maxVisible]); + }; + dayDiv.appendChild(more); + } + calendarBody.appendChild(dayDiv); } - // Fill trailing empty days (from next month) const totalDaysRendered = firstDay + daysInMonth; - const remainingCells = 42 - totalDaysRendered; // Max 6 rows * 7 days = 42 cells + const remainingCells = 42 - totalDaysRendered; for (let i = 1; i <= remainingCells; i++) { const dayDiv = document.createElement('div'); dayDiv.classList.add('day', 'other-month'); @@ -310,7 +516,7 @@ } } - function changeMonth(delta) { + async function changeMonth(delta) { currentMonth += delta; if (currentMonth < 0) { currentMonth = 11; @@ -319,16 +525,20 @@ currentMonth = 0; currentYear++; } - renderCalendar(); + await renderCalendar(); } - // Initial render renderCalendar(); - // Event listeners for navigation buttons document.getElementById('prevMonth').addEventListener('click', () => changeMonth(-1)); document.getElementById('nextMonth').addEventListener('click', () => changeMonth(1)); - + document.getElementById('modalClose').addEventListener('click', () => { + document.getElementById('eventModal').style.display = 'none'; + }); + window.addEventListener('click', (e) => { + const modal = document.getElementById('eventModal'); + if (e.target === modal) modal.style.display = 'none'; + }); diff --git a/tinytorch/site/extra/community/modules/profile.js b/tinytorch/site/extra/community/modules/profile.js index 1ddfe1683..ae7e0c611 100644 --- a/tinytorch/site/extra/community/modules/profile.js +++ b/tinytorch/site/extra/community/modules/profile.js @@ -1,5 +1,5 @@ import { SUPABASE_URL, NETLIFY_URL, getBasePath } from './config.js'; -import { forceLogin } from './state.js?v=2'; +import { forceLogin, getSession } from './state.js?v=2'; import { initCandle } from './candle.js'; export async function geocodeAndSetCoordinates(location) { @@ -39,6 +39,21 @@ let candleInitialized = false; export function openProfileModal() { const profileOverlay = document.getElementById('profileOverlay'); profileOverlay.classList.add('active'); + + // Reset delete section + const deleteBtn = document.getElementById('profileDeleteBtn'); + const deleteConfirmSection = document.getElementById('deleteConfirmSection'); + const deleteConfirmInput = document.getElementById('profileDeleteConfirmInput'); + const deleteFinalBtn = document.getElementById('profileDeleteFinalBtn'); + if (deleteBtn) deleteBtn.classList.remove('hidden'); + if (deleteConfirmSection) deleteConfirmSection.classList.add('hidden'); + if (deleteConfirmInput) deleteConfirmInput.value = ''; + if (deleteFinalBtn) { + deleteFinalBtn.disabled = true; + deleteFinalBtn.style.opacity = '0.5'; + deleteFinalBtn.style.cursor = 'not-allowed'; + } + fetchUserProfile(); if (!candleInitialized) { @@ -49,6 +64,79 @@ export function openProfileModal() { } } +export function setupProfileDeleteEvents() { + const deleteBtn = document.getElementById('profileDeleteBtn'); + const deleteConfirmSection = document.getElementById('deleteConfirmSection'); + const deleteConfirmName = document.getElementById('deleteConfirmName'); + const deleteConfirmInput = document.getElementById('profileDeleteConfirmInput'); + const deleteFinalBtn = document.getElementById('profileDeleteFinalBtn'); + const profileDisplayNameInput = document.getElementById('profileDisplayName'); + + if (!deleteBtn) return; + + deleteBtn.addEventListener('click', () => { + let displayName = profileDisplayNameInput.value.trim(); + if (!displayName) { + const { email } = getSession(); + displayName = email || 'DELETE'; + } + deleteConfirmName.textContent = displayName; + deleteConfirmSection.classList.remove('hidden'); + deleteBtn.classList.add('hidden'); + }); + + deleteConfirmInput.addEventListener('input', () => { + const displayName = deleteConfirmName.textContent.trim(); + if (deleteConfirmInput.value.trim() === displayName) { + deleteFinalBtn.disabled = false; + deleteFinalBtn.style.opacity = '1'; + deleteFinalBtn.style.cursor = 'pointer'; + } else { + deleteFinalBtn.disabled = true; + deleteFinalBtn.style.opacity = '0.5'; + deleteFinalBtn.style.cursor = 'not-allowed'; + } + }); + + deleteFinalBtn.addEventListener('click', async () => { + const confirmResult = confirm("Are you absolutely sure? This will delete all your data and access."); + if (!confirmResult) return; + + deleteFinalBtn.disabled = true; + deleteFinalBtn.textContent = 'Deleting...'; + + const token = localStorage.getItem("tinytorch_token"); + try { + const response = await fetch(`${SUPABASE_URL}/delete-account`, { + method: 'POST', + headers: { + 'Authorization': `Bearer ${token}`, + 'Content-Type': 'application/json' + } + }); + + if (!response.ok) { + let errData = {}; + try { + errData = await response.json(); + } catch(e) {} + throw new Error(errData.error || 'Failed to delete account'); + } + + alert("Your account has been deleted."); + localStorage.removeItem("tinytorch_token"); + localStorage.removeItem("tinytorch_refresh_token"); + localStorage.removeItem("tinytorch_user"); + window.location.href = getBasePath() + '/index.html'; + } catch (error) { + console.error("Delete account error:", error); + alert("Error deleting account: " + error.message); + deleteFinalBtn.disabled = false; + deleteFinalBtn.textContent = 'Permanently Delete My Account'; + } + }); +} + export function closeProfileModal() { const profileOverlay = document.getElementById('profileOverlay'); profileOverlay.classList.remove('active'); diff --git a/tinytorch/site/extra/community/modules/styles.js b/tinytorch/site/extra/community/modules/styles.js index 8c3856d11..a71108c26 100644 --- a/tinytorch/site/extra/community/modules/styles.js +++ b/tinytorch/site/extra/community/modules/styles.js @@ -441,6 +441,33 @@ export function injectStyles() { background: #b71c1c; } + .profile-delete-btn { + width: 100%; + padding: 12px; + background: transparent; + color: #d32f2f; + border: 1px solid #d32f2f; + border-radius: 10px; + font-size: 1rem; + font-weight: bold; + cursor: pointer; + transition: all 0.2s; + } + + .profile-delete-btn:hover:not(:disabled) { + background: #fff5f5; + } + + #profileDeleteFinalBtn { + background: #d32f2f; + color: white; + border: none; + } + + #profileDeleteFinalBtn:hover:not(:disabled) { + background: #b71c1c; + } + /* --- Mobile Optimizations --- */ @media (max-width: 768px) { /* Sidebar becomes a bottom sheet */ diff --git a/tinytorch/site/extra/community/modules/ui.js b/tinytorch/site/extra/community/modules/ui.js index 18c5479b6..7d5e6fe1f 100644 --- a/tinytorch/site/extra/community/modules/ui.js +++ b/tinytorch/site/extra/community/modules/ui.js @@ -132,15 +132,15 @@ export function renderLayout() {
-
-
-

Your Profile

+
+
+

Your Profile

- +
@@ -180,12 +180,26 @@ export function renderLayout() { + +
+

Danger Zone

+

Deleting your account is permanent and cannot be undone.

+ + + +
-
- +
+
+ +
From 81373c5dd72f25b25ae8f1cac7768e17fe1d5896 Mon Sep 17 00:00:00 2001 From: kai Date: Fri, 27 Feb 2026 17:06:38 -0500 Subject: [PATCH 5/5] bug fix user manual account broken --- tinytorch/site/extra/community/.gitignore | 8 + tinytorch/site/extra/community/app.js | 50 ++-- tinytorch/site/extra/community/community.html | 219 +++++++++--------- .../site/extra/community/modules/auth.js | 158 +++++++------ .../site/extra/community/modules/candle.js | 9 + .../site/extra/community/modules/guard.js | 127 +++++----- .../site/extra/community/modules/profile.js | 6 +- .../site/extra/community/modules/state.js | 50 +++- tinytorch/site/extra/community/modules/ui.js | 2 +- .../site/extra/community/package-lock.json | 78 +++++++ tinytorch/site/extra/community/package.json | 12 + .../site/extra/community/playwright.config.js | 21 ++ .../site/extra/community/profile_setup.html | 177 ++++++-------- .../extra/community/tests/e2e/auth.spec.js | 77 ++++++ .../tests/e2e/credentials.example.json | 6 + .../community/tests/e2e/lifecycle.spec.js | 48 ++++ 16 files changed, 661 insertions(+), 387 deletions(-) create mode 100644 tinytorch/site/extra/community/.gitignore create mode 100644 tinytorch/site/extra/community/package-lock.json create mode 100644 tinytorch/site/extra/community/package.json create mode 100644 tinytorch/site/extra/community/playwright.config.js create mode 100644 tinytorch/site/extra/community/tests/e2e/auth.spec.js create mode 100644 tinytorch/site/extra/community/tests/e2e/credentials.example.json create mode 100644 tinytorch/site/extra/community/tests/e2e/lifecycle.spec.js diff --git a/tinytorch/site/extra/community/.gitignore b/tinytorch/site/extra/community/.gitignore new file mode 100644 index 000000000..b5b06f31b --- /dev/null +++ b/tinytorch/site/extra/community/.gitignore @@ -0,0 +1,8 @@ +# Secrets +tests/e2e/credentials.json + +# Playwright +test-results/ +playwright-report/ +blob-report/ +playwright/.cache/ diff --git a/tinytorch/site/extra/community/app.js b/tinytorch/site/extra/community/app.js index 9b55e7e61..c2144e8dc 100644 --- a/tinytorch/site/extra/community/app.js +++ b/tinytorch/site/extra/community/app.js @@ -1,7 +1,7 @@ import { injectStyles } from './modules/styles.js'; -import { renderLayout, updateNavState } from './modules/ui.js?v=3'; -import { getSession } from './modules/state.js?v=2'; -import { openModal, closeModal, handleToggle, handleAuth, handleLogout, setMode, verifySession, signInWithSocial, supabase } from './modules/auth.js?v=3'; +import { renderLayout, updateNavState } from './modules/ui.js'; +import { getSession } from './modules/state.js'; +import { openModal, closeModal, handleToggle, handleAuth, handleLogout, setMode, verifySession, signInWithSocial, supabase } from './modules/auth.js'; import { openProfileModal, closeProfileModal, handleProfileUpdate, geocodeAndSetCoordinates, checkAndAutoUpdateLocation, setupProfileDeleteEvents } from './modules/profile.js'; import { setupCameraEvents } from './modules/camera.js'; import { getBasePath } from './modules/config.js'; @@ -35,38 +35,24 @@ import { getBasePath } from './modules/config.js'; // Initialize profile events setupProfileDeleteEvents(); - // 2.6 Check for Supabase Session & Verify - const checkProfile = async (session) => { - if (!session || window.location.pathname.includes('profile_setup')) return; - - const { data: profile } = await supabase - .from('profiles') - .select('display_name, institution, location') - .eq('id', session.user.id) - .single(); - - const hasName = profile && profile.display_name; - const hasInst = profile && profile.institution && (Array.isArray(profile.institution) ? profile.institution.length > 0 : !!profile.institution); - const hasLoc = profile && profile.location; - - if (!hasName || !hasInst || !hasLoc) { - window.location.href = getBasePath() + '/profile_setup.html'; - } - }; - supabase.auth.getSession().then(({ data: { session } }) => { if (session) { localStorage.setItem("tinytorch_token", session.access_token); if (session.refresh_token) localStorage.setItem("tinytorch_refresh_token", session.refresh_token); if (session.user) localStorage.setItem("tinytorch_user", JSON.stringify(session.user)); - // Clean URL hash if present (Supabase puts tokens there) - if (window.location.hash && window.location.hash.includes('access_token')) { - window.history.replaceState({}, document.title, window.location.pathname + window.location.search); + // Clean URL of tokens/code (Supabase puts tokens in hash or code in query) + if ((window.location.hash && window.location.hash.includes('access_token')) || + (window.location.search && window.location.search.includes('code='))) { + // Remove sensitive parameters while preserving non-sensitive ones + const url = new URL(window.location); + url.hash = ''; + url.searchParams.delete('code'); + url.searchParams.delete('type'); + window.history.replaceState({}, document.title, url.toString()); } updateNavState(); - checkProfile(session); } // 3. Verify Session (Async) verifySession(); @@ -82,7 +68,6 @@ import { getBasePath } from './modules/config.js'; if (session.refresh_token) localStorage.setItem("tinytorch_refresh_token", session.refresh_token); if (session.user) localStorage.setItem("tinytorch_user", JSON.stringify(session.user)); updateNavState(); - checkProfile(session); } else if (event === 'SIGNED_OUT') { localStorage.removeItem("tinytorch_token"); updateNavState(); @@ -171,10 +156,13 @@ import { getBasePath } from './modules/config.js'; const action = params.get('action'); if (action === 'login') { - localStorage.removeItem("tinytorch_token"); - localStorage.removeItem("tinytorch_refresh_token"); - localStorage.removeItem("tinytorch_user"); - updateNavState(); + const { isLoggedIn } = getSession(); + if (!isLoggedIn) { + localStorage.removeItem("tinytorch_token"); + localStorage.removeItem("tinytorch_refresh_token"); + localStorage.removeItem("tinytorch_user"); + updateNavState(); + } openModal('login'); } else if (action === 'profile') { const { isLoggedIn } = getSession(); diff --git a/tinytorch/site/extra/community/community.html b/tinytorch/site/extra/community/community.html index d01d87e7b..5ecd8dc53 100644 --- a/tinytorch/site/extra/community/community.html +++ b/tinytorch/site/extra/community/community.html @@ -289,15 +289,18 @@
- 0 Members + 0 Members on Map +
+
+ + 0 without Location (Lost at Sea)
0 Institutions
-
- - Members with Unknown Location +
+ Note: Members without a location set in their profile are placed at the Sea Station.
@@ -307,7 +310,6 @@ - - // Handle Window Resize - window.addEventListener('resize', () => { - const w = window.innerWidth; - const h = window.innerHeight; - svg.attr('width', w).attr('height', h); - projection.translate([w/2, h/2]).scale(Math.min(w, h) / 2.5); - center[0] = w/2; - center[1] = h/2; + + // Update static globe background + globeGroup.select("path").attr("d", path); + redraw(); }); diff --git a/tinytorch/site/extra/community/modules/auth.js b/tinytorch/site/extra/community/modules/auth.js index c72d0cc75..0e3ca7862 100644 --- a/tinytorch/site/extra/community/modules/auth.js +++ b/tinytorch/site/extra/community/modules/auth.js @@ -1,11 +1,8 @@ import { NETLIFY_URL, SUPABASE_URL, SUPABASE_PROJECT_URL, SUPABASE_ANON_KEY, getBasePath } from './config.js'; -import { createClient } from 'https://cdn.jsdelivr.net/npm/@supabase/supabase-js/+esm'; -import { updateNavState } from './ui.js?v=2'; +import { updateNavState } from './ui.js'; import { closeProfileModal, openProfileModal } from './profile.js'; -import { getSession, forceLogin, clearSession } from './state.js?v=2'; +import { getSession, forceLogin, clearSession, supabase } from './state.js'; -// Initialize Supabase Client -const supabase = createClient(SUPABASE_PROJECT_URL, SUPABASE_ANON_KEY); export { supabase }; export async function signInWithSocial(provider) { @@ -267,20 +264,51 @@ export async function handleAuth(e) { authSubmit.innerHTML = '
'; try { - let endpoint, body; - - if (currentMode === 'forgot') { - endpoint = '/api/auth/reset-password'; - body = { email }; - } else { - endpoint = currentMode === 'login' ? '/api/auth/login' : '/api/auth/signup'; - body = { + // Direct Supabase flow for Signup + if (currentMode === 'signup') { + const redirectUrl = window.location.origin + basePath + '/index.html?action=login&confirmed_email=true'; + console.log("Requesting signup with redirect to:", redirectUrl); + + const { error } = await supabase.auth.signUp({ email, password, - redirect_to: window.location.origin + basePath + '/index.html?action=login&confirmed_email=true' - }; + options: { + emailRedirectTo: redirectUrl + } + }); + if (error) throw error; + + closeModal(); + showMessageModal( + 'Check your Email', + 'If you don\'t already have an account, we have sent you an email. Please check your inbox to confirm your signup.' + ); + return; } + // Direct Supabase flow for Password Reset + if (currentMode === 'forgot') { + const redirectUrl = window.location.origin + basePath + '/index.html?action=reset-password'; + console.log("Requesting password reset with redirect to:", redirectUrl); + + const { error } = await supabase.auth.resetPasswordForEmail(email, { + redirectTo: redirectUrl + }); + if (error) throw error; + + showMessageModal('Reset Link Sent', 'If an account exists, a reset link has been sent.'); + setMode('login'); + return; + } + + // Keep existing API flow for Login + const endpoint = '/api/auth/login'; + const body = { + email, + password, + redirect_to: window.location.origin + basePath + '/index.html?action=login&confirmed_email=true' + }; + const url = `${NETLIFY_URL}${endpoint}`; const response = await fetch(url, { @@ -294,72 +322,60 @@ export async function handleAuth(e) { const data = await response.json(); - if (currentMode === 'forgot') { - if (response.ok) { - showMessageModal('Reset Link Sent', data.message || 'If an account exists, a reset link has been sent.'); - setMode('login'); - } else { - throw new Error(data.error || 'Failed to send reset link'); + if (!response.ok) { + throw new Error(data.error || 'Login failed'); + } + + if (data.access_token) { + localStorage.setItem("tinytorch_token", data.access_token); + if (data.refresh_token) localStorage.setItem("tinytorch_refresh_token", data.refresh_token); + localStorage.setItem("tinytorch_user", JSON.stringify(data.user)); + + // Sync Supabase Client so it doesn't trigger SIGNED_OUT + if (data.refresh_token) { + const { error: sessionError } = await supabase.auth.setSession({ + access_token: data.access_token, + refresh_token: data.refresh_token + }); + if (sessionError) { + console.error("Supabase setSession error during login:", sessionError); } - } else { - if (!response.ok) { - throw new Error(data.error || (currentMode === 'login' ? 'Login failed' : 'Signup failed')); } - if (currentMode === 'login') { - if (data.access_token) { - localStorage.setItem("tinytorch_token", data.access_token); - if (data.refresh_token) localStorage.setItem("tinytorch_refresh_token", data.refresh_token); - localStorage.setItem("tinytorch_user", JSON.stringify(data.user)); + updateNavState(); - // Sync Supabase Client so it doesn't trigger SIGNED_OUT - if (data.refresh_token) { - const { error: sessionError } = await supabase.auth.setSession({ - access_token: data.access_token, - refresh_token: data.refresh_token - }); - if (sessionError) { - console.error("Supabase setSession error during login:", sessionError); - } - } + // Check Profile Completeness immediately + const { data: profile } = await supabase + .from('profiles') + .select('display_name, institution, location') + .eq('id', data.user.id) + .single(); - updateNavState(); + const hasName = profile && profile.display_name; + const hasInst = profile && profile.institution && (Array.isArray(profile.institution) ? profile.institution.length > 0 : !!profile.institution); + const hasLoc = profile && profile.location; - // Check Profile Completeness immediately - const { data: profile } = await supabase - .from('profiles') - .select('display_name, institution, location') - .eq('id', data.user.id) - .single(); + const params = new URLSearchParams(window.location.search); + const nextParam = params.get('next'); + + closeModal(); // Always close modal on success - const hasName = profile && profile.display_name; - const hasInst = profile && profile.institution && (Array.isArray(profile.institution) ? profile.institution.length > 0 : !!profile.institution); - const hasLoc = profile && profile.location; + if (nextParam) { + // Ensure nextParam is a clean path if it was encoded + const cleanNext = decodeURIComponent(nextParam).split('?')[0]; + window.location.href = cleanNext; + return; + } - if (!hasName || !hasInst || !hasLoc) { - window.location.href = basePath + '/profile_setup.html'; - return; - } + if (!hasName || !hasInst || !hasLoc) { + window.location.href = basePath + '/profile_setup.html'; + return; + } - const params = new URLSearchParams(window.location.search); - if (params.get('action') === 'profile') { - closeModal(); - openProfileModal(); - } else { - window.location.href = basePath + '/dashboard.html'; - } - } + if (params.get('action') === 'profile') { + openProfileModal(); } else { - // Signup Success - Show Message Modal - // We close the auth modal first so it doesn't overlap - closeModal(); - showMessageModal( - 'Check your Email', - 'If you don\'t already have an account, we have sent you an email. Please check your inbox to confirm your signup.', - () => { - window.location.href = basePath + '/dashboard.html'; - } - ); + window.location.href = basePath + '/dashboard.html'; } } diff --git a/tinytorch/site/extra/community/modules/candle.js b/tinytorch/site/extra/community/modules/candle.js index 1a279aa79..2e787f11f 100644 --- a/tinytorch/site/extra/community/modules/candle.js +++ b/tinytorch/site/extra/community/modules/candle.js @@ -1,8 +1,17 @@ // --- CANDLE ANIMATION MODULE --- +const activeLoops = new Set(); + export function initCandle(canvasId) { const canvas = document.getElementById(canvasId); if (!canvas) return; + // Prevent multiple loops on the same canvas + if (activeLoops.has(canvasId)) { + console.log(`Candle loop already running for ${canvasId}, skipping init.`); + return; + } + activeLoops.add(canvasId); + const ctx = canvas.getContext("2d"); // Coordinate system setup: diff --git a/tinytorch/site/extra/community/modules/guard.js b/tinytorch/site/extra/community/modules/guard.js index 9f025f6fb..34091925f 100644 --- a/tinytorch/site/extra/community/modules/guard.js +++ b/tinytorch/site/extra/community/modules/guard.js @@ -1,81 +1,92 @@ -import { createClient } from 'https://cdn.jsdelivr.net/npm/@supabase/supabase-js/+esm'; import { SUPABASE_PROJECT_URL, SUPABASE_ANON_KEY, SUPABASE_URL, getBasePath } from './config.js'; +import { clearSession, supabase } from './state.js'; -const supabase = createClient(SUPABASE_PROJECT_URL, SUPABASE_ANON_KEY); const LOGIN_PAGE = getBasePath() + '/index.html'; const SETUP_PAGE = getBasePath() + '/profile_setup.html'; +const DASHBOARD_PAGE = getBasePath() + '/dashboard.html'; (async function guard() { - // 0. HYDRATE SESSION (Fix for Direct Email Login) + const path = window.location.pathname; + // Enhanced path checking for landing page + const isOnIndex = path.endsWith('/') || path.endsWith('index.html') || path === getBasePath() || path === getBasePath() + '/'; + const isOnSetupPage = path.includes('profile_setup.html'); + const isPublicPage = isOnIndex || path.includes('login') || path.includes('about') || path.includes('contact'); + const isProtected = !isPublicPage; + + // 0. Get Session const storedToken = localStorage.getItem("tinytorch_token"); const storedRefresh = localStorage.getItem("tinytorch_refresh_token"); + if (storedToken && storedRefresh) { - await supabase.auth.setSession({ - access_token: storedToken, - refresh_token: storedRefresh - }); + try { + await supabase.auth.setSession({ + access_token: storedToken, + refresh_token: storedRefresh + }); + } catch (e) { + console.warn("Guard: setSession failed", e); + } } - // 1. Check Session (Supabase Client) const { data: { session } } = await supabase.auth.getSession(); - let profile = null; - - if (session) { - // 2a. Fetch Profile via Client - const { data } = await supabase - .from('profiles') - .select('display_name, institution, location') - .eq('id', session.user.id) - .single(); - profile = data; - } else { - // 1b. Fallback: Check Token Manually - if (!storedToken) { - // No session, no token -> Redirect - if (!window.location.pathname.includes('index') && !window.location.pathname.includes('login') && !window.location.pathname.includes('about')) { - window.location.href = LOGIN_PAGE + '?action=login&next=' + encodeURIComponent(window.location.pathname); - } - return; - } - - // Have token, verify via API - try { - const res = await fetch(`${SUPABASE_URL}/get-profile-details`, { - headers: { 'Authorization': `Bearer ${storedToken}` } - }); - - if (!res.ok) { - throw new Error("Token invalid"); - } - const data = await res.json(); - profile = data.profile; // API returns { profile: {...}, completed_modules: [...] } - - } catch (e) { - console.warn("Guard: Token validation failed", e); - if (!window.location.pathname.includes('index') && !window.location.pathname.includes('login')) { - window.location.href = LOGIN_PAGE + '?action=login&next=' + encodeURIComponent(window.location.pathname); - } - return; - } + if (!session && isProtected) { + console.log("🚧 No session on protected page. Redirecting to login..."); + window.location.href = LOGIN_PAGE + '?action=login&next=' + encodeURIComponent(path); + return; } - // 3. The Rules - // Must have ALL three: Name, Institution, Location + if (!session) return; // Public page, no session, we are fine. + + // 1. Fetch Profile with Timeout + let profile = null; + const controller = new AbortController(); + const timeoutId = setTimeout(() => controller.abort(), 5000); + + try { + // Use the API for profile details + const res = await fetch(`${SUPABASE_URL}/get-profile-details`, { + headers: { 'Authorization': `Bearer ${session.access_token}` }, + signal: controller.signal + }); + clearTimeout(timeoutId); + + if (res.ok) { + const data = await res.json(); + profile = data.profile; + } else if (res.status === 401 || res.status === 404) { + // 401: Token expired, 404: User deleted from DB + console.warn(`Guard: Session invalid or account deleted (${res.status}). Purging...`); + await clearSession(); + + if (isProtected) { + window.location.href = LOGIN_PAGE + '?action=login'; + } else { + // If on a public page, just reload to clear UI state + window.location.reload(); + } + return; + } + } catch (e) { + console.warn("Guard: Profile fetch failed or timed out", e); + } + + // 2. The Rules const hasName = profile && profile.display_name; const hasInst = profile && profile.institution && (Array.isArray(profile.institution) ? profile.institution.length > 0 : !!profile.institution); const hasLoc = profile && profile.location; const isComplete = hasName && hasInst && hasLoc; - const isOnSetupPage = window.location.pathname.includes('profile_setup'); - if (!isComplete && !isOnSetupPage) { - console.log("🚧 Profile incomplete. Redirecting to setup..."); - window.location.href = SETUP_PAGE; - } - else if (isComplete && isOnSetupPage) { - // If they are done but try to visit setup, send them to dashboard - window.location.href = getBasePath() + '/dashboard.html'; + if (isComplete) { + if (isOnSetupPage) { + console.log("✅ Profile complete. Moving to dashboard..."); + window.location.href = DASHBOARD_PAGE; + } + } else { + if (!isOnSetupPage && isProtected) { + console.log("🚧 Profile incomplete. Redirecting to setup..."); + window.location.href = SETUP_PAGE; + } } - })(); diff --git a/tinytorch/site/extra/community/modules/profile.js b/tinytorch/site/extra/community/modules/profile.js index ae7e0c611..f0ae9c1cc 100644 --- a/tinytorch/site/extra/community/modules/profile.js +++ b/tinytorch/site/extra/community/modules/profile.js @@ -1,5 +1,5 @@ import { SUPABASE_URL, NETLIFY_URL, getBasePath } from './config.js'; -import { forceLogin, getSession } from './state.js?v=2'; +import { forceLogin, getSession, clearSession } from './state.js'; import { initCandle } from './candle.js'; export async function geocodeAndSetCoordinates(location) { @@ -124,9 +124,7 @@ export function setupProfileDeleteEvents() { } alert("Your account has been deleted."); - localStorage.removeItem("tinytorch_token"); - localStorage.removeItem("tinytorch_refresh_token"); - localStorage.removeItem("tinytorch_user"); + await clearSession(); window.location.href = getBasePath() + '/index.html'; } catch (error) { console.error("Delete account error:", error); diff --git a/tinytorch/site/extra/community/modules/state.js b/tinytorch/site/extra/community/modules/state.js index 662b663ac..6ea585a30 100644 --- a/tinytorch/site/extra/community/modules/state.js +++ b/tinytorch/site/extra/community/modules/state.js @@ -1,4 +1,16 @@ import { getBasePath } from './config.js'; +import { createClient } from 'https://cdn.jsdelivr.net/npm/@supabase/supabase-js/+esm'; +import { SUPABASE_PROJECT_URL, SUPABASE_ANON_KEY } from './config.js'; + +const supabase = createClient(SUPABASE_PROJECT_URL, SUPABASE_ANON_KEY, { + auth: { + flowType: 'pkce', // Prefer PKCE for security, or keep 'implicit' if standard for this app + autoRefreshToken: true, + persistSession: true, + detectSessionInUrl: true + } +}); +export { supabase }; // State Management export function getSession() { @@ -12,10 +24,46 @@ export function getSession() { return { token, email, isLoggedIn: !!token }; } -export function clearSession() { +export async function clearSession() { + console.log("🧹 Aggressively clearing session and cookies..."); + try { + await supabase.auth.signOut(); + } catch (e) { + console.warn("Supabase signOut error during clearSession:", e); + } + + // 1. Explicitly remove our own keys localStorage.removeItem("tinytorch_token"); localStorage.removeItem("tinytorch_refresh_token"); localStorage.removeItem("tinytorch_user"); + sessionStorage.removeItem("tinytorch_location_checked"); + + // 2. Clear all Supabase and auth-related keys from localStorage + const keysToRemove = []; + for (let i = 0; i < localStorage.length; i++) { + const key = localStorage.key(i); + if (key && ( + key.includes("supabase") || + key.includes("auth-token") || + key.startsWith("sb-") + )) { + keysToRemove.push(key); + } + } + keysToRemove.forEach(k => localStorage.removeItem(k)); + + // 3. Clear all auth-related cookies + const cookies = document.cookie.split(";"); + for (let i = 0; i < cookies.length; i++) { + const cookie = cookies[i]; + const eqPos = cookie.indexOf("="); + const name = eqPos > -1 ? cookie.substr(0, eqPos).trim() : cookie.trim(); + // Clear for all common paths and subdomains + document.cookie = name + "=;expires=Thu, 01 Jan 1970 00:00:00 GMT;path=/;domain=" + window.location.hostname; + document.cookie = name + "=;expires=Thu, 01 Jan 1970 00:00:00 GMT;path=/"; + } + + console.log("✨ Session cleared."); } export function forceLogin() { diff --git a/tinytorch/site/extra/community/modules/ui.js b/tinytorch/site/extra/community/modules/ui.js index 7d5e6fe1f..7f2c4fd2e 100644 --- a/tinytorch/site/extra/community/modules/ui.js +++ b/tinytorch/site/extra/community/modules/ui.js @@ -1,5 +1,5 @@ import { getBasePath, NETLIFY_URL } from './config.js'; -import { getSession } from './state.js?v=2'; +import { getSession } from './state.js'; export function updateNavState() { const { isLoggedIn, email: userEmail } = getSession(); diff --git a/tinytorch/site/extra/community/package-lock.json b/tinytorch/site/extra/community/package-lock.json new file mode 100644 index 000000000..aadc8413b --- /dev/null +++ b/tinytorch/site/extra/community/package-lock.json @@ -0,0 +1,78 @@ +{ + "name": "tinytorch-community-tests", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "tinytorch-community-tests", + "version": "1.0.0", + "devDependencies": { + "@playwright/test": "^1.42.0" + } + }, + "node_modules/@playwright/test": { + "version": "1.58.2", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.58.2.tgz", + "integrity": "sha512-akea+6bHYBBfA9uQqSYmlJXn61cTa+jbO87xVLCWbTqbWadRVmhxlXATaOjOgcBaWU4ePo0wB41KMFv3o35IXA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright": "1.58.2" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/playwright": { + "version": "1.58.2", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.58.2.tgz", + "integrity": "sha512-vA30H8Nvkq/cPBnNw4Q8TWz1EJyqgpuinBcHET0YVJVFldr8JDNiU9LaWAE1KqSkRYazuaBhTpB5ZzShOezQ6A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.58.2" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.58.2", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.58.2.tgz", + "integrity": "sha512-yZkEtftgwS8CsfYo7nm0KE8jsvm6i/PTgVtB8DL726wNf6H2IMsDuxCpJj59KDaxCtSnrWan2AeDqM7JBaultg==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + } + } +} diff --git a/tinytorch/site/extra/community/package.json b/tinytorch/site/extra/community/package.json new file mode 100644 index 000000000..07c8343c1 --- /dev/null +++ b/tinytorch/site/extra/community/package.json @@ -0,0 +1,12 @@ +{ + "name": "tinytorch-community-tests", + "version": "1.0.0", + "description": "E2E tests for Tiny Torch Community", + "scripts": { + "test": "npx playwright test", + "test:ui": "npx playwright test --ui" + }, + "devDependencies": { + "@playwright/test": "^1.42.0" + } +} diff --git a/tinytorch/site/extra/community/playwright.config.js b/tinytorch/site/extra/community/playwright.config.js new file mode 100644 index 000000000..665e043c8 --- /dev/null +++ b/tinytorch/site/extra/community/playwright.config.js @@ -0,0 +1,21 @@ +const { defineConfig, devices } = require('@playwright/test'); + +module.exports = defineConfig({ + testDir: './tests/e2e', + fullyParallel: true, + forbidOnly: !!process.env.CI, + retries: process.env.CI ? 2 : 0, + workers: process.env.CI ? 1 : undefined, + reporter: 'html', + use: { + baseURL: 'http://localhost:8000/community/', + trace: 'on-first-retry', + screenshot: 'only-on-failure', + }, + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, + }, + ], +}); diff --git a/tinytorch/site/extra/community/profile_setup.html b/tinytorch/site/extra/community/profile_setup.html index 1bc3bfadd..c3637b885 100644 --- a/tinytorch/site/extra/community/profile_setup.html +++ b/tinytorch/site/extra/community/profile_setup.html @@ -307,9 +307,10 @@