odac 1.4.3 → 1.4.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/.agent/rules/coding.md +2 -2
  2. package/.agent/rules/memory.md +5 -1
  3. package/.github/workflows/release.yml +2 -0
  4. package/.husky/pre-push +0 -1
  5. package/.kiro/steering/coding.md +27 -0
  6. package/.kiro/steering/memory.md +56 -0
  7. package/.kiro/steering/project.md +30 -0
  8. package/.kiro/steering/workflow.md +16 -0
  9. package/CHANGELOG.md +98 -0
  10. package/README.md +2 -1
  11. package/client/odac.js +121 -2
  12. package/docs/ai/skills/backend/authentication.md +7 -5
  13. package/docs/ai/skills/backend/controllers.md +24 -3
  14. package/docs/ai/skills/backend/forms.md +8 -6
  15. package/docs/ai/skills/backend/image-processing.md +93 -0
  16. package/docs/ai/skills/backend/request_response.md +2 -2
  17. package/docs/ai/skills/backend/routing.md +11 -0
  18. package/docs/ai/skills/backend/structure.md +1 -1
  19. package/docs/ai/skills/backend/views.md +34 -9
  20. package/docs/ai/skills/frontend/navigation.md +45 -1
  21. package/docs/ai/skills/frontend/realtime.md +18 -2
  22. package/docs/backend/05-controllers/02-your-trusty-odac-assistant.md +24 -0
  23. package/docs/backend/07-views/03-template-syntax.md +65 -15
  24. package/docs/backend/07-views/03-variables.md +22 -7
  25. package/docs/backend/07-views/11-image-optimization.md +197 -0
  26. package/docs/frontend/02-ajax-navigation/01-quick-start.md +22 -0
  27. package/docs/frontend/02-ajax-navigation/03-advanced-usage.md +51 -0
  28. package/package.json +5 -2
  29. package/src/Auth.js +8 -4
  30. package/src/Config.js +5 -0
  31. package/src/Database/ConnectionFactory.js +16 -0
  32. package/src/Ipc.js +3 -2
  33. package/src/Lang.js +17 -10
  34. package/src/Odac.js +1 -0
  35. package/src/Request.js +20 -20
  36. package/src/Route.js +39 -3
  37. package/src/Validator.js +5 -5
  38. package/src/View/Image.js +495 -0
  39. package/src/View.js +4 -0
  40. package/template/controller/page/about.js +3 -3
  41. package/template/controller/page/index.js +2 -2
  42. package/template/public/assets/js/app.js +38 -54
  43. package/template/skeleton/main.html +4 -4
  44. package/template/view/content/about.html +64 -60
  45. package/template/view/content/home.html +148 -175
  46. package/template/view/css/app.css +46 -0
  47. package/template/view/footer/main.html +10 -9
  48. package/template/view/header/main.html +34 -11
  49. package/test/Auth/verifyMagicLink.test.js +281 -0
  50. package/test/Client/load.test.js +306 -0
  51. package/test/Lang/get.test.js +37 -11
  52. package/test/Odac/image.test.js +61 -0
  53. package/test/Route/set.test.js +102 -0
  54. package/test/View/Image/buildFilename.test.js +62 -0
  55. package/test/View/Image/hash.test.js +59 -0
  56. package/test/View/Image/isAvailable.test.js +15 -0
  57. package/test/View/Image/parse.test.js +83 -0
  58. package/test/View/Image/process.test.js +38 -0
  59. package/test/View/Image/render.test.js +117 -0
  60. package/test/View/Image/serve.test.js +56 -0
  61. package/test/View/Image/url.test.js +53 -0
  62. package/test/View/constructor.test.js +10 -0
  63. package/template/public/assets/css/style.css +0 -1835
@@ -18,7 +18,7 @@ Handling incoming data and sending structured responses.
18
18
  3. **Returning Data**:
19
19
  - `return { ... }`: Returns JSON.
20
20
  - `return Odac.return({ ... })`: Explicit JSON return.
21
- - `Odac.Response.header('Key', 'Value')`: Set custom headers.
21
+ - `Odac.Request.header('Key', 'Value')`: Set custom headers.
22
22
 
23
23
  ## Reference Patterns
24
24
  ### 1. Unified Request Handling
