jskelet 0.1.1 → 0.1.2

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 (64) hide show
  1. package/AGENTS.md +5 -0
  2. package/CHANGELOG.md +63 -0
  3. package/README.md +21 -7
  4. package/bin/jskelet.mjs +6 -6
  5. package/docs/03-routing.md +48 -9
  6. package/docs/04-render-ve-sablonlar.md +2 -2
  7. package/docs/05-islands.md +59 -6
  8. package/docs/06-cache.md +39 -7
  9. package/docs/07-yapilandirma.md +51 -1
  10. package/docs/08-build.md +4 -4
  11. package/docs/09-dev-araclari.md +5 -0
  12. package/docs/12-panel-ve-oturum.md +384 -0
  13. package/docs/README.md +25 -2
  14. package/docs/en/01-getting-started.md +292 -0
  15. package/docs/en/02-architecture.md +305 -0
  16. package/docs/en/03-routing.md +493 -0
  17. package/docs/en/04-rendering.md +504 -0
  18. package/docs/en/05-islands.md +492 -0
  19. package/docs/en/06-caching.md +454 -0
  20. package/docs/en/07-configuration.md +736 -0
  21. package/docs/en/08-build.md +383 -0
  22. package/docs/en/09-dev-tools.md +314 -0
  23. package/docs/en/10-deployment.md +332 -0
  24. package/docs/en/11-migration.md +360 -0
  25. package/docs/en/12-dashboards-and-sessions.md +392 -0
  26. package/docs/en/README.md +112 -0
  27. package/package.json +4 -2
  28. package/src/build/build.mjs +1 -1
  29. package/src/build/tasks/client.mjs +2 -2
  30. package/src/build/tasks/fonts.mjs +3 -3
  31. package/src/build/tasks/icons.mjs +1 -1
  32. package/src/build/tasks/images.mjs +2 -2
  33. package/src/client/devtools/overlay.js +196 -164
  34. package/src/client/devtools/report.js +96 -96
  35. package/src/client/form.js +192 -0
  36. package/src/client/index.js +10 -1
  37. package/src/client/registry.js +78 -4
  38. package/src/client/swap.js +188 -0
  39. package/src/config/defaults.js +34 -0
  40. package/src/config/index.js +68 -13
  41. package/src/config/pattern.js +1 -1
  42. package/src/dev-server.mjs +1 -1
  43. package/src/http/control-flow.js +16 -1
  44. package/src/http/cookies.js +257 -0
  45. package/src/http/request-context.js +162 -0
  46. package/src/index.js +19 -2
  47. package/src/init.mjs +32 -31
  48. package/src/log.mjs +8 -2
  49. package/src/logo.png +0 -0
  50. package/src/runtime/alias-hooks.mjs +1 -1
  51. package/src/server/assets.js +1 -1
  52. package/src/server/create-app.js +12 -4
  53. package/src/server/dev/devtools.js +6 -2
  54. package/src/server/dev/version-check.mjs +139 -0
  55. package/src/server/head-hints.js +1 -1
  56. package/src/server/html-cache.js +10 -4
  57. package/src/server/middleware/csrf.js +134 -0
  58. package/src/server/prewarm.js +6 -6
  59. package/src/server/render.js +199 -16
  60. package/src/server/router.js +14 -7
  61. package/src/server/status-page.js +1 -1
  62. package/src/version.mjs +9 -4
  63. package/src/views/components/loader.js +1 -1
  64. package/src/views/helpers/tags.js +53 -1
