Files
MosisService/portal/internal/web/docs/guides/best-practices.md

536 lines
9.5 KiB
Markdown

# 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("<", "&lt;")
text = text:gsub(">", "&gt;")
commentElement.inner_rml = text
end
```
## Accessibility
### Use Semantic Elements
```xml
<!-- Bad: Divs for everything -->
<div class="button" onclick="submit()">Submit</div>
<!-- Good: Proper elements -->
<button onclick="submit()">Submit</button>
<!-- Good: Headings create hierarchy -->
<h1>Settings</h1>
<h2>Account</h2>
<h2>Notifications</h2>
```
### Provide Text Alternatives
```xml
<!-- Images should describe their purpose -->
<img src="icons/search.tga" alt="Search"/>
<!-- Icons with meaning need labels -->
<button aria-label="Close">
<img src="icons/close.tga"/>
</button>
```
### Ensure Color Contrast
Text should have at least 4.5:1 contrast ratio:
```css
/* Good contrast */
.light-text {
color: #ffffff;
background-color: #1a1a2e; /* Contrast: 12.6:1 */
}
/* Bad contrast */
.low-contrast {
color: #888888;
background-color: #666666; /* Contrast: 1.3:1 */
}
```
### Don't Rely on Color Alone
```css
/* Bad: Only color indicates error */
.error {
color: red;
}
/* Good: Icon + color + text */
.error {
color: #ff4444;
}
.error::before {
content: "⚠ ";
}
```
## Testing
### Test Error States
Don't just test the happy path:
```lua
-- Test these scenarios:
-- 1. Empty data
-- 2. Network failure
-- 3. Invalid input
-- 4. Timeouts
-- 5. Missing permissions
```
### Test Navigation Flows
Ensure users can:
- Navigate forward and back
- Return to the home screen
- Handle the back button at any screen
### Test Edge Cases
- Very long text/names
- Empty lists
- Maximum values
- Rapid repeated actions
- Interrupted operations
### Use Debug Logging
```lua
local DEBUG = true
function debugLog(...)
if DEBUG then
print("[DEBUG]", ...)
end
end
-- In production build, set DEBUG = false
```
## Deployment
### Use Meaningful Version Numbers
Follow semantic versioning:
- **MAJOR**: Breaking changes
- **MINOR**: New features, backward compatible
- **PATCH**: Bug fixes
```json
{
"version": "2.1.3",
"version_code": 15
}
```
### Write Good Release Notes
```
Version 2.1.0
New Features:
- Added dark mode support
- New export to PDF feature
Improvements:
- Faster loading times
- Better error messages
Bug Fixes:
- Fixed crash when opening empty files
- Fixed date format on some devices
```
### Test Before Submitting
1. Run on the Designer
2. Test all features manually
3. Check on a real device if possible
4. Verify all assets load correctly
5. Test offline behavior
## Summary Checklist
Before submitting your app:
- [ ] All features work as expected
- [ ] Error states are handled gracefully
- [ ] Loading states shown during async operations
- [ ] Touch targets are at least 48dp
- [ ] Text is readable (contrast ratio ≥ 4.5:1)
- [ ] No console errors in normal usage
- [ ] Timers and intervals cleaned up properly
- [ ] User data persists correctly
- [ ] App works after fresh install
- [ ] Version number and code are updated
- [ ] Release notes are meaningful
## See Also
- [UI Design Guide](ui-design.md) - Design patterns
- [Lua Scripting Guide](lua-scripting.md) - Code patterns
- [Troubleshooting](../troubleshooting.md) - Common issues