tkeron 4.0.0-beta.9 → 4.0.1

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 (78) hide show
  1. package/{readme.md → README.md} +36 -4
  2. package/bun.lock +23 -21
  3. package/changelog.md +300 -8
  4. package/docs/best-practices.md +127 -101
  5. package/docs/cli-reference.md +58 -38
  6. package/docs/components-html.md +30 -24
  7. package/docs/components-typescript.md +110 -79
  8. package/docs/getting-started.md +24 -18
  9. package/docs/mcp-server.md +19 -36
  10. package/docs/overview.md +16 -10
  11. package/docs/pre-rendering.md +163 -157
  12. package/docs/testing.md +331 -0
  13. package/examples/basic_build/src/index.ts +5 -5
  14. package/examples/with_assets/src/index.ts +5 -5
  15. package/examples/with_com_html_priority/src/admin/admin-panel.com.html +1 -1
  16. package/examples/with_com_html_priority/src/admin/dashboard.html +16 -16
  17. package/examples/with_com_html_priority/src/admin/nested-stat.com.html +1 -1
  18. package/examples/with_com_html_priority/src/admin/site-header.com.html +1 -1
  19. package/examples/with_com_html_priority/src/blog/comment-item.com.html +1 -1
  20. package/examples/with_com_html_priority/src/blog/comment-section.com.html +1 -1
  21. package/examples/with_com_html_priority/src/blog/post.html +22 -19
  22. package/examples/with_com_html_priority/src/index.html +15 -15
  23. package/examples/with_com_html_priority/src/info-box.com.html +1 -1
  24. package/examples/with_com_html_priority/src/site-header.com.html +1 -1
  25. package/examples/with_com_mixed_priority/src/dashboard/main.html +17 -17
  26. package/examples/with_com_mixed_priority/src/dashboard/stats-widget.com.ts +1 -1
  27. package/examples/with_com_mixed_priority/src/footer-info.com.html +3 -1
  28. package/examples/with_com_mixed_priority/src/index.html +16 -16
  29. package/examples/with_com_mixed_priority/src/page-header.com.html +8 -1
  30. package/examples/with_com_mixed_priority/src/settings/config.html +16 -16
  31. package/examples/with_com_mixed_priority/src/settings/priority-test.com.html +3 -1
  32. package/examples/with_com_mixed_priority/src/users/profiles/activity-feed.com.ts +7 -3
  33. package/examples/with_com_mixed_priority/src/users/profiles/page-header.com.html +8 -1
  34. package/examples/with_com_mixed_priority/src/users/profiles/profile-actions.com.html +45 -4
  35. package/examples/with_com_mixed_priority/src/users/profiles/user-profile.com.ts +1 -1
  36. package/examples/with_com_mixed_priority/src/users/profiles/view.html +17 -17
  37. package/examples/with_com_ts/src/index.html +15 -11
  38. package/examples/with_com_ts_priority/src/analytics/chart-widget.com.ts +7 -5
  39. package/examples/with_com_ts_priority/src/analytics/report.html +16 -16
  40. package/examples/with_com_ts_priority/src/analytics/user-stats.com.ts +1 -1
  41. package/examples/with_com_ts_priority/src/data-table.com.ts +7 -4
  42. package/examples/with_com_ts_priority/src/index.html +15 -15
  43. package/examples/with_com_ts_priority/src/sales/regional/data-table.com.ts +8 -5
  44. package/examples/with_com_ts_priority/src/sales/regional/overview.html +16 -16
  45. package/examples/with_com_ts_priority/src/sales/regional/sales-summary.com.ts +7 -4
  46. package/examples/with_com_ts_priority/src/user-stats.com.ts +1 -1
  47. package/examples/with_component_iteration/src/card-list.com.ts +40 -12
  48. package/examples/with_component_iteration/src/engagement-meter.com.ts +11 -6
  49. package/examples/with_component_iteration/src/index.html +108 -51
  50. package/examples/with_component_iteration/src/index.ts +1 -1
  51. package/examples/with_component_iteration/src/note-box.com.html +12 -2
  52. package/examples/with_component_iteration/src/status-badge.com.ts +14 -7
  53. package/examples/with_component_iteration/src/user-card.com.ts +10 -9
  54. package/examples/with_pre/src/contact.pre.ts +3 -3
  55. package/examples/with_pre/src/index.ts +5 -5
  56. package/examples/with_pre/src/section/index.pre.ts +2 -4
  57. package/index.ts +31 -40
  58. package/mcp-server.ts +168 -445
  59. package/package.json +19 -8
  60. package/src/banner.ts +5 -4
  61. package/src/build.ts +29 -29
  62. package/src/buildDir.ts +36 -18
  63. package/src/buildEntrypoints.ts +17 -6
  64. package/src/buildWrapper.ts +21 -0
  65. package/src/cleanupOrphanedTempDirs.ts +31 -0
  66. package/src/develop.ts +47 -46
  67. package/src/getBuildResult.ts +146 -0
  68. package/src/index.ts +7 -0
  69. package/src/init.ts +39 -42
  70. package/src/initWrapper.ts +39 -26
  71. package/src/processCom.ts +136 -108
  72. package/src/processComTs.ts +173 -151
  73. package/src/processPre.ts +24 -27
  74. package/src/promptUser.ts +15 -0
  75. package/src/setupSigintHandler.ts +6 -0
  76. package/tests/test-helpers.ts +30 -0
  77. package/funciones.md +0 -63
  78. package/src/logger.ts +0 -11
