[{"data":1,"prerenderedAt":534},["ShallowReactive",2],{"project-sped-goals-ios-app":3},{"_id":4,"date":5,"description":6,"descriptionText":523,"image":524,"images":525,"preview":15,"previewText":15,"slug":526,"tags":527,"title":532,"url":533},"project-bloom-finance","2025",[7,18,26,36,44,52,60,69,77,85,93,101,109,117,142,150,158,166,174,182,190,198,206,214,223,235,243,259,267,275,287,295,306,313,321,329,337,348,356,367,374,382,390,398,409,417,428,435,443,451,462,470,481,488,496,504,512],{"_key":8,"_type":9,"children":10,"markDefs":16,"style":17},"dcd4a6c9a1e9","block",[11],{"_key":12,"_type":13,"marks":14,"text":15},"323452590ed0","span",[],"This product was built to help special-education teachers spend less time on admin work and more time on student intervention.",[],"normal",{"_key":19,"_type":9,"children":20,"markDefs":25,"style":17},"3bcc10cdd3d0",[21],{"_key":22,"_type":13,"marks":23,"text":24},"491667fa5c3d",[],"From a business lens, the project drives value in four ways:",[],{"_key":27,"_type":9,"children":28,"level":33,"listItem":34,"markDefs":35,"style":17},"bc2dcaff8839",[29],{"_key":30,"_type":13,"marks":31,"text":32},"d7a653a3c4ba",[],"Improves teacher retention and daily engagement by reducing friction in logging student progress.",1,"bullet",[],{"_key":37,"_type":9,"children":38,"level":33,"listItem":34,"markDefs":43,"style":17},"2e8eda78bf0c",[39],{"_key":40,"_type":13,"marks":41,"text":42},"f22ed643664e",[],"Increases instructional quality by surfacing at-risk students earlier (risk scoring, reminder workflows, due-date warnings, and summary views).",[],{"_key":45,"_type":9,"children":46,"level":33,"listItem":34,"markDefs":51,"style":17},"4073948384f6",[47],{"_key":48,"_type":13,"marks":49,"text":50},"089bf9435e5d",[],"Expands monetization opportunities with a production-ready subscription and commerce stack (RevenueCat + Shopify + order history).",[],{"_key":53,"_type":9,"children":54,"level":33,"listItem":34,"markDefs":59,"style":17},"859abb694645",[55],{"_key":56,"_type":13,"marks":57,"text":58},"c972d517d0a5",[],"Reduces rollout risk through feature flags, staged environments, and strong test coverage around high-risk flows (notifications, store, auth).",[],{"_key":61,"_type":9,"children":62,"markDefs":67,"style":68},"076222ec6d36",[63],{"_key":64,"_type":13,"marks":65,"text":66},"fc36fd7330ee",[],"Product + Technical Stack",[],"h3",{"_key":70,"_type":9,"children":71,"level":33,"listItem":34,"markDefs":76,"style":17},"d58ca2171f48",[72],{"_key":73,"_type":13,"marks":74,"text":75},"ddd0fd45fe98",[],"Platform: iOS 16+ app in Swift.",[],{"_key":78,"_type":9,"children":79,"level":33,"listItem":34,"markDefs":84,"style":17},"c4c731e01c82",[80],{"_key":81,"_type":13,"marks":82,"text":83},"0fc85b1c5fa1",[],"UI layer: UIKit-first architecture (ViewControllers, Storyboards, programmatic navigation), plus SwiftUI for WidgetKit extension.",[],{"_key":86,"_type":9,"children":87,"level":33,"listItem":34,"markDefs":92,"style":17},"8bd4b4b31a67",[88],{"_key":89,"_type":13,"marks":90,"text":91},"7bdea2f53f4f",[],"Data/backend: Firebase Auth, Firestore, Remote Config, Analytics, Cloud Messaging.",[],{"_key":94,"_type":9,"children":95,"level":33,"listItem":34,"markDefs":100,"style":17},"3072d05d5d56",[96],{"_key":97,"_type":13,"marks":98,"text":99},"67777a929593",[],"Server logic: Firebase Cloud Functions (Node.js 20) for webhook/event processing (subscription and commerce sync paths).",[],{"_key":102,"_type":9,"children":103,"level":33,"listItem":34,"markDefs":108,"style":17},"d86de5728c82",[104],{"_key":105,"_type":13,"marks":106,"text":107},"1a733aafa77a",[],"Commerce: Shopify Storefront GraphQL client for native catalog/cart flows, with WebView fallback for resilience.",[],{"_key":110,"_type":9,"children":111,"level":33,"listItem":34,"markDefs":116,"style":17},"8c51759dd123",[112],{"_key":113,"_type":13,"marks":114,"text":115},"4c6ef5255a14",[],"Subscriptions: RevenueCat integration for entitlement management.",[],{"_key":118,"_type":9,"children":119,"level":33,"listItem":34,"markDefs":141,"style":17},"3e2577ad44ef",[120,124,129,133,137],{"_key":121,"_type":13,"marks":122,"text":123},"570504cf60d8",[],"Supporting libraries: CocoaPods (",{"_key":125,"_type":13,"marks":126,"text":128},"20270208da3f",[127],"code","SwiftyJSON",{"_key":130,"_type":13,"marks":131,"text":132},"be110d2e52f5",[],", ",{"_key":134,"_type":13,"marks":135,"text":136},"a5c96e480e4c",[127],"Charts",{"_key":138,"_type":13,"marks":139,"text":140},"a7cd88508d13",[],") + SPM dependencies (Firebase SDKs, Google Sign-In, RevenueCat, IQKeyboardManager).",[],{"_key":143,"_type":9,"children":144,"level":33,"listItem":34,"markDefs":149,"style":17},"35adff662e66",[145],{"_key":146,"_type":13,"marks":147,"text":148},"0a767dc20da1",[],"CI/dev tooling: local scripts for notification test suites, typography guard checks, and release version bumping.",[],{"_key":151,"_type":9,"children":152,"markDefs":157,"style":68},"c1d4886f8194",[153],{"_key":154,"_type":13,"marks":155,"text":156},"12a8516a81c2",[],"Technical constraints",[],{"_key":159,"_type":9,"children":160,"markDefs":165,"style":17},"0660383f9b8b",[161],{"_key":162,"_type":13,"marks":163,"text":164},"b367af1a5011",[],"Because this is a native iOS product, rendering is client-side native rendering, not web SSR/CSR.",[],{"_key":167,"_type":9,"children":168,"markDefs":173,"style":17},"07e1fd4fdf3e",[169],{"_key":170,"_type":13,"marks":171,"text":172},"94a11bbac9e4",[],"The rendering approach is intentionally hybrid:",[],{"_key":175,"_type":9,"children":176,"level":33,"listItem":34,"markDefs":181,"style":17},"be81ef8f4c66",[177],{"_key":178,"_type":13,"marks":179,"text":180},"ed84b90492d2",[],"Main app: UIKit renders screens immediately and hydrates with Firestore/network data asynchronously.",[],{"_key":183,"_type":9,"children":184,"level":33,"listItem":34,"markDefs":189,"style":17},"74a9704c7ef2",[185],{"_key":186,"_type":13,"marks":187,"text":188},"f265eb4bddcd",[],"Navigation shell: root scene decides logged-in vs onboarding flow at runtime, then tab composition is rebuilt based on Remote Config feature flags.",[],{"_key":191,"_type":9,"children":192,"level":33,"listItem":34,"markDefs":197,"style":17},"d7bdf7af63ff",[193],{"_key":194,"_type":13,"marks":195,"text":196},"5e74b6500307",[],"Store experience: runtime switch between native Shopify catalog screens and WebView fallback to keep the business flow live even if native store prerequisites are disabled.",[],{"_key":199,"_type":9,"children":200,"level":33,"listItem":34,"markDefs":205,"style":17},"a5643003bbb7",[201],{"_key":202,"_type":13,"marks":203,"text":204},"a1ca018969ec",[],"Widget: SwiftUI WidgetKit timeline rendering reads precomputed student snapshots from shared storage; app-side services periodically refresh and write ranked students for fast widget draw time.",[],{"_key":207,"_type":9,"children":208,"markDefs":213,"style":68},"85197b5dc123",[209],{"_key":210,"_type":13,"marks":211,"text":212},"8614418c3edd",[],"Key Challenges and How They Were Solved",[],{"_key":215,"_type":9,"children":216,"markDefs":222,"style":17},"236f7f7d3294",[217],{"_key":218,"_type":13,"marks":219,"text":221},"e2c82a9dc6f7",[220],"strong","1) Reliable reminders across real-world device behavior",[],{"_key":224,"_type":9,"children":225,"markDefs":234,"style":17},"397e2a58a85c",[226,230],{"_key":227,"_type":13,"marks":228,"text":229},"629e3712025d",[220],"Challenge:",{"_key":231,"_type":13,"marks":232,"text":233},"c2f82e0a2ca9",[],"\nLocal reminders can drift or duplicate due to timezone changes, DST transitions, app lifecycle events, and evolving student targets.",[],{"_key":236,"_type":9,"children":237,"markDefs":242,"style":17},"71f41e930372",[238],{"_key":239,"_type":13,"marks":240,"text":241},"1cfd6e5538dd",[220],"Solution:",[],{"_key":244,"_type":9,"children":245,"level":33,"listItem":34,"markDefs":258,"style":17},"5ed88addb3a7",[246,250,254],{"_key":247,"_type":13,"marks":248,"text":249},"a224f076abef",[],"Built deterministic reminder identifiers (",{"_key":251,"_type":13,"marks":252,"text":253},"1f154e615571",[127],"reminder.\u003Cstudent>.\u003Cprogress>.\u003Ctimestamp>",{"_key":255,"_type":13,"marks":256,"text":257},"3dd7011db92c",[],") and reconciliation logic.",[],{"_key":260,"_type":9,"children":261,"level":33,"listItem":34,"markDefs":266,"style":17},"a62fc8185310",[262],{"_key":263,"_type":13,"marks":264,"text":265},"3c9f521e2f1e",[],"Rescheduled on app active and significant time/timezone changes.",[],{"_key":268,"_type":9,"children":269,"level":33,"listItem":34,"markDefs":274,"style":17},"9148be058bc3",[270],{"_key":271,"_type":13,"marks":272,"text":273},"7e54a9cee878",[],"Applied capped scheduling windows to stay inside iOS pending-notification limits.",[],{"_key":276,"_type":9,"children":277,"markDefs":286,"style":17},"c99631fee107",[278,282],{"_key":279,"_type":13,"marks":280,"text":281},"2fc510204714",[220],"Business outcome:",{"_key":283,"_type":13,"marks":284,"text":285},"dcbae4769805",[],"\nTeachers get predictable reminders, improving consistency of progress logging without operational overhead.",[],{"_key":288,"_type":9,"children":289,"markDefs":294,"style":17},"ae9aa29f9f77",[290],{"_key":291,"_type":13,"marks":292,"text":293},"96351e54ad50",[220],"2) Preventing notification data inconsistency",[],{"_key":296,"_type":9,"children":297,"markDefs":305,"style":17},"addf8254a847",[298,301],{"_key":299,"_type":13,"marks":300,"text":229},"7930cd8ce09e",[220],{"_key":302,"_type":13,"marks":303,"text":304},"7885ee6232f0",[],"\nNotification inbox data can become inconsistent when local notifications, remote delivery, and Firestore mirrors update concurrently.",[],{"_key":307,"_type":9,"children":308,"markDefs":312,"style":17},"42dccd7abdd2",[309],{"_key":310,"_type":13,"marks":311,"text":241},"bf4228caef61",[220],[],{"_key":314,"_type":9,"children":315,"level":33,"listItem":34,"markDefs":320,"style":17},"05997ff8198e",[316],{"_key":317,"_type":13,"marks":318,"text":319},"5be31ff903ef",[],"Added idempotent mirror sync patterns with serialized sync execution.",[],{"_key":322,"_type":9,"children":323,"level":33,"listItem":34,"markDefs":328,"style":17},"a50e4d5d80a6",[324],{"_key":325,"_type":13,"marks":326,"text":327},"a9d223bf7df9",[],"Used transactional read-state updates and chunked bulk mark-as-read writes.",[],{"_key":330,"_type":9,"children":331,"level":33,"listItem":34,"markDefs":336,"style":17},"5ba81310b288",[332],{"_key":333,"_type":13,"marks":334,"text":335},"ef7b260a1704",[],"Backed this with dedicated notification hardening tests and index/rules support.",[],{"_key":338,"_type":9,"children":339,"markDefs":347,"style":17},"eaf87c6376ba",[340,343],{"_key":341,"_type":13,"marks":342,"text":281},"02a0f3a8d66b",[220],{"_key":344,"_type":13,"marks":345,"text":346},"af903cf44e16",[],"\nInbox reliability improves trust and lowers support burden caused by duplicate/stale notifications.",[],{"_key":349,"_type":9,"children":350,"markDefs":355,"style":17},"473c5691c4d4",[351],{"_key":352,"_type":13,"marks":353,"text":354},"c71ce02c02ed",[220],"3) Safe monetization rollout (subscription + commerce)",[],{"_key":357,"_type":9,"children":358,"markDefs":366,"style":17},"4a7586b8c628",[359,362],{"_key":360,"_type":13,"marks":361,"text":229},"5895265d14b6",[220],{"_key":363,"_type":13,"marks":364,"text":365},"d220696d8184",[],"\nIntroducing payments and commerce in an existing education app can break UX and create identity/ownership mismatches.",[],{"_key":368,"_type":9,"children":369,"markDefs":373,"style":17},"e7be28bf30bf",[370],{"_key":371,"_type":13,"marks":372,"text":241},"7e78f221f416",[220],[],{"_key":375,"_type":9,"children":376,"level":33,"listItem":34,"markDefs":381,"style":17},"718be2540cb7",[377],{"_key":378,"_type":13,"marks":379,"text":380},"e42463b2b8d1",[],"Implemented RevenueCat entitlement setup with guarded initialization.",[],{"_key":383,"_type":9,"children":384,"level":33,"listItem":34,"markDefs":389,"style":17},"a9946623a9c7",[385],{"_key":386,"_type":13,"marks":387,"text":388},"7c0ca5e85964",[],"Added Shopify native store with feature-gated rollout and WebView fallback.",[],{"_key":391,"_type":9,"children":392,"level":33,"listItem":34,"markDefs":397,"style":17},"07522c24890f",[393],{"_key":394,"_type":13,"marks":395,"text":396},"fa75b7e994ea",[],"Built guest-to-auth order claim flow so purchases remain attached to the right account after login.",[],{"_key":399,"_type":9,"children":400,"markDefs":408,"style":17},"cd138881e9e7",[401,404],{"_key":402,"_type":13,"marks":403,"text":281},"5e390515d398",[220],{"_key":405,"_type":13,"marks":406,"text":407},"9660ce1d7be4",[],"\nMonetization can scale without sacrificing user trust or causing checkout-related drop-off.",[],{"_key":410,"_type":9,"children":411,"markDefs":416,"style":17},"e8d46030b881",[412],{"_key":413,"_type":13,"marks":414,"text":415},"539ae2f7efa1",[220],"4) Balancing speed with accessibility and maintainability",[],{"_key":418,"_type":9,"children":419,"markDefs":427,"style":17},"aa636538a92d",[420,423],{"_key":421,"_type":13,"marks":422,"text":229},"07e4d9dff1d1",[220],{"_key":424,"_type":13,"marks":425,"text":426},"46f8e8965e68",[],"\nLegacy-style UIKit codebases often drift into inconsistent typography/accessibility behavior over time.",[],{"_key":429,"_type":9,"children":430,"markDefs":434,"style":17},"cf10c38b2a7c",[431],{"_key":432,"_type":13,"marks":433,"text":241},"c714e8ad201a",[220],[],{"_key":436,"_type":9,"children":437,"level":33,"listItem":34,"markDefs":442,"style":17},"9f7d6b963042",[438],{"_key":439,"_type":13,"marks":440,"text":441},"943b45046726",[],"Standardized typography tokens with Dynamic Type support across UIKit + SwiftUI surfaces.",[],{"_key":444,"_type":9,"children":445,"level":33,"listItem":34,"markDefs":450,"style":17},"2f3eb1a2f9aa",[446],{"_key":447,"_type":13,"marks":448,"text":449},"4e5ec6c2683e",[],"Added a guard script to block non-compliant hard-coded font usage in UI code.",[],{"_key":452,"_type":9,"children":453,"markDefs":461,"style":17},"8a8268a8afeb",[454,457],{"_key":455,"_type":13,"marks":456,"text":281},"06a6cef582aa",[220],{"_key":458,"_type":13,"marks":459,"text":460},"ed973030236e",[],"\nLower accessibility risk, more consistent UI quality, and reduced long-term maintenance cost.",[],{"_key":463,"_type":9,"children":464,"markDefs":469,"style":17},"4400dcae1729",[465],{"_key":466,"_type":13,"marks":467,"text":468},"000a78cd89d8",[220],"5) Performance under data-heavy teacher workflows",[],{"_key":471,"_type":9,"children":472,"markDefs":480,"style":17},"0edb9912b78b",[473,476],{"_key":474,"_type":13,"marks":475,"text":229},"77b205312ca4",[220],{"_key":477,"_type":13,"marks":478,"text":479},"5d72ded92d6c",[],"\nTeachers may manage many students and targets; slow list loads hurt daily usage.",[],{"_key":482,"_type":9,"children":483,"markDefs":487,"style":17},"511a1c348d22",[484],{"_key":485,"_type":13,"marks":486,"text":241},"4334a50af9df",[220],[],{"_key":489,"_type":9,"children":490,"level":33,"listItem":34,"markDefs":495,"style":17},"197189461105",[491],{"_key":492,"_type":13,"marks":493,"text":494},"9f2f70cd49fc",[],"Parallelized student/progress fetch flows.",[],{"_key":497,"_type":9,"children":498,"level":33,"listItem":34,"markDefs":503,"style":17},"7a8c71342c5a",[499],{"_key":500,"_type":13,"marks":501,"text":502},"ec5e56470fbc",[],"Added short-lived cache strategy for read-heavy screens.",[],{"_key":505,"_type":9,"children":506,"level":33,"listItem":34,"markDefs":511,"style":17},"9a6eebd9ab19",[507],{"_key":508,"_type":13,"marks":509,"text":510},"2eefb7a59b03",[],"Precomputed ranked widget snapshots during fetch to avoid expensive widget-time computation.",[],{"_key":513,"_type":9,"children":514,"markDefs":522,"style":17},"b14a4fd458f3",[515,518],{"_key":516,"_type":13,"marks":517,"text":281},"f2edb61d3c5c",[220],{"_key":519,"_type":13,"marks":520,"text":521},"535abbe8f612",[],"\nFaster perceived app responsiveness and improved day-to-day usability.",[],"This product was built to help special-education teachers spend less time on admin work and more time on student intervention.\n\nFrom a business lens, the project drives value in four ways:\n\nImproves teacher retention and daily engagement by reducing friction in logging student progress.\n\nIncreases instructional quality by surfacing at-risk students earlier (risk scoring, reminder workflows, due-date warnings, and summary views).\n\nExpands monetization opportunities with a production-ready subscription and commerce stack (RevenueCat + Shopify + order history).\n\nReduces rollout risk through feature flags, staged environments, and strong test coverage around high-risk flows (notifications, store, auth).\n\nProduct + Technical Stack\n\nPlatform: iOS 16+ app in Swift.\n\nUI layer: UIKit-first architecture (ViewControllers, Storyboards, programmatic navigation), plus SwiftUI for WidgetKit extension.\n\nData/backend: Firebase Auth, Firestore, Remote Config, Analytics, Cloud Messaging.\n\nServer logic: Firebase Cloud Functions (Node.js 20) for webhook/event processing (subscription and commerce sync paths).\n\nCommerce: Shopify Storefront GraphQL client for native catalog/cart flows, with WebView fallback for resilience.\n\nSubscriptions: RevenueCat integration for entitlement management.\n\nSupporting libraries: CocoaPods (SwiftyJSON, Charts) + SPM dependencies (Firebase SDKs, Google Sign-In, RevenueCat, IQKeyboardManager).\n\nCI/dev tooling: local scripts for notification test suites, typography guard checks, and release version bumping.\n\nTechnical constraints\n\nBecause this is a native iOS product, rendering is client-side native rendering, not web SSR/CSR.\n\nThe rendering approach is intentionally hybrid:\n\nMain app: UIKit renders screens immediately and hydrates with Firestore/network data asynchronously.\n\nNavigation shell: root scene decides logged-in vs onboarding flow at runtime, then tab composition is rebuilt based on Remote Config feature flags.\n\nStore experience: runtime switch between native Shopify catalog screens and WebView fallback to keep the business flow live even if native store prerequisites are disabled.\n\nWidget: SwiftUI WidgetKit timeline rendering reads precomputed student snapshots from shared storage; app-side services periodically refresh and write ranked students for fast widget draw time.\n\nKey Challenges and How They Were Solved\n\n1) Reliable reminders across real-world device behavior\n\nChallenge:\nLocal reminders can drift or duplicate due to timezone changes, DST transitions, app lifecycle events, and evolving student targets.\n\nSolution:\n\nBuilt deterministic reminder identifiers (reminder.\u003Cstudent>.\u003Cprogress>.\u003Ctimestamp>) and reconciliation logic.\n\nRescheduled on app active and significant time/timezone changes.\n\nApplied capped scheduling windows to stay inside iOS pending-notification limits.\n\nBusiness outcome:\nTeachers get predictable reminders, improving consistency of progress logging without operational overhead.\n\n2) Preventing notification data inconsistency\n\nChallenge:\nNotification inbox data can become inconsistent when local notifications, remote delivery, and Firestore mirrors update concurrently.\n\nSolution:\n\nAdded idempotent mirror sync patterns with serialized sync execution.\n\nUsed transactional read-state updates and chunked bulk mark-as-read writes.\n\nBacked this with dedicated notification hardening tests and index/rules support.\n\nBusiness outcome:\nInbox reliability improves trust and lowers support burden caused by duplicate/stale notifications.\n\n3) Safe monetization rollout (subscription + commerce)\n\nChallenge:\nIntroducing payments and commerce in an existing education app can break UX and create identity/ownership mismatches.\n\nSolution:\n\nImplemented RevenueCat entitlement setup with guarded initialization.\n\nAdded Shopify native store with feature-gated rollout and WebView fallback.\n\nBuilt guest-to-auth order claim flow so purchases remain attached to the right account after login.\n\nBusiness outcome:\nMonetization can scale without sacrificing user trust or causing checkout-related drop-off.\n\n4) Balancing speed with accessibility and maintainability\n\nChallenge:\nLegacy-style UIKit codebases often drift into inconsistent typography/accessibility behavior over time.\n\nSolution:\n\nStandardized typography tokens with Dynamic Type support across UIKit + SwiftUI surfaces.\n\nAdded a guard script to block non-compliant hard-coded font usage in UI code.\n\nBusiness outcome:\nLower accessibility risk, more consistent UI quality, and reduced long-term maintenance cost.\n\n5) Performance under data-heavy teacher workflows\n\nChallenge:\nTeachers may manage many students and targets; slow list loads hurt daily usage.\n\nSolution:\n\nParallelized student/progress fetch flows.\n\nAdded short-lived cache strategy for read-heavy screens.\n\nPrecomputed ranked widget snapshots during fetch to avoid expensive widget-time computation.\n\nBusiness outcome:\nFaster perceived app responsiveness and improved day-to-day usability.","/images/projects/sped/sped-goals.jpg",null,"sped-goals-ios-app",[528,529,530,531],"iOS app","Swift 4","Mobile apps","SAAS","SPED Goals — IEP & Goal Tracking App for Special Education Teachers","https://apps.apple.com/id/app/sped-goals/id6447784065",1775998939297]