# 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("&", "&amp;")
    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