@@ -3,6 +3,7 @@
3
3
  ## What Tkeron Does
4
4
 
5
5
  **Build time:**
6
+
6
7
  - Runs `.pre.ts` - full DOM access, TypeScript, Bun APIs
7
8
  - Executes `.com.ts` - reads attributes, generates HTML
8
9
  - Inlines `.com.html` - static replacement
@@ -10,6 +11,7 @@
10
11
  - Copies assets
11
12
 
12
13
  **Runtime (browser):**
14
+
13
15
  - Your compiled JavaScript runs
14
16
  - Zero framework overhead
15
17
  - Use any browser APIs
@@ -36,27 +38,28 @@ Dynamic generation with attributes:
36
38
 
37
39
  ```typescript
38
40
  // user-badge.com.ts
39
- const count = com.getAttribute('count') || '3';
41
+ const count = com.getAttribute("count") || "3";
40
42
  const items = [];
41
43
  for (let i = 1; i <= parseInt(count); i++) {
42
44
  items.push(`<li>Item ${i}</li>`);
43
45
  }
44
- com.innerHTML = `<ul>${items.join('')}</ul>`;
46
+ com.innerHTML = `<ul>${items.join("")}</ul>`;
45
47
  ```
46
48
 
47
49
  Use for: Dynamic lists, conditional rendering, computed HTML.
48
50
 
49
51
  **Always validate and escape:**
52
+
50
53
  ```typescript
51
54
  function escapeHtml(str: string): string {
52
55
  return str
53
- .replace(/&/g, '&amp;')
54
- .replace(/</g, '&lt;')
55
- .replace(/>/g, '&gt;')
56
- .replace(/"/g, '&quot;');
56
+ .replace(/&/g, "&amp;")
57
+ .replace(/</g, "&lt;")
58
+ .replace(/>/g, "&gt;")
59
+ .replace(/"/g, "&quot;");
57
60
  }
58
61
 
59
- const text = com.getAttribute('text') || '';
62
+ const text = com.getAttribute("text") || "";
60
63
  com.innerHTML = `<p>${escapeHtml(text)}</p>`;
61
64
  ```
62
65
 
@@ -66,14 +69,15 @@ Build-time DOM manipulation:
66
69
 
67
70
  ```typescript
68
71
  // index.pre.ts
69
- const res = await fetch('https://api.quotable.io/random');
72
+ const res = await fetch("https://api.quotable.io/random");
70
73
  const data = await res.json();
71
- document.getElementById('quote').textContent = data.content;
74
+ document.getElementById("quote").textContent = data.content;
72
75
  ```
73
76
 
74
77
  Use for: API data fetching, SEO meta tags, build metadata.
75
78
 
76
79
  **Handle errors:**
80
+
77
81
  ```typescript
78
82
  const controller = new AbortController();
79
83
  setTimeout(() => controller.abort(), 5000);
@@ -114,20 +118,22 @@ Components run at build time. For runtime interactivity, use your `.ts` files:
114
118
 
115
119
  ```typescript
