# Best Practices Guidelines for building high-quality Mosis apps that users love. ## Performance ### Minimize DOM Queries Cache element references instead of querying repeatedly: ```lua -- Bad: Queries on every frame function updateScore() document:GetElementById("score").inner_rml = tostring(score) end -- Good: Cache the reference local scoreElement function onLoad() scoreElement = document:GetElementById("score") end function updateScore() scoreElement.inner_rml = tostring(score) end ``` ### Batch DOM Updates Group multiple changes together: ```lua -- Bad: Multiple separate updates elem1.style.color = "red" elem2.style.color = "red" elem3.style.color = "red" -- Good: Use a class parent:SetClass("error-state", true) ``` ### Use Efficient Data Structures ```lua -- For frequent lookups, use tables as maps local itemLookup = {} for i, item in ipairs(items) do itemLookup[item.id] = item end -- O(1) lookup instead of O(n) search local item = itemLookup["item-123"] ``` ### Clean Up Timers Always clear intervals when navigating away: ```lua local updateInterval function onScreenLoad() updateInterval = setInterval(function() updateData() end, 1000) end function onScreenUnload() if updateInterval then clearInterval(updateInterval) updateInterval = nil end end ``` ### Lazy Load Content Don't load everything at startup: ```lua -- Load data when user scrolls to section function onSectionVisible(sectionId) if not loadedSections[sectionId] then loadSectionData(sectionId) loadedSections[sectionId] = true end end ``` ## User Experience ### Provide Feedback Show users that actions are happening: ```lua function onSubmit() -- Show loading state immediately submitButton:SetClass("loading", true) submitButton:SetAttribute("disabled", "disabled") http.post(url, data, function(response) submitButton:SetClass("loading", false) submitButton:RemoveAttribute("disabled") if response.ok then showSuccess("Saved!") else showError("Failed to save") end end) end ``` ### Handle Errors Gracefully Never show raw error messages to users: ```lua http.get(url, function(response) if response.ok then displayData(json.decode(response.body)) else -- User-friendly message showMessage("Unable to load data. Please check your connection.") -- Log details for debugging console.error("API error:", response.status, response.body) end end) ``` ### Make Touch Targets Large Enough Minimum 48dp for touchable elements: ```css .button { min-width: 48dp; min-height: 48dp; padding: 12dp 24dp; } .list-item { min-height: 56dp; padding: 16dp; } ``` ### Support Undo for Destructive Actions ```lua local deletedItem = nil local undoTimeout = nil function deleteItem(itemId) deletedItem = items[itemId] items[itemId] = nil updateList() showUndoSnackbar("Item deleted", function() -- Undo callback items[itemId] = deletedItem deletedItem = nil updateList() end) -- Clear undo after 5 seconds undoTimeout = setTimeout(function() deletedItem = nil permanentlyDelete(itemId) end, 5000) end ``` ### Remember User State Restore position and selections when returning: ```lua function onScreenUnload() storage.set("list_scroll_position", scrollContainer.scroll_top) storage.set("selected_tab", currentTab) end function onScreenLoad() local scrollPos = storage.get("list_scroll_position") if scrollPos then scrollContainer.scroll_top = scrollPos end local tab = storage.get("selected_tab") if tab then selectTab(tab) end end ``` ## Code Quality ### Use Local Variables Local variables are faster and prevent global pollution: ```lua -- Bad: Global count = 0 -- Good: Local local count = 0 -- Good: Module-level local local Utils = {} local cache = {} -- Private to module ``` ### Handle Edge Cases ```lua function divide(a, b) if b == 0 then console.warn("Division by zero") return 0 end return a / b end function getUsername(user) if not user then return "Unknown" end return user.name or user.email or "Unknown" end ``` ### Use Meaningful Names ```lua -- Bad local t = {} local n = 0 -- Good local userScores = {} local attemptCount = 0 -- Bad function p(x) return x * 100 end -- Good function toPercentage(decimal) return decimal * 100 end ``` ### Keep Functions Small Each function should do one thing: ```lua -- Bad: Does too much function processUser(userId) local user = fetchUser(userId) validateUser(user) updateUserStats(user) sendWelcomeEmail(user) logActivity(user) return formatUserResponse(user) end -- Good: Composed of small functions function processNewUser(userId) local user = fetchUser(userId) if not isValidUser(user) then return nil, "Invalid user" end initializeUserStats(user) queueWelcomeEmail(user) return user end ``` ### Comment Why, Not What ```lua -- Bad: Describes what (obvious from code) -- Increment counter by 1 counter = counter + 1 -- Good: Explains why -- Reset retry count after successful connection -- to prevent unnecessary backoff on next attempt retryCount = 0 ``` ## Security ### Validate All Input ```lua function searchItems(query) -- Sanitize input if type(query) ~= "string" then return {} end query = query:sub(1, 100) -- Limit length query = query:gsub("[^%w%s]", "") -- Remove special chars return performSearch(query) end ``` ### Don't Trust External Data ```lua http.get(url, function(response) local success, data = pcall(function() return json.decode(response.body) end) if not success then console.error("Invalid JSON from API") return end -- Validate structure if type(data.items) ~= "table" then console.error("Missing items array") return end processItems(data.items) end) ``` ### Never Store Secrets in Code ```lua -- Bad: Hardcoded API key local API_KEY = "sk-12345abcde" -- Good: Use environment/config local apiKey = config.get("api_key") ``` ### Sanitize Display Content When displaying user-generated content, prevent injection: ```lua function displayComment(text) -- Escape HTML entities text = text:gsub("&", "&") text = text:gsub("<", "<") text = text:gsub(">", ">") commentElement.inner_rml = text end ``` ## Accessibility ### Use Semantic Elements ```xml