@@ -36,7 +36,7 @@ module.exports = async function(Odac) {
36
36
  ### 2. Header and Status Management
37
37
  ```javascript
38
38
  module.exports = function(Odac) {
39
- Odac.Response.header('Content-Type', 'text/plain');
39
+ Odac.Request.header('Content-Type', 'text/plain');
40
40
  return "Raw text response";
41
41
  };
42
42
  ```
@@ -17,6 +17,8 @@ Routes are defined in the `route/` directory. ODAC uses a two-phase routing stra
17
17
  - `Odac.Route.page(url, controller)`: For HTML views (GET).
18
18
  - `Odac.Route.get(url, controller)`: Targeted GET requests.
19
19
  - `Odac.Route.post(url, controller)`: Sensitive POST requests (CSRF enabled by default).
20
+ - `Odac.Route.ws(url, controller)`: Bidirectional and persistent WebSocket connections.
21
+ - `Odac.Route.authWs(url, controller)`: Authenticated WebSocket connections.
20
22
  2. **Parameters**: Use `{id}` syntax for dynamic segments. Accessed via `Odac.request('id')`.
21
23
  3. **Middlewares**: Chain logic using `.use('name')`. Global middlewares reside in `middleware/`.
22
24
  4. **Error Handling**: Use `Odac.Route.error(code, controller)` for custom 404/500 pages.
@@ -51,6 +53,15 @@ Odac.Route.error(500, 'errors/ServerError');
51
53
  Odac.Route.post('/api/webhook', 'Api@webhook', { token: false });
52
54
  ```
53
55
 
56
+ ### 5. Realtime & WebSocket Routes
57
+ ```javascript
58
+ // Public WebSocket
59
+ Odac.Route.ws('/chat', 'Chat');
60
+
61
+ // Authenticated WebSocket with path params
62
+ Odac.Route.authWs('/game/{roomId}', 'Game@join');
63
+ ```
64
+
54
65
  ## Best Practices
55
66
  - **Method Specification**: Use `.page()` for views to enable AJAX navigation compatibility.
56
67
  - **Static First**: Prefer exact URL matches over parametric ones where possible (faster).
@@ -50,7 +50,7 @@ module.exports = User;
50
50
  class User {
51
51
  async show(Odac) {
52
52
  // Service is automatically available as Odac.User
53
- const profile = await Odac.User.getProfile(Odac.Request.input('id'));
53
+ const profile = await Odac.User.getProfile(await Odac.request('id'));
54
54
 
55
55
  return Odac.View.make('user.profile', { profile });
56
56
  }
@@ -14,12 +14,18 @@ Views in ODAC are logic-light but powerful. They support automatic XSS protectio
14
14
 
15
15
  ## Core Rules
16
16
  1. **Skeleton Architecture**: Use `Odac.View.skeleton('name')` to wrap content in a layout.
17
- 2. **Data Binding**:
18
- - `{{ key }}`: Escaped output (Standard).
19
- - `{!! key !!}`: Raw output (Use with extreme caution).
20
- 3. **Conditionals**: Use `<odac:if condition="VAR"> ... </odac:if>`.
21
- 4. **Looping**: Use `<odac:for in="ARRAY" value="ITEM"> ... </odac:for>` or the performance-optimized `[[odac_for ...]]`.
22
- 5. **Server-Side JS**: Use `<script:odac>` for complex calculations during rendering.
17
+ 2. **Data Binding — Two Equivalent Syntaxes**:
18
+ - `<odac var="key" />`: Tag-based output (HTML-escaped, XSS-safe).
19
+ - `{{ key }}`: Inline/interpolation output (HTML-escaped, XSS-safe). Identical behavior to `<odac var>`.
20
+ - `<odac var="key" raw />` or `{!! key !!}`: Raw output (Use with extreme caution).
21
+ 3. **Choosing the Right Syntax**:
22
+ - **Inside HTML attributes** (`src`, `alt`, `href`, `class`, `value`, etc.) → Always prefer `{{ }}`. It reads naturally and keeps markup clean.
23
+ - **Inline within text or mixed HTML** → Prefer `{{ }}` for short interpolations.
24
+ - **Standalone block output** (the variable is the only content of an element) → Prefer `<odac var="" />` for structural clarity and IDE support.
25
+ - Both syntaxes compile to the same engine output. The choice is about readability, not functionality.
26
+ 4. **Conditionals**: Use `<odac:if condition="VAR"> ... </odac:if>`.
27
+ 5. **Looping**: Use `<odac:for in="ARRAY" value="ITEM"> ... </odac:for>` or the performance-optimized `[[odac_for ...]]`.
28
+ 6. **Server-Side JS**: Use `<script:odac>` for complex calculations during rendering.
23
29
 
24
30
  ## Reference Patterns
25
31
 
@@ -35,8 +41,17 @@ Odac.View.set({
35
41
 
36
42
  ### 2. Template Syntax Reference
37
43
  ```html
38
- <!-- Display Variable -->
39
- <h1>{{ title }}</h1>
44
+ <!-- Standalone block output — prefer <odac var> -->
45
+ <h1><odac var="title" /></h1>
46
+
47
+ <!-- Inside attributes — prefer {{ }} -->
48
+ <img src="{{ product.image }}" alt="{{ product.name }}">
49
+ <a href="/user/{{ user.id }}" class="btn {{ isActive ? 'active' : '' }}">Profile</a>
50
+ <input type="text" value="{{ query }}">
51
+
52
+ <!-- Inline text interpolation — prefer {{ }} -->
53
+ <p>Welcome, {{ user.name }}. You have {{ notifications }} new messages.</p>
54
+ <span>${{ product.price }}</span>
40
55
 
41
56
  <!-- Conditional -->
42
57
  <odac:if condition="stats.users > 100">
@@ -62,7 +77,17 @@ Perfect for calculations that shouldn't clutter the controller but are too compl
62
77
  <p>Tax: ${{ tax }}</p>
63
78
  ```
64
79
 
80
+ ## Syntax Selection Guide
81
+
82
+ | Context | Preferred Syntax | Example |
83
+ |---------|-----------------|---------|
84
+ | HTML attributes (`src`, `href`, `alt`, `class`, `value`) | `{{ }}` | `<img src="{{ photo.url }}" alt="{{ photo.caption }}">` |
85
+ | Inline text within elements | `{{ }}` | `<p>Hello, {{ user.name }}</p>` |
86
+ | Standalone element content | `<odac var />` | `<h1><odac var="title" /></h1>` |
87
+ | Raw HTML output (trusted only) | `<odac var raw />` or `{!! !!}` | `<div><odac var="content" raw /></div>` |
88
+
65
89
  ## Security Best Practices
66
- - **Always use `{{ }}`**: Standard tags prevent XSS.
90
+ - **Both `{{ }}` and `<odac var>` are XSS-safe**: Both apply HTML escaping by default. Use either with confidence.
91
+ - **Raw output requires trust**: Only use `raw` / `{!! !!}` with content you fully control. Never with user input.
67
92
  - **Limit `<script:odac>`**: Do not perform database queries or API calls inside views; keep them in the controller.
68
93
  - **Partial Awareness**: Use `<odac:include view="path.to.view" />` for reusable components.
@@ -2,7 +2,7 @@
2
2
  name: frontend-navigation-spa-skill
3
3
  description: Single-page navigation patterns in odac.js for smooth transitions, route control, and lifecycle-safe execution.
4
4
  metadata:
5
- tags: frontend, navigation, spa, ajax-navigation, page-lifecycle, transitions
5
+ tags: frontend, navigation, spa, ajax-navigation, page-lifecycle, transitions, view-transitions
6
6
  ---
7
7
 
8
8
  # Frontend Navigation & SPA Skill
@@ -13,6 +13,7 @@ Smooth transitions and single-page application behavior using `odac.js`.
13
13
  1. **Selection**: Enable via `Odac.action({ navigate: 'main' })`.
14
14
  2. **Exclusion**: Use `data-navigate="false"` or `.no-navigate` class for full reloads.
15
15
  3. **Lifecycle**: Use `load` and `page` events to run code after navigation.
16
+ 4. **View Transitions**: Add `odac-transition` attribute to elements for native browser View Transition API animations. Zero-config — no JS setup required.
16
17
 
17
18
  ## Patterns
18
19
  ```javascript
@@ -25,3 +26,46 @@ Odac.action({
25
26
  }
26
27
  });