116
120
  // index.ts - runs in browser
117
- document.getElementById('btn').addEventListener('click', () => {
118
- console.log('clicked');
121
+ document.getElementById("btn").addEventListener("click", () => {
122
+ console.log("clicked");
119
123
  });
120
124
  ```
121
125
 
122
126
  ## Build vs Runtime
123
127
 
124
128
  **Build time (.pre.ts, .com.ts):**
129
+
125
130
  - Runs in Bun
126
131
  - Has `fetch()`, `Bun.file()`, Node APIs
127
132
  - Can import npm packages
128
133
  - No `window`, `localStorage`, etc.
129
134
 
130
135
  **Runtime (browser):**
136
+
131
137
  - Your compiled `.js` files
132
138
  - Standard browser environment
133
139
  - All browser APIs available
@@ -137,18 +143,21 @@ document.getElementById('btn').addEventListener('click', () => {
137
143
  Tkeron does NOT bundle npm packages for browser.
138
144
 
139
145
  **Build time:**
146
+
140
147
  ```typescript
141
148
  // .pre.ts - works
142
- import _ from 'lodash';
149
+ import _ from "lodash";
143
150
  ```
144
151
 
145
152
  **Browser:**
153
+
146
154
  ```typescript
147
155
  // index.ts - won't work without bundler
148
- import _ from 'lodash';
156
+ import _ from "lodash";
149
157
  ```
150
158
 
151
159
  **Solutions:**
160
+
152
161
  - Use CDN: `<script src="https://cdn.jsdelivr.net/npm/lodash"></script>`
153
162
  - Use a bundler alongside (Vite, esbuild)
154
163
  - Write vanilla JavaScript
@@ -164,11 +173,13 @@ tsc --noEmit && tk build
164
173
  ## Performance
165
174
 
166
175
  **Build time:**
176
+
167
177
  - Cache external data when possible
168
178
  - Use timeouts for API calls
169
179
  - Parallel requests with `Promise.all()`
170
180
 
171
181
  **Output size:**
182
+
172
183
  - Keep components lean
173
184
  - Use CSS classes, not inline styles
174
185
  - Share styles via external CSS files
@@ -184,6 +195,7 @@ tk build # Build for production
184
195
  ## Common Patterns
185
196
 
186
197
  **Client-side counter:**
198
+
187
199
  ```html
188
200
  <!-- counter.com.html -->
189
201
  <button id="btn">Count: <span id="count">0</span></button>
@@ -192,25 +204,27 @@ tk build # Build for production
192
204
  ```typescript
193
205
  // index.ts
194
206
  let count = 0;
195
- document.getElementById('btn').addEventListener('click', () => {
207
+ document.getElementById("btn").addEventListener("click", () => {
196
208
  count++;
197
- document.getElementById('count').textContent = count.toString();
209
+ document.getElementById("count").textContent = count.toString();
198
210
  });
199
211
  ```
200
212
 
201
213
  **Dynamic list component:**
214
+
202
215
  ```typescript
203
216
  // list.com.ts
204
- const items = com.getAttribute('items')?.split(',') || [];
205
- com.innerHTML = `<ul>${items.map(i => `<li>${i}</li>`).join('')}</ul>`;
217
+ const items = com.getAttribute("items")?.split(",") || [];
218
+ com.innerHTML = `<ul>${items.map((i) => `<li>${i}</li>`).join("")}</ul>`;
206
219
  ```
207
220
 
208
221
  **Pre-render API data:**
222
+
209
223
  ```typescript
210
224
  // index.pre.ts
211
- const res = await fetch('https://api.github.com/users/tkeron');
225
+ const res = await fetch("https://api.github.com/users/tkeron");
212
226
  const data = await res.json();
213
- document.getElementById('stars').textContent = data.public_repos;
227
+ document.getElementById("stars").textContent = data.public_repos;
214
228
  ```
215
229
 
216
230
  ## Summary
@@ -223,30 +237,31 @@ document.getElementById('stars').textContent = data.public_repos;
223
237
  - No type checking (use `tsc --noEmit`)
224
238
  - Powered by Bun
225
239
  document.querySelectorAll('.my-button').forEach(btn => {
226
- btn.addEventListener('click', handleClick);
240
+ btn.addEventListener('click', handleClick);
227
241
  });
228
- });
229
- ```
242
+ });
243
+
244
+ ````
230
245
 
231
246
  ```typescript
