# Modyo Platform Best Practices

Essential rules for all Modyo development work. Follow these principles consistently.

## Content Security Policy (CSP)

1. **ALWAYS** include `nonce="{{csp_nonce}}"` on inline `<style>` and `<script>` tags
2. **NEVER** use inline `style=""` or event attributes (`onclick`, `onload`, etc.)
3. **ALWAYS** include `nonce: csp_nonce` parameter in `stylesheet_tag` and `script_tag` filters
4. Use event listeners in `<script>` tags instead of inline event handlers

## CSS and JavaScript Organization

### Hierarchy (Preference Order)

1. **CSS/JS Templates** - For global, reusable code (CDN-cached)
2. **Widget CSS/JS Properties** - For widget-specific code
3. **Snippet `<style>`/`<script>` Tags** - For component-specific code only

### Decision Rules

- If used across multiple pages → CSS/JS Template
- If part of a widget → Widget CSS/JS properties
- If component-specific and not reused → Snippet tags with nonce
- Never use inline `style=""` or event attributes

## URL Management

1. **ALWAYS** use site-relative URLs (`/products`, not `https://site.com/products`)
2. **PREFER** Modyo URL helpers (`{{ site.url }}`, `{{ asset.url }}`)
3. **NEVER** hardcode domain names in templates

## Liquid Templates

### Defensive Programming

1. **ALWAYS** check variable existence before use (`{% if variable %}`)
2. **ALWAYS** filter by visibility (`menu.items | visible_items`)
3. **ALWAYS** assign repeated expressions to variables (cache)
4. Handle `nil` cases gracefully with fallbacks

### Global Drops

Available everywhere: `site`, `request`, `menus`, `spaces`, `current_user`, `csp_nonce`

### Field Names

1. **ALWAYS** use exact field names from type schema (case-sensitive)
2. **VERIFY** field names with `type-get` before using in templates
3. Remember Modyo uses `snake_case` (not `camelCase`)

## Navigation Menus

1. Access via `menus['slug']` (case-sensitive slug, not name)
2. **ALWAYS** check menu exists before iterating
3. **ALWAYS** filter items by visibility (`| visible_items`)
4. Use `| active_page: request.url` for active state
5. Use `| item_rel` filter for external link `rel` attributes

## Content Pages

1. **NEVER** use leading slash in `path` parameter (`"blog"` not `"/blog"`)
2. Set `has_router: true` to enable dynamic slug routing
3. Provide `content_type_id` for content/entry pages
4. Use two templates: `index` (list) and `show` (detail)

## Widget Development

1. **ALWAYS** include all three properties: `html`, `css`, `js` (empty string if not needed)
2. **PREFER** `sync: false` (async loading) except for critical above-the-fold content
3. **ALWAYS** publish widget before adding to pages
4. Use `definition_uuid` (not `id`) when adding widgets to pages

## Publishing Workflow

1. **ALWAYS** use two-step process: `release-get-elements-to-publish` → `release-create`
2. Publish related changes together (template + menu, widget + page, etc.)
3. **NEVER** publish partially if changes depend on each other
4. Widget IDs change after publishing; UUIDs remain stable

## Performance

1. Use `defer: 'defer'` for non-critical JavaScript
2. Cache filtered/computed values in variables
3. Prefer CSS/JS templates (CDN-cached) over inline code
4. Limit menu nesting to 3 levels maximum

## Accessibility

1. Use semantic HTML (`<nav>`, `<main>`, `<article>`)
2. Include ARIA attributes (`role`, `aria-label`, `aria-current`)
3. Indicate active page state (`aria-current="page"`)
4. Mark external links visually and with `aria-label`
5. Provide skip navigation links

## Testing Requirements

Before deploying, verify:

- [ ] All inline styles/scripts have `nonce="{{csp_nonce}}"`
- [ ] No hardcoded domains in URLs
- [ ] All variables checked for existence
- [ ] Visibility filters applied to menus/items
- [ ] Field names match type schema exactly
- [ ] Content page paths have no leading slash
- [ ] External links have proper `target` and `rel`
- [ ] Widget CSS/JS properties included (even if empty)
- [ ] Related changes published together

## Common Violations to Avoid

| Violation | Impact | Fix |
|-----------|--------|-----|
| Missing nonce | CSP blocks code | Add `nonce="{{csp_nonce}}"` |
| Inline style/onclick | CSP blocks code | Use classes + event listeners |
| Hardcoded URLs | Breaks in other environments | Use site-relative paths |
| No existence checks | Liquid errors | Add `{% if variable %}` |
| Wrong field names | Missing content | Verify with `type-get` |
| Leading slash in path | Broken routing | Remove leading `/` |
| Partial publishing | Inconsistent state | Publish related changes together |
| Omitting widget properties | Update fails | Include all: html, css, js |

## Priority Rules

**CRITICAL** (Must never violate):
- CSP nonce on inline code
- No inline style/event attributes
- No hardcoded domains
- Exact field name matching

**HIGH** (Follow consistently):
- Existence checks
- Visibility filtering
- Site-relative URLs
- Two-step publishing

**MEDIUM** (Best practice):
- CSS/JS template preference
- Async widget loading
- Variable caching
- Semantic HTML

## Resources

For implementation details ("how to"), see:
- [Navigation Liquid Guide](./resources/modyo-tools/channels/navigation/navigation-menus-liquid-guide.md)
- [CSS/JS Templates Guide](./CSS_JS_TEMPLATES.md)
- [Channels Module Guide](../src/tools/channels/CLAUDE.md)
- [Content Module Guide](../src/tools/content/CLAUDE.md)