27
28
  ```
29
+
30
+ ## View Transitions (Native Browser API)
31
+
32
+ Elements with the `odac-transition` attribute automatically use the browser's View Transition API instead of the legacy fade animation. The attribute value becomes the `view-transition-name`, enabling per-element morphing between pages.
33
+
34
+ ### HTML Usage
35
+ ```html
36
+ <header odac-transition="header">Site Header</header>
37
+ <nav odac-transition="sidebar">Navigation</nav>
38
+ <main>Content updated by AJAX loader</main>
39
+ <img odac-transition="hero" src="/hero.jpg" />
40
+ ```
41
+
42
+ ### Behavior
43
+ - When `odac-transition` elements exist and the browser supports View Transition API → native transition is used.
44
+ - When no `odac-transition` elements exist or the API is unsupported → legacy fade fallback runs automatically.
45
+ - Transition names are applied before the snapshot and cleaned up after the transition completes.
46
+ - The attribute value must be unique per page (browser requirement for `view-transition-name`).
47
+
48
+ ### CSS Customization
49
+ ```css
50
+ /* Target a specific element's transition */
51
+ ::view-transition-old(hero) {
52
+ animation: fade-out 0.3s ease;
53
+ }
54
+ ::view-transition-new(hero) {
55
+ animation: fade-in 0.3s ease;
56
+ }
57
+
58
+ /* Slide sidebar from left */
59
+ ::view-transition-old(sidebar) {
60
+ animation: slide-out-left 0.25s ease;
61
+ }
62
+ ::view-transition-new(sidebar) {
63
+ animation: slide-in-left 0.25s ease;
64
+ }
65
+ ```
66
+
67
+ ### Rules
68
+ 1. Each `odac-transition` value must be unique within the page.
69
+ 2. Elements persist across navigations (e.g., shared header) for smooth morphing.
70
+ 3. No JavaScript configuration needed — attribute-only setup.
71
+ 4. Falls back to fade animation gracefully on unsupported browsers.
@@ -50,5 +50,21 @@ source.onmessage = (event) => {
50
50
 
51
51
  ## Best Practices
52
52
  - **Resource Management**: Shared WebSocket automatically closes when the last tab using it is closed.
53
- - **Fallback**: If `SharedWorker` is not supported (e.g., Safari), `Odac.ws` automatically falls back to a standard WebSocket.
54
- - **Server-Side Hubs**: Ensure the backend uses `Odac.Hub` to send messages to the correct rooms or users.
53
+ - **Security**: Always use `Odac.ws(url, { token: true })` for authenticated paths; the client will automatically attach the latest CSRF/Session token.
54
+ - **Distributed State**: Use `Odac.Ipc` in backend WS handlers to broadcast messages across multiple server workers.
55
+
56
+ ### 4. Rooms & Broadcasting (Backend Example)
57
+ ```javascript
58
+ // controller/ws/Game.js
59
+ module.exports = async function(Odac) {
60
+ const ws = Odac.ws;
61
+ const roomId = await Odac.request('roomId');
62
+
63
+ ws.join(roomId);
64
+
65
+ ws.on('message', (data) => {
66
+ // Broadcast only to this room
67
+ ws.to(roomId).send({ user: ws.id, move: data.move });
68
+ });
69
+ };
70
+ ```
@@ -21,6 +21,7 @@ Remember the `Odac` object? It's your best friend inside a controller. It's pass
21
21
  * `Odac.setInterval(callback, delay)`: Schedule repeating tasks (auto-cleanup).
22
22
  * `Odac.setTimeout(callback, delay)`: Schedule one-time tasks (auto-cleanup).
23
23
  * `Odac.stream(input)`: Create streaming responses (SSE).
24
+ * `Odac.image(src, options)`: Get a processed image URL (resize, format conversion, caching).
24
25
 
25
26
  #### Memory-Safe Timers
26
27
 
@@ -41,3 +42,26 @@ module.exports = async (Odac) => {
41
42
  ```