232
247
  // ❌ Don't try to maintain state
233
248
  let count = 0; // This is meaningless
234
249
  com.innerHTML = `<button>Count: ${count}</button>`;
235
- ```
250
+ ````
236
251
 
237
252
  **Solution:** Use client-side JavaScript for stateful UI.
238
253
 
239
254
  ```typescript
240
255
  // ❌ Don't access DOM outside 'com'
241
- const header = document.querySelector('header');
242
- com.innerHTML = header?.innerHTML || ''; // Won't work
256
+ const header = document.querySelector("header");
257
+ com.innerHTML = header?.innerHTML || ""; // Won't work
243
258
  ```
244
259
 
245
260
  **Solution:** Use pre-rendering for document-wide access.
246
261
 
247
262
  ```typescript
248
263
  // ❌ Don't use relative imports that won't resolve
249
- import { helper } from '../../../utils'; // May break
264
+ import { helper } from "../../../utils"; // May break
250
265
  ```
251
266
 
252
267
  **Solution:** Use absolute paths or ensure the temp build directory preserves import paths.
@@ -261,10 +276,10 @@ import { helper } from '../../../utils'; // May break
261
276
  // ✅ Fetch data at build time
262
277
  async function fetchPosts() {
263
278
  try {
264
- const res = await fetch('https://api.example.com/posts');
279
+ const res = await fetch("https://api.example.com/posts");
265
280
  return await res.json();
266
281
  } catch (error) {
267
- console.error('Failed to fetch:', error);
282
+ console.error("Failed to fetch:", error);
268
283
  return [];
269
284
  }
270
285
  }
@@ -274,7 +289,7 @@ const posts = await fetchPosts();
274
289
 
275
290
  ```typescript
276
291
  // ✅ Inject build metadata
277
- const buildTime = document.getElementById('build-time');
292
+ const buildTime = document.getElementById("build-time");
278
293
  if (buildTime) {
279
294
  buildTime.textContent = new Date().toISOString();
280
295
  }