@@ -117,7 +117,7 @@ function captureErrors() {
117
117
  const target = /** @type {HTMLElement | null} */ (event.target);
118
118
  if (target && target !== /** @type {any} */ (window)) {
119
119
  const url = target.getAttribute?.("src") ?? target.getAttribute?.("href");
120
- pushError("error", `Kaynak yüklenemedi: ${url ?? target.tagName}`, {
120
+ pushError("error", `Resource failed to load: ${url ?? target.tagName}`, {
121
121
  source: "resource",
122
122
  });
123
123
  return;
@@ -799,26 +799,26 @@ const TRASH_ICON = `<svg viewBox="0 0 16 16"><path d="M2.5 4.5h11M6 4.5V3h4v1.5M
799
799
  const SKELETON = `
800
800
  <div class="root">
801
801
  <div class="backdrop" data-action="backdrop" hidden>
802
- <div class="panel" role="dialog" aria-label="JSkelet dev araçları">
802
+ <div class="panel" role="dialog" aria-label="JSkelet dev tools">
803
803
  <div class="head">
804
804
  <img src="${BASE}/logo.png" alt="">
805
805
  <span class="title">JSkelet Dev</span>
806
806
  <span class="pill"><span class="dot"></span><span data-part="pill"></span></span>
807
807
  <span class="spacer"></span>
808
808
  <span class="kbd">Alt+D</span>
809
- <button class="btn icon" data-action="toggle" title="Kapat (Esc)">✕</button>
809
+ <button class="btn icon" data-action="toggle" title="Close (Esc)">✕</button>
810
810
  </div>
811
811
  <div class="rail">
812
- <button class="tab" data-tab="errors">${TAB_ICONS.errors} Hatalar<span class="chip" data-part="chip" hidden></span></button>
813
- <button class="tab" data-tab="perf">${TAB_ICONS.perf} Performans</button>
814
- <button class="tab" data-tab="server">${TAB_ICONS.server} Sunucu</button>
812
+ <button class="tab" data-tab="errors">${TAB_ICONS.errors} Errors<span class="chip" data-part="chip" hidden></span></button>
813
+ <button class="tab" data-tab="perf">${TAB_ICONS.perf} Performance</button>
814
+ <button class="tab" data-tab="server">${TAB_ICONS.server} Server<span class="chip neutral" data-part="version-chip" hidden>update</span></button>
815
815
  <button class="tab" data-tab="warm">${TAB_ICONS.warm} Prewarming<span class="chip neutral" data-part="warm-chip" hidden></span></button>
816
816
  <div class="rail-foot">
817
- <button class="tab" data-tab="about">${TAB_ICONS.about} Hakkında</button>
817
+ <button class="tab" data-tab="about">${TAB_ICONS.about} About</button>
818
818
  <div class="divider"></div>
819
- <button class="btn wide" data-action="reload">${REFRESH_ICON} Sayfayı yenile</button>
820
- <a class="btn wide" href="${BASE}/report" target="_blank" rel="noreferrer">${SCOPE_ICON} Detaylı İncele</a>
821
- <button class="btn wide danger" data-action="clear">${TRASH_ICON} Kayıtları temizle</button>
819
+ <button class="btn wide" data-action="reload">${REFRESH_ICON} Reload page</button>
820
+ <a class="btn wide" href="${BASE}/report" target="_blank" rel="noreferrer">${SCOPE_ICON} Inspect in detail</a>
821
+ <button class="btn wide danger" data-action="clear">${TRASH_ICON} Clear records</button>
822
822
  </div>
823
823
  </div>
824
824
  <div class="body"></div>
@@ -830,7 +830,7 @@ const SKELETON = `
830
830
  <span data-part="warm-text"></span>
831
831
  </span>
832
832
  <button class="bubble" data-action="toggle">
833
- <img src="${BASE}/logo.png" alt="JSkelet dev araçları">
833
+ <img src="${BASE}/logo.png" alt="JSkelet dev tools">
834
834
  <span class="badge" hidden></span>
835
835
  </button>
836
836
  </div>
@@ -892,7 +892,7 @@ function pathLink(value) {
892
892
  const text = escapeHtml(value);
893
893
  if (!value?.startsWith("/")) return text;
894
894
 
895
- return `<a href="${escapeHtml(value)}" target="_blank" rel="noreferrer" title="Yeni sekmede aç: ${text}">${text}</a>`;
895
+ return `<a href="${escapeHtml(value)}" target="_blank" rel="noreferrer" title="Open in new tab: ${text}">${text}</a>`;
896
896
  }
897
897
 
898
898
  /** @param {string} value */
@@ -938,7 +938,7 @@ function ratio(value, ceiling) {
938
938
  */
939
939
  const clipboard = new Map();
940
940
 
941
- /** Kısa süreli "kopyalandı" geri bildirimi için. */
941
+ /** Kısa süreli "Copied" geri bildirimi için. */
942
942
  let copiedKey = null;
943
943
 
944
944
  /**
@@ -958,7 +958,7 @@ function asText(item) {
958
958
 
959
959
  /** @param {{ id?: number, level: string, message: string, stack?: string | null, source?: string, at: number }[]} list */
960
960
  function errorList(list, scope) {
961
- if (!list.length) return `<div class="empty">Kayıt yok.</div>`;
961
+ if (!list.length) return `<div class="empty">No records.</div>`;
962
962
  return list
963
963
  .map((item) => {
964
964
  const key = `${scope}:${item.id ?? item.at}`;
@@ -968,14 +968,14 @@ function errorList(list, scope) {
968
968
  return `<div class="item ${item.level === "warn" ? "warn" : ""}">
969
969
  <div class="meta">
970
970
  <span class="tag">${escapeHtml(item.source ?? "server")}</span>
971
- <span>${new Date(item.at).toLocaleTimeString("tr-TR")}</span>
971
+ <span>${new Date(item.at).toLocaleTimeString()}</span>
972
972
  <span class="spacer"></span>
973
973
  ${copyButton(key)}
974
974
  </div>
975
975
  <div class="msg">${escapeHtml(item.message)}</div>
976
976
  ${
977
977
  item.stack
978
- ? `<button class="link" data-action="stack" data-key="${escapeHtml(key)}">${shown ? "▾ yığını gizle" : "▸ yığını göster"}</button>
978
+ ? `<button class="link" data-action="stack" data-key="${escapeHtml(key)}">${shown ? "▾ hide stack" : "▸ show stack"}</button>
979
979
  ${shown ? `<pre>${escapeHtml(item.stack)}</pre>` : ""}`
980
980
  : ""
981
981
  }
@@ -994,7 +994,7 @@ const CHECK_ICON = `<svg viewBox="0 0 16 16" aria-hidden="true"><path d="M3 8.5l
994
994
  function copyButton(key, label = "") {
995
995
  const done = copiedKey === key;
996
996
  return `<button class="copy ${done ? "done" : ""}" data-action="copy" data-key="${escapeHtml(key)}"
997
- title="${done ? "Kopyalandı" : "Kopyala"}">${done ? CHECK_ICON : COPY_ICON}${
997
+ title="${done ? "Copied" : "Copy"}">${done ? CHECK_ICON : COPY_ICON}${
998
998
  label ? `<span>${escapeHtml(label)}</span>` : ""
999
999
  }</button>`;
1000
1000
  }
@@ -1028,16 +1028,16 @@ function errorsTab() {
1028
1028
  ${lede(
1029
1029
  errorCount ? "bad" : total ? "mid" : "good",
1030
1030
  errorCount
1031
- ? `<strong>${errorCount} hata</strong> yakalandı${total - errorCount ? `, yanında ${total - errorCount} uyarı var` : ""}. En yenisi üstte; yığın izini açıp kaydı tek tıkla kopyalayabilirsin.`
1031
+ ? `<strong>${errorCount} errors</strong> captured${total - errorCount ? `, plus ${total - errorCount} warnings` : ""}. Newest first; expand the stack trace and copy a record with one click.`
1032
1032
  : total
1033
- ? `Hata yok, <strong>${total} uyarı</strong> var. Uyarılar genelde eksik veri ya da port edilmemiş island'lardan gelir.`
1034
- : "Bu oturumda tarayıcı ya da sunucu tarafında hiç hata görülmedi.",
1033
+ ? `No errors, <strong>${total} warnings</strong>. Warnings usually come from missing data or islands that were never ported.`
1034
+ : "No browser or server side errors were seen in this session.",
1035
1035
  )}
1036
- <h4>Tarayıcı · ${clientErrors.length}${
1037
- total ? `<span class="head-action">${copyButton("all", "tümünü kopyala")}</span>` : ""
1036
+ <h4>Browser · ${clientErrors.length}${
1037
+ total ? `<span class="head-action">${copyButton("all", "copy all")}</span>` : ""
1038
1038
  }</h4>
1039
1039
  ${errorList(clientErrors, "client")}
1040
- <h4>Sunucu · ${server.length}</h4>
1040
+ <h4>Server · ${server.length}</h4>
1041
1041
  ${errorList(
1042
1042
  server.map((item) => ({ ...item, source: "server" })),
1043
1043
  "server",
@@ -1063,7 +1063,7 @@ function perfTab() {
1063
1063
  `LCP: ${ms(metrics.lcp)} CLS: ${metrics.cls.toFixed(3)} INP: ${ms(metrics.inp)}`,
1064
1064
  `FCP: ${ms(metrics.fcp)} TTFB: ${ms(metrics.ttfb)}`,
1065
1065
  `Long task: ${metrics.longTasks} Blocking: ${ms(metrics.blocking)}`,
1066
- `İstek: ${resources.count} Transfer: ${kb(resources.bytes)}`,
1066
+ `Requests: ${resources.count} Transfer: ${kb(resources.bytes)}`,
1067
1067
  `Island: ${islands.ready}/${islands.total}`,
1068
1068
  ].join("\n"),
1069
1069
  );
@@ -1078,11 +1078,11 @@ function perfTab() {
1078
1078
  return `
1079
1079
  ${lede(
1080
1080
  tone,
1081
- `Sayfa <strong>${ms(metrics.lcp)}</strong>'de en büyük içeriği boyadı, düzen kayması <strong>${metrics.cls.toFixed(3)}</strong>. ` +
1082
- `Ana iş parçacığı ${metrics.longTasks} uzun görevle <strong>${ms(metrics.blocking)}</strong> bloke oldu; ` +
1083
- `${resources.count} istekte <strong>${kb(resources.bytes)}</strong> indirildi ve ${islands.total} island'ın ${islands.ready}'i bağlandı.`,
1081
+ `The page painted its largest content at <strong>${ms(metrics.lcp)}</strong>, layout shift is <strong>${metrics.cls.toFixed(3)}</strong>. ` +
1082
+ `The main thread was blocked <strong>${ms(metrics.blocking)}</strong> by ${metrics.longTasks} long tasks; ` +
1083
+ `<strong>${kb(resources.bytes)}</strong> was downloaded over ${resources.count} requests and ${islands.ready} of ${islands.total} islands mounted.`,
1084
1084
  )}
1085
- <h4>Core Web Vitals<span class="head-action">${copyButton("perf", "özeti kopyala")}</span></h4>
1085
+ <h4>Core Web Vitals<span class="head-action">${copyButton("perf", "copy summary")}</span></h4>
1086
1086
  ${metricGrid([
1087
1087
  { label: "LCP", value: ms(metrics.lcp), tone: grade(metrics.lcp, [2500, 4000]), ratio: ratio(metrics.lcp, 4000) },
1088
1088
  { label: "CLS", value: metrics.cls.toFixed(3), tone: grade(metrics.cls * 1000, [100, 250]), ratio: metrics.cls / 0.25 },
@@ -1090,7 +1090,7 @@ function perfTab() {
1090
1090
  { label: "FCP", value: ms(metrics.fcp), tone: grade(metrics.fcp, [1800, 3000]), ratio: ratio(metrics.fcp, 3000) },
1091
1091
  { label: "TTFB", value: ms(metrics.ttfb), tone: grade(metrics.ttfb, [800, 1800]), ratio: ratio(metrics.ttfb, 1800) },
1092
1092
  ])}
1093
- <h4>Yükleme</h4>
1093
+ <h4>Loading</h4>
1094
1094
  ${metricGrid([
1095
1095
  { label: "DOMContentLoaded", value: ms(metrics.dcl) },
1096
1096
  { label: "Load", value: ms(metrics.load) },
@@ -1098,9 +1098,9 @@ function perfTab() {
1098
1098
  { label: "Blocking", value: ms(metrics.blocking), tone: grade(metrics.blocking, [200, 600]), ratio: metrics.blocking / 600 },
1099
1099
  ...(memory ? [{ label: "JS heap", value: kb(memory) }] : []),
1100
1100
  ])}
1101
- <h4>Kaynaklar</h4>
1101
+ <h4>Resources</h4>
1102
1102
  ${metricGrid([
1103
- { label: "İstek", value: String(resources.count) },
1103
+ { label: "Requests", value: String(resources.count) },
1104
1104
  { label: "Transfer", value: kb(resources.bytes) },
1105
1105
  { label: "Island", value: `${islands.ready}/${islands.total}`, ratio: islands.total ? islands.ready / islands.total : null, tone: "good" },
1106
1106
  ])}
@@ -1109,7 +1109,7 @@ function perfTab() {
1109
1109
  .map(
1110
1110
  ([kind, info]) => `<div class="row">
1111
1111
  <span class="tag">${escapeHtml(kind)}</span>
1112
- <span class="path">${info.count} istek</span>
1112
+ <span class="path">${info.count} requests</span>
1113
1113
  <span class="hint">${Math.round((info.bytes / heaviest) * 100)}%</span>
1114
1114
  <span>${kb(info.bytes)}</span>
1115
1115
  </div>`,
@@ -1119,9 +1119,30 @@ function perfTab() {
1119
1119
  `;
1120
1120
  }
1121
1121
 
1122
+ /**
1123
+ * Yeni bir sürüm varsa yükseltme satırı. Kayıt defterine ulaşılamadıysa
1124
+ * (`latest` boş) hiçbir şey basılmaz: dev panelinde "bilinmiyor" satırı
1125
+ * gürültüden başka bir şey değil.
1126
+ */
1127
+ function versionNotice() {
1128
+ const version = serverStats?.version;
1129
+ if (!version?.outdated) return "";
1130
+
1131
+ clipboard.set("upgrade", `npm install jskelet@${version.latest}`);
1132
+
1133
+ return lede(
1134
+ "mid",
1135
+ `JSkelet <strong>${escapeHtml(version.latest)}</strong> is out; this project runs ` +
1136
+ `<strong>${escapeHtml(version.current)}</strong>. Upgrade with ` +
1137
+ `<code>npm install jskelet@latest</code> and skim the changelog for breaking changes. ` +
1138
+ copyButton("upgrade", "copy command"),
1139
+ );
1140
+ }
1141
+
1122
1142
  function serverTab() {
1123
- if (!serverStats) return `<div class="empty">Sunucu istatistikleri bekleniyor…</div>`;
1143
+ if (!serverStats) return `<div class="empty">Waiting for server stats…</div>`;
1124
1144
 
1145
+ const version = serverStats.version;
1125
1146
  const requests = serverStats.requests ?? [];
1126
1147
  const slowest = requests.reduce((max, item) => Math.max(max, item.ms), 0);
1127
1148
  const average = requests.length
@@ -1144,24 +1165,30 @@ function serverTab() {
1144
1165
  ${lede(
1145
1166
  offline ? "mid" : grade(average, [150, 500]) || "good",
1146
1167
  offline
1147
- ? "Sunucuya şu an ulaşılamıyor; yeniden başlatılmayı bekliyoruz. Eldeki kayıtlar korunuyor."
1148
- : `Sunucu <strong>${Math.round(serverStats.uptime ?? 0)} sn</strong> ayakta, ` +
1149
- `son ${requests.length} isteği ortalama <strong>${ms(average)}</strong> ile yanıtladı ` +
1150
- `(en yavaş ${ms(slowest)}). Süreç ${kb(serverStats.memory?.rss ?? 0)} bellek kullanıyor` +
1151
- `${restarts ? ` ve bu oturumda ${restarts} kez yeniden başladı` : ""}.`,
1168
+ ? "The server is unreachable right now; we are waiting for it to restart. Existing records are kept."
1169
+ : `The server has been up <strong>${Math.round(serverStats.uptime ?? 0)} s</strong>, ` +
1170
+ `answering the last ${requests.length} requests in <strong>${ms(average)}</strong> on average ` +
1171
+ `(slowest ${ms(slowest)}). The process uses ${kb(serverStats.memory?.rss ?? 0)} of memory` +
1172
+ `${restarts ? ` and restarted ${restarts} times in this session` : ""}.`,
1152
1173
  )}
1153
- <h4>Süreç${
1174
+ ${versionNotice()}
1175
+ <h4>Process${
1154
1176
  requests.length
1155
- ? `<span class="head-action">${copyButton("requests", "istekleri kopyala")}</span>`
1177
+ ? `<span class="head-action">${copyButton("requests", "copy requests")}</span>`
1156
1178
  : ""
1157
1179
  }</h4>
1158
1180
  ${metricGrid([
1181
+ {
1182
+ label: "JSkelet",
1183
+ value: version?.current ?? "—",
1184
+ tone: version?.outdated ? "mid" : "",
1185
+ },
1159
1186
  { label: "Node", value: serverStats.node ?? "—" },
1160
- { label: "Uptime", value: `${Math.round(serverStats.uptime ?? 0)} sn` },
1187
+ { label: "Uptime", value: `${Math.round(serverStats.uptime ?? 0)} s` },
1161
1188
  { label: "RSS", value: kb(serverStats.memory?.rss ?? 0) },
1162
1189
  { label: "Heap", value: kb(serverStats.memory?.heapUsed ?? 0) },
1163
- { label: "Ort. render", value: ms(average), tone: grade(average, [150, 500]) },
1164
- { label: "En yavaş", value: ms(slowest), tone: grade(slowest, [300, 1000]) },
1190
+ { label: "Avg. render", value: ms(average), tone: grade(average, [150, 500]) },
1191
+ { label: "Slowest", value: ms(slowest), tone: grade(slowest, [300, 1000]) },
1165
1192
  { label: "Restart", value: String(restarts), tone: offline ? "mid" : "" },
1166
1193
  ...(serverStats.prewarm?.total
1167
1194
  ? [
@@ -1174,7 +1201,7 @@ function serverTab() {
1174
1201
  ]
1175
1202
  : []),
1176
1203
  ])}
1177
- <h4>Son istekler</h4>
1204
+ <h4>Recent requests</h4>
1178
1205
  ${
1179
1206
  requests.length
1180
1207
  ? `<div class="rows">${requests
@@ -1187,7 +1214,7 @@ function serverTab() {
1187
1214
  </div>`,
1188
1215
  )
1189
1216
  .join("")}</div>`
1190
- : `<div class="empty">Henüz istek yok.</div>`
1217
+ : `<div class="empty">No requests yet.</div>`
1191
1218
  }
1192
1219
  `;
1193
1220
  }
@@ -1198,7 +1225,7 @@ function warmTab() {
1198
1225
  if (!warm?.total) {
1199
1226
  return `
1200
1227
  ${warmToolbar(false)}
1201
- <div class="empty">Önbellek ısıtması henüz başlamadı. Dev'de sunucu bir süre sakin kalınca tetiklenir; <code>PREWARM=0</code> ile kapatılabilir.</div>
1228
+ <div class="empty">Cache prewarming has not started yet. In dev it triggers once the server stays idle for a while; disable it with <code>PREWARM=0</code>.</div>
1202
1229
  `;
1203
1230
  }
1204
1231
 
@@ -1220,7 +1247,7 @@ function warmTab() {
1220
1247
  clipboard.set(
1221
1248
  "warm",
1222
1249
  [
1223
- `Prewarm ${warm.ok}/${warm.total} · ${warm.failed} hata · ${(elapsed / 1000).toFixed(1)}s · ${kb(bytes)}`,
1250
+ `Prewarm ${warm.ok}/${warm.total} · ${warm.failed} errors · ${(elapsed / 1000).toFixed(1)}s · ${kb(bytes)}`,
1224
1251
  ...entries.map(
1225
1252
  (item) =>
1226
1253
  `${String(item.status).padStart(3)} ${String(item.ms).padStart(5)}ms ${String(
@@ -1235,38 +1262,38 @@ function warmTab() {
1235
1262
  ${lede(
1236
1263
  warm.active ? "mid" : warm.failed ? "bad" : "good",
1237
1264
  warm.active
1238
- ? `Isıtma sürüyor: <strong>${warm.done}/${warm.total}</strong> sayfa denendi, şu ana kadar ${kb(bytes)} render edildi.`
1265
+ ? `Prewarming in progress: <strong>${warm.done}/${warm.total}</strong> pages tried, ${kb(bytes)} rendered so far.`
1239
1266
  : warm.failed
1240
- ? `<strong>${warm.ok}/${warm.total}</strong> sayfa ısındı, <strong>${warm.failed} sayfa hata verdi</strong>. Hatalı yollar aşağıda en üstte listeli.`
1241
- : `Tüm <strong>${warm.total}</strong> sayfa ${(elapsed / 1000).toFixed(1)} saniyede ısıtıldı, toplam <strong>${kb(bytes)}</strong> HTML üretildi. İlk ziyaretçi soğuk render beklemeyecek.`,
1267
+ ? `<strong>${warm.ok}/${warm.total}</strong> pages warmed, <strong>${warm.failed} pages failed</strong>. The failing paths are listed at the top below.`
1268
+ : `All <strong>${warm.total}</strong> pages were warmed in ${(elapsed / 1000).toFixed(1)} seconds, producing <strong>${kb(bytes)}</strong> of HTML in total. The first visitor will not wait for a cold render.`,
1242
1269
  )}
1243
- <h4>Özet<span class="head-action">${copyButton("warm", "listeyi kopyala")}</span></h4>
1270
+ <h4>Summary<span class="head-action">${copyButton("warm", "copy list")}</span></h4>
1244
1271
  ${metricGrid([
1245
- { label: "Denenen", value: `${warm.done}/${warm.total}`, ratio: warm.done / warm.total, tone: "good" },
1246
- { label: "Başarılı", value: String(warm.ok), tone: "good" },
1247
- { label: "Hata", value: String(warm.failed), tone: warm.failed ? "bad" : "" },
1248
- { label: "Süre", value: `${(elapsed / 1000).toFixed(1)} sn` },
1249
- { label: "Toplam HTML", value: kb(bytes) },
1250
- { label: "Ort. render", value: ms(average), tone: grade(average, [150, 500]) },
1251
- { label: "En yavaş", value: ms(slowest?.ms ?? null), tone: grade(slowest?.ms ?? null, [300, 1000]) },
1252
- { label: "Cache'ten", value: `${cached}/${entries.length}` },
1272
+ { label: "Tried", value: `${warm.done}/${warm.total}`, ratio: warm.done / warm.total, tone: "good" },
1273
+ { label: "Succeeded", value: String(warm.ok), tone: "good" },
1274
+ { label: "Failed", value: String(warm.failed), tone: warm.failed ? "bad" : "" },
1275
+ { label: "Duration", value: `${(elapsed / 1000).toFixed(1)} s` },
1276
+ { label: "Total HTML", value: kb(bytes) },
1277
+ { label: "Avg. render", value: ms(average), tone: grade(average, [150, 500]) },
1278
+ { label: "Slowest", value: ms(slowest?.ms ?? null), tone: grade(slowest?.ms ?? null, [300, 1000]) },
1279
+ { label: "From cache", value: `${cached}/${entries.length}` },
1253
1280
  ])}
1254
1281
  ${
1255
1282
  failures.length
1256
- ? `<h4>Hatalar · ${failures.length}<span class="head-action">
1257
- <button class="btn mini" data-action="warm-retry" ${warm.active ? "disabled" : ""}>${REFRESH_ICON} Tekrar dene</button>
1283
+ ? `<h4>Errors · ${failures.length}<span class="head-action">
1284
+ <button class="btn mini" data-action="warm-retry" ${warm.active ? "disabled" : ""}>${REFRESH_ICON} Retry</button>
1258
1285
  </span></h4>
1259
1286
  <div class="rows">${failures.map(warmRow).join("")}</div>`
1260
1287
  : ""
1261
1288
  }
1262
- <h4>Denenen sayfalar · ${entries.length}</h4>
1289
+ <h4>Pages tried · ${entries.length}</h4>
1263
1290
  ${
1264
1291
  entries.length
1265
1292
  ? `<div class="rows">${[...entries]
1266
1293
  .sort((a, b) => b.ms - a.ms)
1267
1294
  .map(warmRow)
1268
1295
  .join("")}</div>`
1269
- : `<div class="empty">Henüz sonuç yok.</div>`
1296
+ : `<div class="empty">No results yet.</div>`
1270
1297
  }
1271
1298
  `;
1272
1299
  }
@@ -1278,7 +1305,7 @@ function warmTab() {
1278
1305
  function warmToolbar(active) {
1279
1306
  return `<div class="toolbar">
1280
1307
  <button class="btn mini" data-action="warm-run" ${active ? "disabled" : ""}>
1281
- ${REFRESH_ICON} ${active ? "Isıtma sürüyor…" : "Baştan prewarm başlat"}
1308
+ ${REFRESH_ICON} ${active ? "Prewarming…" : "Start prewarm from scratch"}
1282
1309
  </button>
1283
1310
  </div>`;
1284
1311
  }
@@ -1305,145 +1332,145 @@ const ABOUT = {
1305
1332
  framework: {
1306
1333
  label: "Framework",
1307
1334
  html: `
1308
- <p><strong>JSkelet</strong>, hazır bir meta-framework kullanmadan yazılmış
1309
- ince bir katman: Express üstünde EJS ile sunucu tarafı render, esbuild ile island
1310
- tabanlı istemci kodu ve token'lara bağlı bir tema sistemi.</p>
1335
+ <p><strong>JSkelet</strong> is a thin layer written without any off-the-shelf
1336
+ meta-framework: server side rendering with EJS on top of Express, island based
1337
+ client code via esbuild and a token driven theme system.</p>
1311
1338
  <div class="steps">
1312
- <div class="step"><span class="n">1</span><span><b>Sunucu render</b><span>Express + EJS sayfayı tamamen sunucuda üretir; sonuç HTML önbelleğinde tutulur, tekrar eden istekler milisaniyenin altında döner.</span></span></div>
1313
- <div class="step"><span class="n">2</span><span><b>Island'lar</b><span>Etkileşimli her parça <code>data-island</code> ile işaretlenir, modülü görünüm alanına girince yüklenir. Sayfanın tamamı hidrate edilmez.</span></span></div>
1314
- <div class="step"><span class="n">3</span><span><b>Build hattı</b><span>esbuild JS'i, Tailwind + LightningCSS stilleri, sharp görselleri, Phosphor ikonları derler; çıktı hash'lenip <code>manifest.json</code>'a yazılır.</span></span></div>
1315
- <div class="step"><span class="n">4</span><span><b>Dev döngüsü</b><span>Kaynaklar izlenir: CSS değişince stylesheet takas edilir, JS veya sunucu değişince tek seferlik tam yenileme yapılır.</span></span></div>
1316
- <div class="step"><span class="n">5</span><span><b>Gözlemlenebilirlik</b><span>Bu panel hataları, Web Vitals ölçümlerini ve sunucu isteklerini toplar; kayıtlar sunucu yeniden başlasa da korunur.</span></span></div>
1339
+ <div class="step"><span class="n">1</span><span><b>Server render</b><span>Express + EJS produce the page entirely on the server; the result is kept in the HTML cache, so repeat requests return in under a millisecond.</span></span></div>
1340
+ <div class="step"><span class="n">2</span><span><b>Islands</b><span>Every interactive piece is marked with <code>data-island</code> and its module loads once it enters the viewport. The whole page is never hydrated.</span></span></div>
1341
+ <div class="step"><span class="n">3</span><span><b>Build pipeline</b><span>esbuild compiles JS, Tailwind + LightningCSS the styles, sharp the images, Phosphor the icons; the output is hashed and written to <code>manifest.json</code>.</span></span></div>
1342
+ <div class="step"><span class="n">4</span><span><b>Dev loop</b><span>Sources are watched: on a CSS change the stylesheet is swapped, on a JS or server change a single full reload happens.</span></span></div>
1343
+ <div class="step"><span class="n">5</span><span><b>Observability</b><span>This panel collects errors, Web Vitals measurements and server requests; records survive a server restart.</span></span></div>
1317
1344
  </div>
1318
1345
  <div class="cards">
1319
- <div class="card"><b>Çalışma zamanı</b><span>Node + Express 5, ESM. Sayfa şablonları EJS, veri katmanı <code>lib/</code> altındaki servisler.</span></div>
1320
- <div class="card"><b>Hedef tarayıcılar</b><span>Chrome/Edge/Firefox 111+, Safari 16.4+. Modern CSS ve ESM doğrudan kullanılır, transpile yükü yok.</span></div>
1321
- <div class="card"><b>Bu panel</b><span>Yalnızca <code>npm run dev</code>'de yüklenir; layout script etiketini sadece development'ta basar, prod çıktısına girmez.</span></div>
1346
+ <div class="card"><b>Runtime</b><span>Node + Express 5, ESM. Page templates are EJS, the data layer is the services under <code>lib/</code>.</span></div>
1347
+ <div class="card"><b>Target browsers</b><span>Chrome/Edge/Firefox 111+, Safari 16.4+. Modern CSS and ESM are used directly, with no transpile overhead.</span></div>
1348
+ <div class="card"><b>This panel</b><span>Loaded only under <code>npm run dev</code>; the layout prints the script tag in development only, so it never reaches the production output.</span></div>
1322
1349
  </div>
1323
1350
  `,
1324
1351
  },
1325
1352
  request: {
1326
- label: "İstek akışı",
1353
+ label: "Request flow",
1327
1354
  html: `
1328
- <p>Bir sayfa isteğinin sunucuda izlediği yol. Her adım <code>server/</code> altında
1329
- ayrı bir dosyada durur; hiçbiri diğerinin içine gömülü değildir.</p>
1355
+ <p>The path a page request follows on the server. Every step lives in its own file
1356
+ under <code>server/</code>; none of them is buried inside another.</p>
1330
1357
  <div class="steps">
1331
- <div class="step"><span class="n">1</span><span><b>Route</b><span><code>server/routes/</code> URL'i eşler ve isteği ilgili controller'a verir.</span></span></div>
1332
- <div class="step"><span class="n">2</span><span><b>Controller</b><span><code>server/controllers/</code> gerekli veriyi <code>lib/</code> servislerinden toplar, şablona geçecek modeli hazırlar.</span></span></div>
1333
- <div class="step"><span class="n">3</span><span><b>Render</b><span><code>server/render.js</code> EJS şablonunu çalıştırır, layout'a manifest girdilerini ve head ipuçlarını basar.</span></span></div>
1334
- <div class="step"><span class="n">4</span><span><b>HTML önbelleği</b><span><code>server/html-cache.js</code> çıktıyı saklar; aynı URL tekrar istenirse şablon hiç çalıştırılmaz. Sunucu sekmesindeki <code>cached</code> etiketi bunu gösterir.</span></span></div>
1335
- <div class="step"><span class="n">5</span><span><b>Yanıt</b><span>Sıkıştırma ve önbellek başlıkları <code>server/middleware/</code> içinde belirlenir; olay akışı gibi yanıtlar sıkıştırmadan geçer.</span></span></div>
1358
+ <div class="step"><span class="n">1</span><span><b>Route</b><span><code>server/routes/</code> matches the URL and hands the request to the right controller.</span></span></div>
1359
+ <div class="step"><span class="n">2</span><span><b>Controller</b><span><code>server/controllers/</code> gathers the data it needs from the <code>lib/</code> services and prepares the model for the template.</span></span></div>
1360
+ <div class="step"><span class="n">3</span><span><b>Render</b><span><code>server/render.js</code> runs the EJS template and prints manifest entries and head hints into the layout.</span></span></div>
1361
+ <div class="step"><span class="n">4</span><span><b>HTML cache</b><span><code>server/html-cache.js</code> stores the output; if the same URL is requested again the template never runs. The <code>cached</code> label on the Server tab shows this.</span></span></div>
1362
+ <div class="step"><span class="n">5</span><span><b>Response</b><span>Compression and cache headers are decided in <code>server/middleware/</code>; responses like event streams skip compression.</span></span></div>
1336
1363
  </div>
1337
- <p><strong>Ön ısıtma:</strong> <code>server/prewarm.js</code> açılışta sık gezilen
1338
- rotaları kendi kendine ziyaret eder, böylece ilk gerçek ziyaretçi sıcak önbelleğe düşer.
1339
- Aynı işi elle yapmak için <code>npm run warm</code>.</p>
1364
+ <p><strong>Prewarming:</strong> <code>server/prewarm.js</code> visits the most travelled
1365
+ routes by itself at startup, so the first real visitor lands on a warm cache.
1366
+ To do the same by hand, use <code>npm run warm</code>.</p>
1340
1367
  `,
1341
1368
  },
1342
1369
  islands: {
1343
- label: "Island'lar",
1370
+ label: "Islands",
1344
1371
  html: `
1345
- <p>İstemci kodu sayfa başına tek bir dev bundle değil; parça parça yüklenen
1346
- <strong>island</strong>'lardan oluşur. Şablon parçası HTML'i basar, island yalnızca
1347
- davranışı ekler.</p>
1372
+ <p>Client code is not one huge bundle per page; it is made of
1373
+ <strong>islands</strong> loaded piece by piece. The template partial prints the HTML,
1374
+ the island only adds behaviour.</p>
1348
1375
  <div class="steps">
1349
- <div class="step"><span class="n">1</span><span><b>İşaretleme</b><span>EJS partial'ı kök elemana <code>data-island="ad"</code> koyar.</span></span></div>
1350
- <div class="step"><span class="n">2</span><span><b>Modül</b><span><code>client/islands/</code> altında aynı adla bir dosya bulunur ve mount fonksiyonunu dışa verir.</span></span></div>
1351
- <div class="step"><span class="n">3</span><span><b>Yükleme</b><span><code>client/core/registry.js</code> IntersectionObserver ile eleman görünüm alanına yaklaşınca modülü dinamik import eder.</span></span></div>
1352
- <div class="step"><span class="n">4</span><span><b>Bağlanma</b><span>Mount başarılıysa elemana <code>data-island-ready</code> yazılır. Performans sekmesindeki <b>Island</b> kartı bu oranı gösterir.</span></span></div>
1376
+ <div class="step"><span class="n">1</span><span><b>Marking</b><span>The EJS partial puts <code>data-island="name"</code> on the root element.</span></span></div>
1377
+ <div class="step"><span class="n">2</span><span><b>Module</b><span>A file with the same name lives under <code>client/islands/</code> and exports the mount function.</span></span></div>
1378
+ <div class="step"><span class="n">3</span><span><b>Loading</b><span><code>client/core/registry.js</code> dynamically imports the module once an IntersectionObserver sees the element approach the viewport.</span></span></div>
1379
+ <div class="step"><span class="n">4</span><span><b>Mounting</b><span>If the mount succeeds, <code>data-island-ready</code> is written on the element. The <b>Island</b> card on the Performance tab shows this ratio.</span></span></div>
1353
1380
  </div>
1354
1381
  <div class="cards">
1355
- <div class="card"><b>Neden</b><span>Ekranın altındaki grafik, harita veya yorum bloğu için JS indirmeye ilk boyamada gerek yok.</span></div>
1356
- <div class="card"><b>Hata izi</b><span>Bir island patlarsa hata bu panelin <b>Hatalar</b> sekmesine <code>client</code> kaynağıyla düşer, sayfanın kalanı çalışmaya devam eder.</span></div>
1357
- <div class="card"><b>Stub'lar</b><span><code>build/tasks/island-stubs.mjs</code> eksik island dosyalarını yakalar; isim uyuşmazlıkları build'de görünür.</span></div>
1382
+ <div class="card"><b>Why</b><span>There is no need to download JS at first paint for a chart, map or comment block below the fold.</span></div>
1383
+ <div class="card"><b>Error trail</b><span>If an island blows up, the error lands on the <b>Errors</b> tab of this panel with the <code>client</code> source, and the rest of the page keeps working.</span></div>
1384
+ <div class="card"><b>Stubs</b><span><code>build/tasks/island-stubs.mjs</code> catches missing island files; name mismatches show up at build time.</span></div>
1358
1385
  </div>
1359
1386
  `,
1360
1387
  },
1361
1388
  build: {
1362
1389
  label: "Build",
1363
1390
  html: `
1364
- <p><code>npm run build</code> tüm varlıkları <code>build/generated/</code> altına
1365
- üretir ve hash'li dosya adlarını <code>manifest.json</code>'a yazar. Şablonlar dosya
1366
- adını asla elle yazmaz, hep manifest üzerinden okur.</p>
1391
+ <p><code>npm run build</code> produces every asset under <code>build/generated/</code>
1392
+ and writes the hashed file names into <code>manifest.json</code>. Templates never write
1393
+ a file name by hand; they always read it from the manifest.</p>
1367
1394
  <div class="rows">
1368
- <div class="row"><span class="tag">client</span><span class="path">esbuild ile island'lar ve çekirdek JS; watch modunda artımlı yeniden paketleme.</span><span class="hint"></span><span></span></div>
1369
- <div class="row"><span class="tag">css</span><span class="path">Tailwind + PostCSS derlemesi, ardından LightningCSS ile küçültme ve tarayıcı hedefine indirme.</span><span class="hint"></span><span></span></div>
1370
- <div class="row"><span class="tag">icons</span><span class="path">Phosphor setinden yalnızca kullanılan ikonlar toplanır, tek sprite'a yazılır.</span><span class="hint"></span><span></span></div>
1371
- <div class="row"><span class="tag">fonts</span><span class="path">Font dosyaları alt kümelenip kopyalanır, preload ipuçları üretilir.</span><span class="hint"></span><span></span></div>
1372
- <div class="row"><span class="tag">images</span><span class="path">sharp ile boyut varyantları ve modern formatlar.</span><span class="hint"></span><span></span></div>
1373
- <div class="row"><span class="tag">precompress</span><span class="path">Statik çıktının gzip/brotli sürümleri önceden hazırlanır; sunucu istek anında sıkıştırmaz.</span><span class="hint"></span><span></span></div>
1395
+ <div class="row"><span class="tag">client</span><span class="path">Islands and core JS via esbuild; incremental rebundling in watch mode.</span><span class="hint"></span><span></span></div>
1396
+ <div class="row"><span class="tag">css</span><span class="path">Tailwind + PostCSS compilation, then minification and browser downleveling with LightningCSS.</span><span class="hint"></span><span></span></div>
1397
+ <div class="row"><span class="tag">icons</span><span class="path">Only the icons actually used are collected from the Phosphor set and written into a single sprite.</span><span class="hint"></span><span></span></div>
1398
+ <div class="row"><span class="tag">fonts</span><span class="path">Font files are subset and copied, and preload hints are generated.</span><span class="hint"></span><span></span></div>
1399
+ <div class="row"><span class="tag">images</span><span class="path">Size variants and modern formats via sharp.</span><span class="hint"></span><span></span></div>
1400
+ <div class="row"><span class="tag">precompress</span><span class="path">gzip/brotli versions of the static output are prepared ahead of time; the server never compresses per request.</span><span class="hint"></span><span></span></div>
1374
1401
  </div>
1375
- <p><code>npm start</code> öncesinde <code>build/ensure-build.mjs</code> çalışır: çıktı
1376
- eksikse build kendiliğinden tetiklenir, yani derlenmemiş bir sunucu ayağa kalkmaz.</p>
1402
+ <p><code>build/ensure-build.mjs</code> runs before <code>npm start</code>: if the output
1403
+ is missing the build triggers itself, so an uncompiled server never comes up.</p>
1377
1404
  `,
1378
1405
  },
1379
1406
  dev: {
1380
- label: "Dev akışı",
1407
+ label: "Dev flow",
1381
1408
  html: `
1382
- <p><code>npm run dev</code> tek bir süreç değil: <code>scripts/dev.mjs</code> build'i
1383
- watch modunda başlatır, sunucuyu ayağa kaldırır ve ikisinin çıktısını tek bir
1384
- okunur akışta birleştirir.</p>
1409
+ <p><code>npm run dev</code> is not a single process: <code>scripts/dev.mjs</code> starts
1410
+ the build in watch mode, brings the server up and merges the output of both into one
1411
+ readable stream.</p>
1385
1412
  <div class="steps">
1386
- <div class="step"><span class="n">1</span><span><b>İzleme</b><span>Yalnızca <code>server/</code>, <code>lib/</code> ve <code>views/</code> izlenir. Build çıktısı izlenmediği için kendi kendini tetikleyen restart döngüsü oluşmaz.</span></span></div>
1387
- <div class="step"><span class="n">2</span><span><b>CSS takası</b><span>Stil değişince sayfa yenilenmez; yeni stylesheet yüklenip eskisi kaldırılır, kaydırma konumu ve açık paneller korunur.</span></span></div>
1388
- <div class="step"><span class="n">3</span><span><b>Tam yenileme</b><span>JS veya sunucu kodu değişince tek seferlik reload yapılır. Sunucu süreç kimliği değiştiğinde de aynı yol izlenir.</span></span></div>
1389
- <div class="step"><span class="n">4</span><span><b>Durum korunması</b><span>Panelin açık/kapalı durumu ve sekmesi sekme belleğinde; sunucu günlükleri ise süreç dışında saklanır. Restart panelin içeriğini silmez.</span></span></div>
1413
+ <div class="step"><span class="n">1</span><span><b>Watching</b><span>Only <code>server/</code>, <code>lib/</code> and <code>views/</code> are watched. The build output is not watched, so no self-triggering restart loop can form.</span></span></div>
1414
+ <div class="step"><span class="n">2</span><span><b>CSS swap</b><span>A style change does not reload the page; the new stylesheet loads and the old one is removed, keeping scroll position and open panels.</span></span></div>
1415
+ <div class="step"><span class="n">3</span><span><b>Full reload</b><span>A JS or server code change causes a single reload. The same path is taken when the server process id changes.</span></span></div>
1416
+ <div class="step"><span class="n">4</span><span><b>State retention</b><span>The panel's open state and tab live in session storage; server logs are kept outside the process. A restart does not wipe the panel's contents.</span></span></div>
1390
1417
  </div>
1391
1418
  <div class="cards">
1392
- <div class="card"><b>Alt+D</b><span>Paneli açar/kapatır. <code>Esc</code> kapatır, karartma alanına tıklamak da kapatır.</span></div>
1393
- <div class="card"><b>Sayfayı yenile</b><span>Panel açıkken yenileme yapar; yenileme sonrası panel aynı sekmeyle geri gelir.</span></div>
1394
- <div class="card"><b>Kayıtları temizle</b><span>Tarayıcı ve sunucu taraflı hata/istek günlüklerini sıfırlar, paneli kapatmaz.</span></div>
1419
+ <div class="card"><b>Alt+D</b><span>Opens/closes the panel. <code>Esc</code> closes it, and so does clicking the backdrop.</span></div>
1420
+ <div class="card"><b>Reload page</b><span>Reloads while the panel is open; afterwards the panel comes back on the same tab.</span></div>
1421
+ <div class="card"><b>Clear records</b><span>Resets the browser and server side error/request logs without closing the panel.</span></div>
1395
1422
  </div>
1396
1423
  `,
1397
1424
  },
1398
1425
  theme: {
1399
- label: "Tema & UI",
1426
+ label: "Theme & UI",
1400
1427
  html: `
1401
- <p>Stil kararları tek kaynaktan gelir. Renk, boşluk, radius ve tipografi
1402
- token'ları <code>styles/theme.css</code> içindeki <code>:root</code> değişkenlerinde
1403
- tanımlıdır; Tailwind sınıfları <code>styles/JSkelet.css</code> içindeki
1404
- <code>@theme inline</code> bloğuyla bu token'lara bağlanır.</p>
1428
+ <p>Style decisions come from a single source. Colour, spacing, radius and typography
1429
+ tokens are defined in the <code>:root</code> variables inside
1430
+ <code>styles/theme.css</code>; Tailwind classes are bound to those tokens by the
1431
+ <code>@theme inline</code> block in <code>styles/JSkelet.css</code>.</p>
1405
1432
  <div class="rows">
1406
- <div class="row"><span class="tag">theme.css</span><span class="path">Token'lar ve <code>.wrapper</code>, <code>.clamp-2</code> gibi hazır sınıflar.</span><span class="hint"></span><span></span></div>
1407
- <div class="row"><span class="tag">JSkelet.css</span><span class="path">Tailwind utility eşlemeleri ve <code>.JSkelet-type-*</code> gibi bileşik sınıflar.</span><span class="hint"></span><span></span></div>
1408
- <div class="row"><span class="tag">components/ui</span><span class="path">Buton, kart, modal gibi yeniden kullanılabilir parçaların tek adresi.</span><span class="hint"></span><span></span></div>
1433
+ <div class="row"><span class="tag">theme.css</span><span class="path">Tokens and ready-made classes such as <code>.wrapper</code> and <code>.clamp-2</code>.</span><span class="hint"></span><span></span></div>
1434
+ <div class="row"><span class="tag">JSkelet.css</span><span class="path">Tailwind utility mappings and composite classes such as <code>.JSkelet-type-*</code>.</span><span class="hint"></span><span></span></div>
1435
+ <div class="row"><span class="tag">components/ui</span><span class="path">The single home for reusable pieces like buttons, cards and modals.</span><span class="hint"></span><span></span></div>
1409
1436
  </div>
1410
- <p><strong>Kural:</strong> yeni bir renk ya da ölçü gerektiğinde hex/pixel
1411
- hardcode edilmez; önce token eklenir, gerekiyorsa Tailwind tarafına eşlenir,
1412
- sonra bileşende kullanılır. Yeni genel amaçlı bir UI parçası da
1413
- <code>views/components/ui/</code> dışına çıkmaz.</p>
1437
+ <p><strong>Rule:</strong> when a new colour or measure is needed, never hardcode a
1438
+ hex/pixel value; add a token first, map it on the Tailwind side if necessary, then use
1439
+ it in the component. A new general purpose UI piece also never leaves
1440
+ <code>views/components/ui/</code>.</p>
1414
1441
  `,
1415
1442
  },
1416
1443
  commands: {
1417
- label: "Komutlar",
1444
+ label: "Commands",
1418
1445
  html: `
1419
- <p>Sık kullanılan npm script'leri. Doğrulama script'leri belirli sayfaların
1420
- beklenen parçaları içerip içermediğini gerçek çıktı üzerinden kontrol eder.</p>
1446
+ <p>Commonly used npm scripts. The verification scripts check against real output
1447
+ whether specific pages contain the pieces they are expected to.</p>
1421
1448
  <div class="rows">
1422
- <div class="row"><span class="tag">dev</span><span class="path">Build watch + sunucu, bu panelle birlikte.</span><span class="hint"></span><span></span></div>
1423
- <div class="row"><span class="tag">build</span><span class="path">Üretim çıktısı: JS, CSS, ikon, font, görsel, ön sıkıştırma.</span><span class="hint"></span><span></span></div>
1424
- <div class="row"><span class="tag">start</span><span class="path">Üretim sunucusu; öncesinde build eksikse tamamlanır.</span><span class="hint"></span><span></span></div>
1425
- <div class="row"><span class="tag">lint</span><span class="path">ESLint. Değişiklik sonrası varsayılan doğrulama yolu budur, tam build şart değil.</span><span class="hint"></span><span></span></div>
1426
- <div class="row"><span class="tag">warm</span><span class="path">Önbelleği elle ısıtır.</span><span class="hint"></span><span></span></div>
1427
- <div class="row"><span class="tag">perf</span><span class="path">Performans denetimi raporu üretir.</span><span class="hint"></span><span></span></div>
1428
- <div class="row"><span class="tag">verify:*</span><span class="path">Rota dumanı, head etiketleri, grafik, yorumlar, hisse sekmeleri, haber akışı ve ana sayfa widget'ları için hedefli kontroller.</span><span class="hint"></span><span></span></div>
1449
+ <div class="row"><span class="tag">dev</span><span class="path">Build watch + server, together with this panel.</span><span class="hint"></span><span></span></div>
1450
+ <div class="row"><span class="tag">build</span><span class="path">Production output: JS, CSS, icons, fonts, images, precompression.</span><span class="hint"></span><span></span></div>
1451
+ <div class="row"><span class="tag">start</span><span class="path">Production server; a missing build is completed first.</span><span class="hint"></span><span></span></div>
1452
+ <div class="row"><span class="tag">lint</span><span class="path">ESLint. This is the default verification path after a change; a full build is not required.</span><span class="hint"></span><span></span></div>
1453
+ <div class="row"><span class="tag">warm</span><span class="path">Warms the cache by hand.</span><span class="hint"></span><span></span></div>
1454
+ <div class="row"><span class="tag">perf</span><span class="path">Produces a performance audit report.</span><span class="hint"></span><span></span></div>
1455
+ <div class="row"><span class="tag">verify:*</span><span class="path">Targeted checks for route smoke, head tags, charts, comments, stock tabs, the news feed and home page widgets.</span><span class="hint"></span><span></span></div>
1429
1456
  </div>
1430
1457
  `,
1431
1458
  },
1432
1459
  layout: {
1433
- label: "Klasörler",
1460
+ label: "Folders",
1434
1461
  html: `
1435
- <p>Bir şeyi nerede arayacağını bilmek için kısa harita.</p>
1462
+ <p>A short map so you know where to look for something.</p>
1436
1463
  <div class="rows">
1437
- <div class="row"><span class="tag">server/</span><span class="path">Express uygulaması: rotalar, controller'lar, middleware, render ve önbellek.</span><span class="hint"></span><span></span></div>
1438
- <div class="row"><span class="tag">views/</span><span class="path">EJS layout'u, sayfalar ve partial'lar. HTML'in tek kaynağı.</span><span class="hint"></span><span></span></div>
1439
- <div class="row"><span class="tag">client/</span><span class="path">Tarayıcı kodu: <code>core/</code> island altyapısı, <code>islands/</code> davranışlar, <code>devtools/</code> bu panel.</span><span class="hint"></span><span></span></div>
1440
- <div class="row"><span class="tag">lib/</span><span class="path">Veri servisleri ve alan mantığı; API çağrıları, dönüşümler, formatlayıcılar.</span><span class="hint"></span><span></span></div>
1441
- <div class="row"><span class="tag">app/</span><span class="path">Tema dosyaları ve paylaşılan UI bileşenleri.</span><span class="hint"></span><span></span></div>
1442
- <div class="row"><span class="tag">build/</span><span class="path">Derleme script'leri (<code>tasks/</code>) ve üretilen çıktı (<code>generated/</code>).</span><span class="hint"></span><span></span></div>
1443
- <div class="row"><span class="tag">framework/</span><span class="path">Build ve dev süreçlerinin paylaştığı yardımcılar, terminal günlüğü dahil.</span><span class="hint"></span><span></span></div>
1444
- <div class="row"><span class="tag">scripts/</span><span class="path">Dev orkestrasyonu ve doğrulama/bakım araçları.</span><span class="hint"></span><span></span></div>
1445
- <div class="row"><span class="tag">public/</span><span class="path">Doğrudan servis edilen statik dosyalar.</span><span class="hint"></span><span></span></div>
1446
- <div class="row"><span class="tag">docs/</span><span class="path">Uzun biçimli notlar ve karar kayıtları.</span><span class="hint"></span><span></span></div>
1464
+ <div class="row"><span class="tag">server/</span><span class="path">The Express application: routes, controllers, middleware, render and cache.</span><span class="hint"></span><span></span></div>
1465
+ <div class="row"><span class="tag">views/</span><span class="path">The EJS layout, pages and partials. The single source of HTML.</span><span class="hint"></span><span></span></div>
1466
+ <div class="row"><span class="tag">client/</span><span class="path">Browser code: <code>core/</code> island infrastructure, <code>islands/</code> behaviours, <code>devtools/</code> this panel.</span><span class="hint"></span><span></span></div>
1467
+ <div class="row"><span class="tag">lib/</span><span class="path">Data services and domain logic; API calls, transforms, formatters.</span><span class="hint"></span><span></span></div>
1468
+ <div class="row"><span class="tag">app/</span><span class="path">Theme files and shared UI components.</span><span class="hint"></span><span></span></div>
1469
+ <div class="row"><span class="tag">build/</span><span class="path">Build scripts (<code>tasks/</code>) and the generated output (<code>generated/</code>).</span><span class="hint"></span><span></span></div>
1470
+ <div class="row"><span class="tag">framework/</span><span class="path">Helpers shared by the build and dev processes, including the terminal logger.</span><span class="hint"></span><span></span></div>
1471
+ <div class="row"><span class="tag">scripts/</span><span class="path">Dev orchestration and verification/maintenance tools.</span><span class="hint"></span><span></span></div>
1472
+ <div class="row"><span class="tag">public/</span><span class="path">Static files served directly.</span><span class="hint"></span><span></span></div>
1473
+ <div class="row"><span class="tag">docs/</span><span class="path">Long form notes and decision records.</span><span class="hint"></span><span></span></div>
1447
1474
  </div>
1448
1475
  `,
1449
1476
  },
@@ -1457,8 +1484,8 @@ function aboutTab() {
1457
1484
  return `
1458
1485
  ${lede(
1459
1486
  "good",
1460
- `JSkelet ön yüzü kendi ince framework'ü üstünde çalışıyor: <strong>sunucuda HTML</strong>, ` +
1461
- `tarayıcıda yalnızca gerekli <strong>island'lar</strong>. Bu sekme panelin içindeki kısa dokümantasyon.`,
1487
+ `The JSkelet front end runs on its own thin framework: <strong>HTML on the server</strong>, ` +
1488
+ `only the necessary <strong>islands</strong> in the browser. This tab is the short documentation inside the panel.`,
1462
1489
  )}
1463
1490
  <div class="subtabs">
1464
1491
  ${Object.entries(ABOUT)
@@ -1519,9 +1546,9 @@ function paintPrewarm(root) {
1519
1546
 
1520
1547
  chip.hidden = false;
1521
1548
  chip.className = `warm ${warm.active ? "" : "done"}`;
1522
- chip.title = `Önbellek ısıtması: ${warm.ok} başarılı${
1523
- warm.failed ? `, ${warm.failed} hata` : ""
1524
- } / ${warm.total} sayfa`;
1549
+ chip.title = `Cache prewarming: ${warm.ok} succeeded${
1550
+ warm.failed ? `, ${warm.failed} failed` : ""
1551
+ } / ${warm.total} pages`;
1525
1552
  chip.querySelector("[data-part='warm-text']").textContent = warm.active
1526
1553
  ? `${warm.done}/${warm.total} prewarmed`
1527
1554
  : `${warm.ok}/${warm.total} prewarmed`;
@@ -1541,12 +1568,12 @@ function paint() {
1541
1568
 
1542
1569
  const tone = errorCount ? "bad" : warnCount ? "warn" : "";
1543
1570
  const badgeCount = errorCount || warnCount;
1544
- const lcpLabel = metrics.lcp ? `${Math.round(metrics.lcp)} ms` : "ölçülüyor";
1571
+ const lcpLabel = metrics.lcp ? `${Math.round(metrics.lcp)} ms` : "measuring";
1545
1572
 
1546
1573
  const bubble = root.querySelector(".bubble");
1547
1574
  bubble.className = `bubble ${tone}`;
1548
- bubble.title = `JSkelet dev araçları — LCP ${lcpLabel}${
1549
- badgeCount ? `, ${errorCount} hata, ${warnCount} uyarı` : ""
1575
+ bubble.title = `JSkelet dev tools — LCP ${lcpLabel}${
1576
+ badgeCount ? `, ${errorCount} errors, ${warnCount} warnings` : ""
1550
1577
  }`;
1551
1578
 
1552
1579
  const badge = root.querySelector(".badge");
@@ -1563,15 +1590,20 @@ function paint() {
1563
1590
  const pill = root.querySelector(".pill");
1564
1591
  pill.className = `pill ${offline ? "warn" : tone}`;
1565
1592
  pill.querySelector("[data-part='pill']").textContent = offline
1566
- ? "sunucu yeniden başlıyor…"
1593
+ ? "server restarting…"
1567
1594
  : errorCount || warnCount
1568
- ? `${errorCount} hata · ${warnCount} uyarı`
1569
- : "sorun yok";
1595
+ ? `${errorCount} errors · ${warnCount} warnings`
1596
+ : "all good";
1570
1597
 
1571
1598
  const chip = root.querySelector("[data-part='chip']");
1572
1599
  chip.hidden = !errorCount;
1573
1600
  chip.textContent = String(errorCount);
1574
1601
 
1602
+ const version = serverStats?.version;
1603
+ const versionChip = root.querySelector("[data-part='version-chip']");
1604
+ versionChip.hidden = !version?.outdated;
1605
+ if (version?.outdated) versionChip.title = `JSkelet ${version.latest} is available`;
1606
+
1575
1607
  const warm = serverStats?.prewarm;
1576
1608
  const warmChip = root.querySelector("[data-part='warm-chip']");
1577
1609
  warmChip.hidden = !warm?.total;