42
43
 
43
44
  With controllers and the `Odac` object, you have everything you need to start building powerful application logic!
45
+
46
+ #### Image Processing
47
+
48
+ Use `Odac.image()` when you need a processed image URL outside of templates — for JSON APIs, div backgrounds, email templates, or cron jobs:
49
+
50
+ ```javascript
51
+ module.exports = async (Odac) => {
52
+ // Get a resized WebP URL for an API response
53
+ const heroUrl = await Odac.image('/images/hero.jpg', { width: 1200 })
54
+ // → "/_odac/img/hero-1200-a1b2c3d4.webp"
55
+
56
+ // Pass a processed URL to the template for a CSS background
57
+ const bgUrl = await Odac.image('/images/banner.jpg', { width: 1920, quality: 90 })
58
+ Odac.set('bgUrl', bgUrl)
59
+
60
+ // Convert to a specific format
61
+ const pngLogo = await Odac.image('/images/logo.png', { width: 200, format: 'png' })
62
+ }
63
+ ```
64
+
65
+ **Options:** `{ width, height, format, quality }` — same as `<odac:img>` tag attributes.
66
+
67
+ > When `sharp` is not installed, `Odac.image()` returns the original source path as a graceful fallback. For template usage with automatic `<img>` tag generation, see the [`<odac:img>` documentation](../07-views/11-image-optimization.md).
@@ -1,8 +1,17 @@
1
1
  ## 🔧 Template Syntax Overview
2
2
 
3
- Odac uses a powerful template engine to create dynamic content in view files. The engine provides a clean, HTML-like syntax for displaying variables, conditionals, loops, translations, and more.
3
+ Odac uses a powerful template engine to create dynamic content in view files. The engine provides two equivalent syntaxes for displaying variables, plus dedicated tags for conditionals, loops, translations, and more.
4
4
 