@@ -282,23 +297,23 @@ if (buildTime) {
282
297
 
283
298
  ```typescript
284
299
  // ✅ Conditional content based on environment
285
- const isProd = process.env.NODE_ENV === 'production';
300
+ const isProd = process.env.NODE_ENV === "production";
286
301
 
287
302
  if (isProd) {
288
303
  // Add analytics
289
- const script = document.createElement('script');
290
- script.src = 'https://analytics.example.com/script.js';
304
+ const script = document.createElement("script");
305
+ script.src = "https://analytics.example.com/script.js";
291
306
  document.body?.appendChild(script);
292
307
  }
293
308
  ```
294
309
 
295
310
  ```typescript
296
311
  // ✅ Generate SEO meta tags
297
- const head = document.querySelector('head');
312
+ const head = document.querySelector("head");
298
313
  if (head) {
299
- const meta = document.createElement('meta');
300
- meta.setAttribute('property', 'og:title');
301
- meta.setAttribute('content', 'My Page Title');
314
+ const meta = document.createElement("meta");
315
+ meta.setAttribute("property", "og:title");
316
+ meta.setAttribute("content", "My Page Title");
302
317
  head.appendChild(meta);
303
318
  }
304
319
  ```
@@ -307,8 +322,8 @@ if (head) {
307
322
 
308
323
  ```typescript
309
324
  // ❌ Don't use browser APIs
310
- window.location.href = '/redirect'; // No 'window'
311
- localStorage.setItem('key', 'value'); // No localStorage
325
+ window.location.href = "/redirect"; // No 'window'
326
+ localStorage.setItem("key", "value"); // No localStorage
312
327
  navigator.userAgent; // No navigator
313
328
  ```
314
329
 
@@ -316,7 +331,7 @@ navigator.userAgent; // No navigator
316
331
 
317
332
  ```typescript
318
333
  // ❌ Don't try to read runtime user data
319
- const username = document.querySelector('#username-input')?.value;
334
+ const username = document.querySelector("#username-input")?.value;
320
335
  // This is build-time, no user input exists yet
321
336
  ```
322
337
 
@@ -324,7 +339,7 @@ const username = document.querySelector('#username-input')?.value;
324
339
 
325
340
  ```typescript
326
341
  // ❌ Don't make blocking requests without timeout
327
- const data = await fetch('https://slow-api.com/data');
342
+ const data = await fetch("https://slow-api.com/data");
328
343
  // If API is down, build hangs forever
329
344
  ```
330
345
 
@@ -335,8 +350,8 @@ const controller = new AbortController();
335
350
  const timeout = setTimeout(() => controller.abort(), 5000);
336
351
 
337
352
  try {
338
- const data = await fetch('https://api.com/data', {
339
- signal: controller.signal
353
+ const data = await fetch("https://api.com/data", {
354
+ signal: controller.signal,
340
355
  });
341
356
  return await data.json();
342
357
  } catch (error) {
@@ -390,6 +405,7 @@ websrc/
390
405
  ```
391
406
 
392
407
  **Benefits:**
408
+
393
409
  - Components are organized by purpose
394
410
  - Related files are grouped together
395
411
  - Easy to find and maintain
@@ -408,6 +424,7 @@ websrc/
408
424
  ```
409
425
 
410
426
  **Problems:**
427
+
411
428
  - No organization
412
429
  - Unclear purpose
413
430
  - Hard to maintain
@@ -429,6 +446,7 @@ social-share-button.com.ts → <social-share-button>
429
446
  ```
430
447
 
431
448
  **Rules:**
449
+
432
450
  - Lowercase with hyphens (kebab-case)
433
451
  - Descriptive and specific
434
452
  - Follows custom element naming (must have hyphen)
@@ -475,43 +493,46 @@ test123.html → Meaningless
475
493
 
476
494
  ```typescript
477
495
  // ✅ Cache external data
478
- import { readFileSync, writeFileSync, existsSync } from 'fs';
496
+ import { readFileSync, writeFileSync, existsSync } from "fs";
479
497
 
480
- const cacheFile = '/tmp/api-cache.json';
498
+ const cacheFile = "/tmp/api-cache.json";
481
499
  const cacheMaxAge = 3600000; // 1 hour
482
500
 
483
501
  async function getCachedData() {
484
502
  if (existsSync(cacheFile)) {
485
- const cache = JSON.parse(readFileSync(cacheFile, 'utf-8'));
503
+ const cache = JSON.parse(readFileSync(cacheFile, "utf-8"));
486
504
  if (Date.now() - cache.timestamp < cacheMaxAge) {
487
505
  return cache.data;
488
506
  }
489
507
  }
490
-
491
- const data = await fetch('https://api.example.com/data');
508
+
509
+ const data = await fetch("https://api.example.com/data");
492
510
  const json = await data.json();
493
-
494
- writeFileSync(cacheFile, JSON.stringify({
495
- timestamp: Date.now(),
496
- data: json
497
- }));
498
-
511
+
512
+ writeFileSync(
513
+ cacheFile,
514
+ JSON.stringify({
515
+ timestamp: Date.now(),
516
+ data: json,
517
+ }),
518
+ );
519
+
499
520
  return json;
500
521
  }
501
522
  ```
502
523
 
503
524
  ```typescript
504
525
  // ✅ Limit API calls
505
- const posts = await fetch('https://api.example.com/posts?limit=10');
526
+ const posts = await fetch("https://api.example.com/posts?limit=10");
506
527
  // Don't fetch all 10,000 posts at build time
507
528
  ```
508
529
 
509
530
  ```typescript
510
531
  // ✅ Parallel processing when possible
511
532
  const [posts, authors, tags] = await Promise.all([
512
- fetch('https://api.example.com/posts').then(r => r.json()),
513
- fetch('https://api.example.com/authors').then(r => r.json()),
514
- fetch('https://api.example.com/tags').then(r => r.json()),
533
+ fetch("https://api.example.com/posts").then((r) => r.json()),
534
+ fetch("https://api.example.com/authors").then((r) => r.json()),
535
+ fetch("https://api.example.com/tags").then((r) => r.json()),
515
536
  ]);
516
537
  ```
517
538
 
@@ -519,15 +540,15 @@ const [posts, authors, tags] = await Promise.all([
519
540
 
520
541
  ```typescript
521
542
  // ❌ Sequential API calls
522
- const posts = await fetch('/api/posts').then(r => r.json());
523
- const authors = await fetch('/api/authors').then(r => r.json());
524
- const tags = await fetch('/api/tags').then(r => r.json());
543
+ const posts = await fetch("/api/posts").then((r) => r.json());
544
+ const authors = await fetch("/api/authors").then((r) => r.json());
545
+ const tags = await fetch("/api/tags").then((r) => r.json());
525
546
  // Use Promise.all instead
526
547
  ```
527
548
 
528
549
  ```typescript
529
550
  // ❌ Large data fetching
530
- const allUsers = await fetch('/api/users?limit=100000');
551
+ const allUsers = await fetch("/api/users?limit=100000");
531
552
  // This makes builds slow and outputs huge HTML
532
553
  ```
533
554
 
@@ -547,7 +568,7 @@ com.innerHTML = `
547
568
  ```html
548
569
  <!-- ✅ Share styles via CSS -->
549
570
  <!-- Don't inline repeated styles in every component -->
550
- <link rel="stylesheet" href="/styles/components.css">
571
+ <link rel="stylesheet" href="/styles/components.css" />
551
572
  ```
552
573
 
553
574
  **❌ DON'T:**
@@ -591,14 +612,14 @@ com.innerHTML = `<div class="card">${content}</div>`;
591
612
  // ✅ Escape HTML in user content
592
613
  function escapeHtml(unsafe: string): string {
593
614
  return unsafe
594
- .replace(/&/g, '&amp;')
595
- .replace(/</g, '&lt;')
596
- .replace(/>/g, '&gt;')
597
- .replace(/"/g, '&quot;')
598
- .replace(/'/g, '&#039;');
615
+ .replace(/&/g, "&amp;")
616
+ .replace(/</g, "&lt;")
617
+ .replace(/>/g, "&gt;")
618
+ .replace(/"/g, "&quot;")
619
+ .replace(/'/g, "&#039;");
599
620
  }
600
621
 
601
- const userComment = com.getAttribute('comment') || '';
622
+ const userComment = com.getAttribute("comment") || "";
602
623
  com.innerHTML = `<p>${escapeHtml(userComment)}</p>`;
603
624
  ```
604
625
 
@@ -607,14 +628,14 @@ com.innerHTML = `<p>${escapeHtml(userComment)}</p>`;
607
628
  function isSafeUrl(url: string): boolean {
608
629
  try {
609
630
  const parsed = new URL(url);
610
- return ['http:', 'https:'].includes(parsed.protocol);
631
+ return ["http:", "https:"].includes(parsed.protocol);
611
632
  } catch {
612
633
  return false;
613
634
  }
614
635
  }
615
636
 
616
- const url = com.getAttribute('href') || '#';
617
- const safeUrl = isSafeUrl(url) ? url : '#';
637
+ const url = com.getAttribute("href") || "#";
638
+ const safeUrl = isSafeUrl(url) ? url : "#";
618
639
  com.innerHTML = `<a href="${safeUrl}">Link</a>`;
619
640
  ```
620
641
 
@@ -622,13 +643,13 @@ com.innerHTML = `<a href="${safeUrl}">Link</a>`;
622
643
 
623
644
  ```typescript
624
645
  // ❌ Raw user input
625
- const userInput = com.getAttribute('text') || '';
646
+ const userInput = com.getAttribute("text") || "";
626
647
  com.innerHTML = userInput; // XSS vulnerability!
627
648
  ```
628
649
 
629
650
  ```typescript
630
651
  // ❌ Unvalidated URLs
631
- const url = com.getAttribute('href') || '';
652
+ const url = com.getAttribute("href") || "";
632
653
  com.innerHTML = `<a href="${url}">Link</a>`;
633
654
  // Can be exploited: <my-link href="javascript:alert('xss')">
634
655
  ```
@@ -639,19 +660,19 @@ com.innerHTML = `<a href="${url}">Link</a>`;
639
660
 
640
661
  ```typescript
641
662
  // ✅ Validate and sanitize
642
- const count = parseInt(com.getAttribute('count') || '5');
663
+ const count = parseInt(com.getAttribute("count") || "5");
643
664
  if (isNaN(count) || count < 0 || count > 100) {
644
- throw new Error('Invalid count: must be 0-100');
665
+ throw new Error("Invalid count: must be 0-100");
645
666
  }
646
667
  ```
647
668
 
648
669
  ```typescript
649
670
  // ✅ Whitelist allowed values
650
- const validTypes = ['info', 'warning', 'error', 'success'];
651
- const type = com.getAttribute('type') || 'info';
671
+ const validTypes = ["info", "warning", "error", "success"];
672
+ const type = com.getAttribute("type") || "info";
652
673
 
653
674
  if (!validTypes.includes(type)) {
654
- throw new Error(`Invalid type. Allowed: ${validTypes.join(', ')}`);
675
+ throw new Error(`Invalid type. Allowed: ${validTypes.join(", ")}`);
655
676
  }
656
677
  ```
657
678
 
@@ -665,7 +686,7 @@ if (!validTypes.includes(type)) {
665
686
 
666
687
  ```typescript
667
688
  // ✅ Fail fast with clear messages
668
- const required = com.getAttribute('data');
689
+ const required = com.getAttribute("data");
669
690
  if (!required) {
670
691
  throw new Error('Attribute "data" is required for data-table component');
671
692
  }
@@ -674,11 +695,11 @@ if (!required) {
674
695
  ```typescript
675
696
  // ✅ Provide context
676
697
  try {
677
- const data = JSON.parse(com.getAttribute('json') || '{}');
698
+ const data = JSON.parse(com.getAttribute("json") || "{}");
678
699
  } catch (error) {
679
700
  throw new Error(
680
701
  `Failed to parse JSON in component. ` +
681
- `Attribute value: ${com.getAttribute('json')}`
702
+ `Attribute value: ${com.getAttribute("json")}`,
682
703
  );
683
704
  }
684
705
  ```
@@ -687,16 +708,16 @@ try {
687
708
 
688
709
  ```typescript
689
710
  // ❌ Silent failures
690
- const data = com.getAttribute('data');
711
+ const data = com.getAttribute("data");
691
712
  if (!data) {
692
- com.innerHTML = ''; // Component just disappears, unclear why
713
+ com.innerHTML = ""; // Component just disappears, unclear why
693
714
  return;
694
715
  }
695
716
  ```
696
717
 
697
718
  ```typescript
698
719
  // ❌ Generic errors
699
- throw new Error('Something went wrong'); // Not helpful
720
+ throw new Error("Something went wrong"); // Not helpful
700
721
  ```
701
722
 
702
723
  ### Pre-rendering Errors
@@ -707,11 +728,11 @@ throw new Error('Something went wrong'); // Not helpful
707
728
  // ✅ Graceful degradation
708
729
  async function fetchData() {
709
730
  try {
710
- const response = await fetch('https://api.example.com/data');
731
+ const response = await fetch("https://api.example.com/data");
711
732
  if (!response.ok) throw new Error(`HTTP ${response.status}`);
712
733
  return await response.json();
713
734
  } catch (error) {
714
- console.warn('API fetch failed, using fallback:', error);
735
+ console.warn("API fetch failed, using fallback:", error);
715
736
  return { items: [] }; // Fallback data
716
737
  }
717
738
  }
@@ -726,7 +747,7 @@ try {
726
747
  const response = await fetch(url, { signal: controller.signal });
727
748
  return await response.json();
728
749
  } catch (error) {
729
- console.error('Request timeout or failed');
750
+ console.error("Request timeout or failed");
730
751
  return fallbackData;
731
752
  }
732
753
  ```
@@ -782,6 +803,7 @@ test -f web/index.js || exit 1
782
803
  Components generate HTML at build time. For runtime interactivity, use your regular `.ts` files:
783
804
 
784
805
  **Static button component:**
806
+
785
807
  ```html
786
808
  <!-- button-counter.com.html -->
787
809
  <button class="counter-button" data-count="0">
@@ -790,13 +812,14 @@ Components generate HTML at build time. For runtime interactivity, use your regu
790
812
  ```
791
813
 
792
814
  **Add interactivity in browser:**
815
+
793
816
  ```typescript
794
817
  // index.ts - runs in browser
795
- document.querySelectorAll('.counter-button').forEach(btn => {
818
+ document.querySelectorAll(".counter-button").forEach((btn) => {
796
819
  let count = 0;
797
- btn.addEventListener('click', () => {
820
+ btn.addEventListener("click", () => {
798
821
  count++;
799
- const span = btn.querySelector('.count');
822
+ const span = btn.querySelector(".count");
800
823
  if (span) span.textContent = count.toString();
801
824
  });
802
825
  });
@@ -820,7 +843,7 @@ page → layout → section → card → header → icon → svg (7 levels!)
820
843
 
821
844
  ```typescript
822
845
  // user-badge.com.ts
823
- const value = com.getAttribute('value') || '0';
846
+ const value = com.getAttribute("value") || "0";
824
847
  const num = parseInt(value, 10);
825
848
 
826
849
  if (isNaN(num)) {
@@ -837,14 +860,14 @@ Always escape dynamic content to prevent XSS:
837
860
  ```typescript
838
861
  function escapeHtml(unsafe: string): string {
839
862
  return unsafe
840
- .replace(/&/g, '&amp;')
841
- .replace(/</g, '&lt;')
842
- .replace(/>/g, '&gt;')
843
- .replace(/"/g, '&quot;')
844
- .replace(/'/g, '&#039;');
863
+ .replace(/&/g, "&amp;")
864
+ .replace(/</g, "&lt;")
865
+ .replace(/>/g, "&gt;")
866
+ .replace(/"/g, "&quot;")
867
+ .replace(/'/g, "&#039;");
845
868
  }
846
869
 
847
- const text = com.getAttribute('text') || '';
870
+ const text = com.getAttribute("text") || "";
848
871
  com.innerHTML = `<p>${escapeHtml(text)}</p>`;
849
872
  ```
850
873
 
@@ -890,6 +913,7 @@ rsync -avz web/ server:/var/www/html/
890
913
  ### Build-Time vs Runtime
891
914
 
892
915
  **Build time (.pre.ts, .com.ts):**
916
+
893
917
  - Runs in Bun runtime (not browser)
894
918
  - Full TypeScript support
895
919
  - Can use `fetch()`, `Bun.file()`, Node APIs
@@ -897,6 +921,7 @@ rsync -avz web/ server:/var/www/html/
897
921
  - Manipulate DOM with full access to `document`
898
922
 
899
923
  **Runtime (browser):**
924
+
900
925
  - Your compiled `.js` files run
901
926
  - Standard browser environment
902
927
  - Use any browser APIs, libraries, patterns
@@ -908,16 +933,17 @@ Tkeron **does NOT bundle** npm packages into browser code.
908
933
 
909
934
  ```typescript
910
935
  // .pre.ts or .com.ts - Build time
911
- import _ from 'lodash'; // ✅ Works - runs in Bun during build
936
+ import _ from "lodash"; // ✅ Works - runs in Bun during build
912
937
  const result = _.chunk([1, 2, 3], 2);
913
938
  ```
914
939
 
915
940
  ```typescript
916
- // index.ts - Browser runtime
917
- import _ from 'lodash'; // ❌ Won't work - not bundled
941
+ // index.ts - Browser runtime
942
+ import _ from "lodash"; // ❌ Won't work - not bundled
918
943
  ```
919
944
 
920
945
  **Solutions:**
946
+
921
947
  - Use CDN: `<script src="https://cdn.jsdelivr.net/npm/lodash"></script>`
922
948
  - Use a bundler alongside Tkeron (Vite, esbuild, etc.)
923
949
  - Write vanilla JavaScript (often simpler than you think)
@@ -998,7 +1024,7 @@ websrc/features/
998
1024
 
999
1025
  ```typescript
1000
1026
  // index.pre.ts
1001
- const isProd = process.env.NODE_ENV === 'production';
1027
+ const isProd = process.env.NODE_ENV === "production";
1002
1028
 
1003
1029
  if (!isProd) {
1004
1030
  // Development-only code