5
- > **Note:** Odac also supports legacy syntax (`{{ }}`, `{!! !!}`, `{{-- --}}`) for backward compatibility, but the new `<odac>` tag syntax is recommended for all new projects.
5
+ ### Two Syntaxes, One Engine
6
+
7
+ ODAC offers two ways to output variables. Both are HTML-escaped (XSS-safe) and compile to the same engine code. The choice is about readability, not functionality.
8
+
9
+ | Syntax | Best For | Example |
10
+ |--------|----------|---------|
11
+ | `<odac var="x" />` | Standalone block output where the variable is the main content | `<h1><odac var="title" /></h1>` |
12
+ | `{{ x }}` | Attributes, inline text, and mixed HTML where tag syntax would be verbose | `<img src="{{ photo.url }}" alt="{{ photo.caption }}">` |
13
+
14
+ > **Guideline:** Use `{{ }}` inside HTML attributes (`src`, `href`, `alt`, `class`, `value`, etc.) and for inline text interpolation. Use `<odac var>` for standalone element content. Both are equally supported and recommended.
6
15
 
7
16
  ### Quick Reference
8
17
 
@@ -13,10 +22,17 @@ This page provides a quick overview of all available template features. For deta
13
22
  Display data passed from controllers using `Odac.set()`:
14
23
 
15
24
  ```html
16
- <!-- HTML-safe output -->
17
- <odac var="username" />
25
+ <!-- Standalone block output — prefer <odac var> -->
26
+ <h1><odac var="username" /></h1>
27
+
28
+ <!-- Inside attributes — prefer {{ }} -->
29
+ <img src="{{ product.image }}" alt="{{ product.name }}">
30
+ <a href="/user/{{ user.id }}">Profile</a>
18
31
 
19
- <!-- Raw HTML output -->
32
+ <!-- Inline text prefer {{ }} -->
33
+ <p>Welcome, {{ user.name }}. You have {{ count }} items.</p>
34
+
35
+ <!-- Raw HTML output (trusted content only) -->
20
36
  <odac var="htmlContent" raw />
21
37
 
22
38
  <!-- String literals -->
@@ -130,6 +146,23 @@ Execute JavaScript on the server during template rendering:
130
146
 
131
147
  **[→ Learn more about Backend JavaScript](./08-backend-javascript.md)**
132
148
 
149
+ ### Image Optimization
150
+
151
+ Automatically resize and convert images to modern formats:
152
+
153
+ ```html
154
+ <!-- Auto-convert to WebP (default) -->
155
+ <odac:img src="/images/hero.jpg" />
156
+
157
+ <!-- Resize + convert -->
158
+ <odac:img src="/images/photo.jpg" width="800" height="600" format="webp" />
159
+
160
+ <!-- With standard HTML attributes -->
161
+ <odac:img src="/images/avatar.jpg" width="64" height="64" alt="Avatar" loading="lazy" />
162
+ ```
163
+
164
+ **[→ Learn more about Image Optimization](./11-image-optimization.md)**
165
+
133
166
  ### Accessing the Odac Object
134
167
 
135
168
  Full access to the Odac object in templates:
@@ -146,8 +179,10 @@ Full access to the Odac object in templates:
146
179
 
147
180
  | Feature | Syntax | Documentation |
148
181
  |---------|--------|---------------|
149
- | Variable (Controller) | `<odac var="x" />` | [Variables](./03-variables.md) |
150
- | Raw HTML | `<odac var="x" raw />` | [Variables](./03-variables.md) |
182
+ | Variable (standalone) | `<odac var="x" />` | [Variables](./03-variables.md) |
183
+ | Variable (inline/attribute) | `{{ x }}` | [Variables](./03-variables.md) |
184
+ | Raw HTML (tag) | `<odac var="x" raw />` | [Variables](./03-variables.md) |
185
+ | Raw HTML (inline) | `{!! x !!}` | [Variables](./03-variables.md) |
151
186
  | String | `<odac>text</odac>` | [Variables](./03-variables.md) |
152
187
  | Query Parameter | `<odac get="key" />` | [Request Data](./04-request-data.md) |
153
188
  | Translation | `<odac translate>key</odac>` | [Translations](./07-translations.md) |
@@ -161,21 +196,36 @@ Full access to the Odac object in templates:
161
196
  | Continue | `<odac:continue />` | [Loops](./06-loops.md) |
162
197
  | JavaScript | `<script:odac>...</script:odac>` | [Backend JavaScript](./08-backend-javascript.md) |
163
198
  | Comment | `<!--odac ... odac-->` | [Comments](./09-comments.md) |
199
+ | Image | `<odac:img src="..." />` | [Image Optimization](./11-image-optimization.md) |
164
200
 
165
- ### Legacy Syntax
201
+ ### Syntax Selection Guide
166
202
 
167
- Odac also supports legacy syntax for backward compatibility:
203
+ Choose the right syntax based on context for clean, readable templates:
168
204
 
169
205
  ```html
170
- <!-- Variable output -->
171
- {{ username }}
206
+ <!-- Attributes — use {{ }} -->
207
+ <img src="{{ product.image }}" alt="{{ product.name }}">
208
+ <a href="/products/{{ product.id }}" class="card {{ isActive ? 'active' : '' }}">
209
+ <input type="text" name="search" value="{{ query }}">
210
+
211
+ <!-- ✅ Inline text — use {{ }} -->
212
+ <p>Hello, {{ user.name }}. You have {{ count }} notifications.</p>
213
+ <span>${{ product.price }}</span>
214
+
215
+ <!-- ✅ Standalone block content — use <odac var> -->
216
+ <h1><odac var="pageTitle" /></h1>
217
+ <td><odac var="user.email" /></td>
218
+
219
+ <!-- ✅ Raw output — either form works -->
220
+ <div><odac var="richContent" raw /></div>
221
+ <div>{!! richContent !!}</div>
222
+ ```
172
223
 
173
- <!-- Raw HTML -->
174
- {!! htmlContent !!}
224
+ ### Legacy Comment Syntax
175
225
 
176
- <!-- Comments -->
226
+ ```html
177
227
  {{-- This is a comment --}}
178
228
  ```
179
229
 
180
- **Note:** The new `<odac>` tag syntax is recommended for all new projects.
230
+ **Note:** For new projects, prefer the `<!--odac ... odac-->` comment syntax for consistency.
181
231
 
@@ -311,18 +311,33 @@ module.exports = async function(Odac) {
311
311
  </odac:if>
312
312
  ```
313
313
 
314
- ### Legacy Syntax (Backward Compatibility)
314
+ ### Inline Syntax (`{{ }}` and `{!! !!}`)
315
315
 
316
- Odac also supports legacy syntax:
316
+ ODAC provides an inline interpolation syntax that is equivalent to the `<odac var>` tag. Both compile to the same engine output and are equally supported.
317
+
318
+ **Use `{{ }}` when the variable appears inside HTML attributes or inline within text:**
317
319
 
318
320
  ```html
319
- <!-- HTML-safe output -->
320
- {{ username }}
321
- {{ user.email }}
321
+ <!-- Inside attributes — {{ }} keeps markup clean -->
322
+ <img src="{{ product.image }}" alt="{{ product.name }}">
323
+ <a href="/user/{{ user.id }}" class="btn {{ isActive ? 'active' : '' }}">Profile</a>
324
+ <input type="text" name="q" value="{{ searchQuery }}">
325
+
326
+ <!-- Inline text interpolation -->
327
+ <p>Welcome, {{ user.name }}. You have {{ notifications }} new messages.</p>
328
+ <span>${{ product.price }}</span>
322
329
 
323
- <!-- Raw HTML output -->
330
+ <!-- Raw inline output (trusted content only) -->
324
331
  {!! htmlContent !!}
325
332
  {!! user.bio !!}
326
333
  ```
327
334
 
328
- **Note:** The new `<odac>` tag syntax is recommended for all new projects as it provides better IDE support and readability.
335
+ **Use `<odac var>` when the variable is the standalone content of an element:**
336
+
337
+ ```html
338
+ <h1><odac var="pageTitle" /></h1>
339
+ <td><odac var="user.email" /></td>
340
+ <p><odac var="product.description" /></p>
341
+ ```
342
+
343
+ Both syntaxes are XSS-safe by default (HTML-escaped). The choice is purely about readability. See [Template Syntax Overview](./03-template-syntax.md) for the full selection guide.
@@ -0,0 +1,197 @@
1
+ ## 🖼️ Image Optimization
2
+
3
+ ODAC provides automatic image optimization through the `<odac:img>` tag. Images are resized, converted to modern formats (WebP, AVIF), and cached — all on-demand, with zero configuration required.
4
+
5
+ > **Note:** Image optimization requires the `sharp` package. Install it with `npm install sharp`. Without it, `<odac:img>` gracefully falls back to a standard `<img>` tag with no processing.
6
+
7
+ ### Basic Usage
8
+
9
+ The simplest usage — just provide a `src`. ODAC automatically converts the image to WebP:
10
+
11
+ ```html
12
+ <odac:img src="/images/hero.jpg" />
13
+ ```
14
+
15
+ This is equivalent to writing:
16
+
17
+ ```html
18
+ <img src="/_odac/img/hero-o-a1b2c3d4.webp">
19
+ ```
20
+
21
+ The processed image is cached after the first request. All subsequent requests are served instantly.
22
+
23
+ ### Resize
24
+
25
+ Specify `width` and/or `height` to resize the image. Aspect ratio is always preserved — the image will never be stretched or enlarged beyond its original size:
26
+
27
+ ```html
28
+ <!-- Resize by width only -->
29
+ <odac:img src="/images/hero.jpg" width="800" />
30
+
31
+ <!-- Resize by both dimensions (fits inside the box) -->
32
+ <odac:img src="/images/hero.jpg" width="800" height="600" />
33
+
34
+ <!-- Thumbnail -->
35
+ <odac:img src="/images/product.jpg" width="200" height="200" />
36
+ ```
37
+
38
+ ### Format Conversion
39
+
40
+ Convert to any modern format using the `format` attribute:
41
+
42
+ ```html
43
+ <!-- WebP (default) -->
44
+ <odac:img src="/images/photo.png" format="webp" />
45
+
46
+ <!-- AVIF (best compression, newer browsers) -->
47
+ <odac:img src="/images/photo.png" format="avif" />
48
+
49
+ <!-- Keep as PNG -->
50
+ <odac:img src="/images/logo.png" format="png" />
51
+ ```
52
+
53
+ **Supported formats:** `webp`, `avif`, `png`, `jpeg`, `tiff`
54
+
55
+ ### Quality
56
+
57
+ Control the compression quality with the `quality` attribute (1–100, default: 80):
58
+
59
+ ```html
60
+ <!-- High quality for hero images -->
61
+ <odac:img src="/images/banner.jpg" width="1200" quality="90" />
62
+
63
+ <!-- Lower quality for thumbnails (smaller file size) -->
64
+ <odac:img src="/images/thumb.jpg" width="100" quality="60" />
65
+ ```
66
+
67
+ ### Standard HTML Attributes
68
+
69
+ All standard `<img>` attributes are passed through as-is:
70
+
71
+ ```html
72
+ <odac:img
73
+ src="/images/avatar.jpg"
74
+ width="64"
75
+ height="64"
76
+ alt="User avatar"
77
+ class="rounded-full"
78
+ loading="lazy"
79
+ decoding="async"
80
+ />
81
+ ```
82
+
83
+ ### Dynamic Source
84
+
85
+ Use template variables for dynamic image sources:
86
+
87
+ ```html
88
+ <!-- From controller data -->
89
+ <odac:img src="{{ product.image }}" width="300" height="300" alt="{{ product.name }}" />
90
+
91
+ <!-- In a loop -->
92
+ <odac:for in="products" value="product">
93
+ <odac:img src="{{ product.thumbnail }}" width="200" alt="{{ product.name }}" />
94
+ </odac:for>
95
+ ```
96
+
97
+ ### Practical Examples
98
+
99
+ #### Product Card
100
+
101
+ ```html
102
+ <div class="product-card">
103
+ <odac:img
104
+ src="{{ product.image }}"
105
+ width="400"
106
+ height="300"
107
+ alt="{{ product.name }}"
108
+ class="product-image"
109
+ loading="lazy"
110
+ />
111
+ <h3><odac var="product.name" /></h3>
112
+ <p>$<odac var="product.price" /></p>
113
+ </div>
114
+ ```
115
+
116
+ #### Hero Banner
117
+
118
+ ```html
119
+ <section class="hero">
120
+ <odac:img
121
+ src="/images/hero.jpg"
122
+ width="1920"
123
+ height="600"
124
+ alt="Hero banner"
125
+ class="hero-image"
126
+ quality="90"
127
+ />
128
+ </section>
129
+ ```
130
+
131
+ #### Avatar with Fallback
132
+
133
+ ```html
134
+ <odac:if condition="user.avatar">
135
+ <odac:img src="{{ user.avatar }}" width="64" height="64" alt="Avatar" class="avatar" />
136
+ <odac:else>
137
+ <img src="/images/default-avatar.png" width="64" height="64" alt="Default avatar" class="avatar">
138
+ </odac:if>
139
+ ```
140
+
141
+ #### Image Gallery
142
+
143
+ ```html
144
+ <div class="gallery">
145
+ <odac:for in="photos" value="photo">
146
+ <a href="{{ photo.url }}">
147
+ <odac:img
148
+ src="{{ photo.url }}"
149
+ width="400"
150
+ height="300"
151
+ alt="{{ photo.caption }}"
152
+ class="gallery-thumb"
153
+ loading="lazy"
154
+ />
155
+ </a>
156
+ </odac:for>
157
+ </div>
158
+ ```
159
+
160
+ ### Configuration
161
+
162
+ You can override the defaults in `odac.json`:
163
+
164
+ ```json
165
+ {
166
+ "image": {
167
+ "format": "webp",
168
+ "quality": 80,
169
+ "maxDimension": 4096
170
+ }
171
+ }
172
+ ```
173
+
174
+ | Option | Default | Description |
175
+ |--------|---------|-------------|
176
+ | `format` | `webp` | Default output format when `format` is not specified in the tag |
177
+ | `quality` | `80` | Default compression quality (1–100) |
178
+ | `maxDimension` | `4096` | Maximum allowed width or height in pixels |
179
+
180
+ ### How It Works
181
+
182
+ 1. The `<odac:img>` tag is compiled at template render time.
183
+ 2. ODAC checks the source file's modification time (`mtime`) and includes it in the hash. This means when you update a source image, the URL automatically changes — no manual cache busting needed.
184
+ 3. On the first request, ODAC processes the source image using `sharp` and saves the result to `storage/.cache/img/`.
185
+ 4. The `<img>` tag in the HTML points to `/_odac/img/{name}-{dimension}-{hash}.{ext}` — an internal route that serves the cached file. The filename includes the original image name and dimension for easy debugging.
186
+ 5. All subsequent requests hit the cache directly, with `Cache-Control: immutable` headers for maximum browser caching.
187
+
188
+ > **Cache Busting:** You don't need to rename files or add query strings. Simply replace the source image in `public/` and the next page render will produce a new URL with a different hash, forcing browsers and CDNs to fetch the updated version.
189
+
190
+ ### Best Practices
191
+
192
+ - Always set `alt` for accessibility.
193
+ - Use `loading="lazy"` for below-the-fold images to improve page load performance.
194
+ - Set explicit `width` and `height` to prevent layout shift (CLS).
195
+ - Keep original high-resolution images in `public/` — let ODAC handle the downsizing.
196
+ - Use `avif` for the best compression ratio on modern browsers; use `webp` for broader compatibility.
197
+ - For programmatic URL access in controllers (JSON APIs, CSS backgrounds, emails), see [`Odac.image()`](../05-controllers/02-your-trusty-odac-assistant.md#image-processing).
@@ -6,6 +6,7 @@ Odac framework includes a built-in AJAX navigation system that enables smooth, s
6
6
 
7
7
  - **Zero Configuration**: Works automatically with all internal links (`/`)
8
8
  - **Smooth Transitions**: Load only specific page sections without full page reload
9
+ - **Native View Transitions**: Automatic browser View Transition API support via `odac-transition` attribute
9
10
  - **History API Integration**: Browser back/forward buttons work seamlessly
10
11
  - **Automatic Token Management**: CSRF tokens are handled automatically
11
12
  - **Progressive Enhancement**: Falls back to normal navigation if JavaScript fails
@@ -178,6 +179,27 @@ module.exports = function (Odac) {
178
179
  5. Browser URL updates via History API
179
180
  6. Page-specific callbacks execute
180
181
 
182
+ ### View Transition Load (with `odac-transition` elements)
183
+
184
+ When elements with `odac-transition` attribute exist on the page and the browser supports the View Transition API, ODAC uses native transitions instead of fade:
185
+
186
+ 1. User clicks `<a href="/about">`
187
+ 2. ODAC assigns `view-transition-name` to all `odac-transition` elements (old state snapshot)
188
+ 3. AJAX request is sent (same as above)
189
+ 4. `document.startViewTransition()` is called — browser captures old state
190
+ 5. DOM is updated with new content inside the transition callback
191
+ 6. New `odac-transition` elements receive their names
192
+ 7. Browser animates between old and new snapshots
193
+ 8. Transition names are cleaned up after completion
194
+
195
+ No configuration needed — just add the attribute to your HTML:
196
+
197
+ ```html
198
+ <header odac-transition="header">{{ HEADER }}</header>
199
+ <main>{{ CONTENT }}</main>
200
+ <img odac-transition="hero" src="/hero.jpg" alt="Hero" />
201
+ ```
202
+
181
203
  **Key Points:**
182
204
  - The `output` keys in the JSON response match the lowercase keys from `Odac.View.set()` in your controller
183
205
  - These keys correspond to UPPERCASE placeholders in your skeleton (e.g., `content` → `{{ CONTENT }}`)