@cccarv82/freya 2.13.0 → 2.13.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.
package/cli/web-ui.css CHANGED
@@ -557,6 +557,30 @@ body {
557
557
  border-color: rgba(59, 130, 246, .35);
558
558
  }
559
559
 
560
+ [data-theme="dark"] .pill.ok {
561
+ background: rgba(16, 185, 129, .18);
562
+ color: #6ee7b7;
563
+ border-color: rgba(16, 185, 129, .35);
564
+ }
565
+
566
+ [data-theme="dark"] .pill.warn {
567
+ background: rgba(245, 158, 11, .18);
568
+ color: #fbbf24;
569
+ border-color: rgba(245, 158, 11, .35);
570
+ }
571
+
572
+ [data-theme="dark"] .pill.info {
573
+ background: rgba(59, 130, 246, .18);
574
+ color: #93c5fd;
575
+ border-color: rgba(59, 130, 246, .35);
576
+ }
577
+
578
+ [data-theme="dark"] .pill.err {
579
+ background: rgba(239, 68, 68, .15);
580
+ color: #fca5a5;
581
+ border-color: rgba(239, 68, 68, .35);
582
+ }
583
+
560
584
  .centerBody {
561
585
  padding: 16px 20px 20px;
562
586
  overflow: auto;
@@ -1313,6 +1337,163 @@ textarea:focus {
1313
1337
  }
1314
1338
  }
1315
1339
 
1340
+ /* ── Insight Pills (Blocker Severity) ── */
1341
+ .insight-pills {
1342
+ display: flex;
1343
+ flex-wrap: wrap;
1344
+ gap: 6px;
1345
+ margin-bottom: 8px;
1346
+ }
1347
+
1348
+ .insight-pill {
1349
+ display: inline-flex;
1350
+ align-items: center;
1351
+ gap: 4px;
1352
+ font-size: 11px;
1353
+ font-weight: 700;
1354
+ padding: 3px 10px;
1355
+ border: 1px solid;
1356
+ letter-spacing: 0.3px;
1357
+ }
1358
+
1359
+ .insight-pill.critical { background: rgba(239,68,68,.12); color: #f87171; border-color: rgba(239,68,68,.3); }
1360
+ .insight-pill.high { background: rgba(249,115,22,.12); color: #fb923c; border-color: rgba(249,115,22,.3); }
1361
+ .insight-pill.medium { background: rgba(250,204,21,.12); color: #facc15; border-color: rgba(250,204,21,.3); }
1362
+ .insight-pill.low { background: rgba(34,197,94,.10); color: #4ade80; border-color: rgba(34,197,94,.25); }
1363
+
1364
+ .insight-clear {
1365
+ display: flex;
1366
+ align-items: center;
1367
+ gap: 6px;
1368
+ font-size: 12px;
1369
+ color: #4ade80;
1370
+ font-weight: 600;
1371
+ }
1372
+
1373
+ .insight-section-title {
1374
+ font-weight: 600;
1375
+ font-size: 11px;
1376
+ text-transform: uppercase;
1377
+ letter-spacing: 0.5px;
1378
+ color: var(--muted);
1379
+ margin: 10px 0 6px;
1380
+ }
1381
+
1382
+ .insight-steps {
1383
+ margin: 0 0 0 2px;
1384
+ padding: 0;
1385
+ list-style: none;
1386
+ }
1387
+
1388
+ .insight-steps li {
1389
+ position: relative;
1390
+ padding: 4px 0 4px 16px;
1391
+ font-size: 12px;
1392
+ color: var(--muted);
1393
+ line-height: 1.4;
1394
+ }
1395
+
1396
+ .insight-steps li::before {
1397
+ content: '';
1398
+ position: absolute;
1399
+ left: 0;
1400
+ top: 11px;
1401
+ width: 6px;
1402
+ height: 6px;
1403
+ background: var(--accent);
1404
+ opacity: 0.6;
1405
+ }
1406
+
1407
+ /* ── Toast Notifications ── */
1408
+ .toast-container {
1409
+ position: fixed;
1410
+ bottom: 20px;
1411
+ right: 20px;
1412
+ z-index: 9999;
1413
+ display: flex;
1414
+ flex-direction: column-reverse;
1415
+ gap: 8px;
1416
+ pointer-events: none;
1417
+ }
1418
+
1419
+ .toast {
1420
+ pointer-events: auto;
1421
+ padding: 10px 16px;
1422
+ border: 1px solid var(--line2);
1423
+ background: var(--paper);
1424
+ backdrop-filter: blur(12px);
1425
+ color: var(--text);
1426
+ font-size: 13px;
1427
+ font-weight: 500;
1428
+ font-family: var(--sans);
1429
+ box-shadow: var(--shadow);
1430
+ max-width: 360px;
1431
+ opacity: 0;
1432
+ transform: translateX(100%);
1433
+ animation: toastIn 0.3s cubic-bezier(0.16, 1, 0.3, 1) forwards;
1434
+ }
1435
+
1436
+ .toast.out {
1437
+ animation: toastOut 0.25s ease-in forwards;
1438
+ }
1439
+
1440
+ .toast.ok { border-left: 3px solid rgba(16, 185, 129, .85); }
1441
+ .toast.err { border-left: 3px solid rgba(239, 68, 68, .85); }
1442
+ .toast.run { border-left: 3px solid rgba(56, 189, 248, .9); }
1443
+ .toast.plan { border-left: 3px solid rgba(250, 204, 21, .9); }
1444
+
1445
+ @keyframes toastIn {
1446
+ from { opacity: 0; transform: translateX(100%); }
1447
+ to { opacity: 1; transform: translateX(0); }
1448
+ }
1449
+
1450
+ @keyframes toastOut {
1451
+ from { opacity: 1; transform: translateX(0); }
1452
+ to { opacity: 0; transform: translateX(100%); }
1453
+ }
1454
+
1455
+ /* ── Loading Skeletons ── */
1456
+ .skeleton {
1457
+ position: relative;
1458
+ overflow: hidden;
1459
+ min-height: 60px;
1460
+ padding: 16px;
1461
+ }
1462
+
1463
+ .skeleton::after {
1464
+ content: '';
1465
+ position: absolute;
1466
+ inset: 0;
1467
+ background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.04), transparent);
1468
+ animation: shimmer 1.5s infinite;
1469
+ }
1470
+
1471
+ @keyframes shimmer {
1472
+ 0% { transform: translateX(-100%); }
1473
+ 100% { transform: translateX(100%); }
1474
+ }
1475
+
1476
+ .skeleton-line {
1477
+ height: 12px;
1478
+ background: var(--line);
1479
+ margin-bottom: 10px;
1480
+ }
1481
+
1482
+ .skeleton-line:nth-child(1) { width: 80%; }
1483
+ .skeleton-line:nth-child(2) { width: 55%; }
1484
+ .skeleton-line:nth-child(3) { width: 70%; }
1485
+
1486
+ /* ── Content Transitions ── */
1487
+ @keyframes fadeIn {
1488
+ from { opacity: 0; transform: translateY(6px); }
1489
+ to { opacity: 1; transform: translateY(0); }
1490
+ }
1491
+
1492
+ .panelBody > *,
1493
+ .swimlaneContainer > * {
1494
+ animation: fadeIn 0.2s ease-out;
1495
+ }
1496
+
1316
1497
  * {
1317
1498
  border-radius: 0 !important;
1318
1499
  }
package/cli/web-ui.js CHANGED
@@ -39,6 +39,37 @@
39
39
  if (pill) pill.textContent = text;
40
40
  const status = $('status');
41
41
  if (status) status.textContent = text;
42
+ // Forward important states to toast system
43
+ if (kind === 'err') showToast('err', text);
44
+ else if (kind === 'ok' && text !== 'pronto') showToast('ok', text);
45
+ }
46
+
47
+ /* ── Toast Notification System ── */
48
+ let _toastContainer = null;
49
+ function showToast(kind, text, duration) {
50
+ if (!_toastContainer) {
51
+ _toastContainer = document.createElement('div');
52
+ _toastContainer.className = 'toast-container';
53
+ document.body.appendChild(_toastContainer);
54
+ }
55
+ const el = document.createElement('div');
56
+ el.className = 'toast ' + kind;
57
+ el.textContent = text;
58
+ el.setAttribute('role', 'alert');
59
+ el.setAttribute('aria-live', 'polite');
60
+ _toastContainer.appendChild(el);
61
+ const ms = duration || (kind === 'err' ? 4000 : 2500);
62
+ setTimeout(() => {
63
+ el.classList.add('out');
64
+ setTimeout(() => el.remove(), 300);
65
+ }, ms);
66
+ }
67
+
68
+ /* ── Skeleton Loading Helper ── */
69
+ function showSkeleton(elId) {
70
+ const el = $(elId);
71
+ if (!el) return;
72
+ el.innerHTML = '<div class="skeleton"><div class="skeleton-line"></div><div class="skeleton-line"></div><div class="skeleton-line"></div></div>';
42
73
  }
43
74
 
44
75
  function escapeHtml(str) {
@@ -962,6 +993,7 @@
962
993
  }
963
994
 
964
995
  async function refreshProjects() {
996
+ showSkeleton('projectsGrid');
965
997
  try {
966
998
  const [r, b, t] = await Promise.all([
967
999
  api('/api/projects/list', { dir: dirOrDefault() }),
@@ -1125,6 +1157,7 @@
1125
1157
  }
1126
1158
 
1127
1159
  async function refreshTimeline() {
1160
+ showSkeleton('timelineGrid');
1128
1161
  try {
1129
1162
  const r = await api('/api/timeline', { dir: dirOrDefault() });
1130
1163
  state.timeline = r.items || [];
@@ -1259,6 +1292,7 @@
1259
1292
  }
1260
1293
 
1261
1294
  async function refreshReportsPage() {
1295
+ showSkeleton('reportsGrid');
1262
1296
  try {
1263
1297
  setPill('run', 'carregando…');
1264
1298
  const r = await api('/api/reports/list', { dir: dirOrDefault() });
@@ -1568,6 +1602,7 @@
1568
1602
  }
1569
1603
 
1570
1604
  async function refreshToday() {
1605
+ showSkeleton('swimlaneContainer');
1571
1606
  try {
1572
1607
  const [t, b] = await Promise.all([
1573
1608
  api('/api/tasks/list', { dir: dirOrDefault(), category: 'DO_NOW', status: 'PENDING', limit: 50 }),
@@ -1586,22 +1621,58 @@
1586
1621
  if (!el) return;
1587
1622
 
1588
1623
  if (!payload || !payload.summary) {
1589
- // Hide the strip if no content
1590
1624
  if (wrap) wrap.style.display = 'none';
1591
1625
  return;
1592
1626
  }
1593
1627
 
1594
1628
  const lines = [];
1595
- lines.push('<div style="margin-bottom:6px; color:var(--text);">' + escapeHtml(payload.summary) + '</div>');
1629
+ const summary = String(payload.summary || '');
1630
+
1631
+ // Parse severity counts from summary (e.g. "CRITICAL: 0, HIGH: 2, ...")
1632
+ const sevRegex = /(\w+):\s*(\d+)/g;
1633
+ let match;
1634
+ const counts = [];
1635
+ let totalActive = 0;
1636
+ while ((match = sevRegex.exec(summary)) !== null) {
1637
+ const sev = match[1].toUpperCase();
1638
+ const count = parseInt(match[2], 10);
1639
+ if (sev !== 'TOTAL') {
1640
+ counts.push({ sev, count });
1641
+ totalActive += count;
1642
+ }
1643
+ }
1644
+
1645
+ if (counts.length > 0) {
1646
+ // Render as styled pills
1647
+ if (totalActive === 0) {
1648
+ lines.push('<div class="insight-clear"><span style="font-size:16px;">&#10003;</span> Nenhum bloqueio ativo</div>');
1649
+ } else {
1650
+ let pills = '<div class="insight-pills">';
1651
+ for (const c of counts) {
1652
+ if (c.count > 0) {
1653
+ pills += '<span class="insight-pill ' + c.sev.toLowerCase() + '">' + escapeHtml(c.sev) + ': ' + c.count + '</span>';
1654
+ }
1655
+ }
1656
+ pills += '</div>';
1657
+ lines.push(pills);
1658
+ }
1659
+ } else {
1660
+ // Fallback: summary doesn't match pattern, render as text
1661
+ lines.push('<div style="color:var(--text); font-size:12px;">' + escapeHtml(summary) + '</div>');
1662
+ }
1663
+
1596
1664
  if (payload.suggestions && payload.suggestions.length) {
1597
- lines.push('<div style="font-weight:600; font-size:11px; text-transform:uppercase; letter-spacing:0.5px; color:var(--muted); margin:8px 0 4px;">Próximos passos</div>');
1598
- lines.push('<ul style="margin:0 0 0 14px; padding:0;">' + payload.suggestions.map((s) => '<li style="margin-bottom:3px;">' + escapeHtml(s) + '</li>').join('') + '</ul>');
1665
+ lines.push('<div class="insight-section-title">Proximos passos</div>');
1666
+ lines.push('<ul class="insight-steps">' + payload.suggestions.map(function (s) {
1667
+ return '<li>' + escapeHtml(s) + '</li>';
1668
+ }).join('') + '</ul>');
1599
1669
  }
1600
1670
  if (payload.top && payload.top.length) {
1601
- lines.push('<div style="font-weight:600; font-size:11px; text-transform:uppercase; letter-spacing:0.5px; color:var(--muted); margin:8px 0 4px;">Top blockers</div>');
1602
- lines.push('<ul style="margin:0 0 0 14px; padding:0;">' + payload.top.map((b) => {
1603
- const c = sevColor(String(b.severity || '').toUpperCase());
1604
- return '<li style="margin-bottom:3px;"><span style="color:' + c + '; font-weight:700;">' + escapeHtml(String(b.severity || '')) + '</span> ' + escapeHtml(String(b.title || '')) + '</li>';
1671
+ lines.push('<div class="insight-section-title">Top blockers</div>');
1672
+ lines.push('<ul class="insight-steps">' + payload.top.map(function (b) {
1673
+ const sev = String(b.severity || '').toUpperCase();
1674
+ const cls = sev === 'CRITICAL' ? 'critical' : sev === 'HIGH' ? 'high' : sev === 'MEDIUM' ? 'medium' : 'low';
1675
+ return '<li><span class="insight-pill ' + cls + '" style="font-size:10px; padding:1px 6px; margin-right:4px;">' + escapeHtml(sev) + '</span>' + escapeHtml(String(b.title || '')) + '</li>';
1605
1676
  }).join('') + '</ul>');
1606
1677
  }
1607
1678
  el.innerHTML = lines.join('');
@@ -2176,8 +2247,12 @@
2176
2247
  const ok = confirm(msg);
2177
2248
  if (!ok) return;
2178
2249
 
2179
- const webhookUrl = target === 'discord' ? $('discord').value.trim() : $('teams').value.trim();
2180
- if (!webhookUrl) throw new Error('Configure o webhook antes.');
2250
+ const discordInput = $('discord');
2251
+ const teamsInput = $('teams');
2252
+ const webhookUrl = target === 'discord'
2253
+ ? (discordInput ? discordInput.value.trim() : (state.discordUrl || ''))
2254
+ : (teamsInput ? teamsInput.value.trim() : (state.teamsUrl || ''));
2255
+ if (!webhookUrl) throw new Error('Configure o webhook em Settings antes de publicar.');
2181
2256
  setPill('run', 'publish…');
2182
2257
  const mode = state.prettyPublish ? 'pretty' : 'chunks';
2183
2258
  await api('/api/publish', { webhookUrl, text: state.lastText, mode, allowSecrets: true });
@@ -2389,10 +2464,13 @@
2389
2464
  if (side) side.textContent = defaults.workspaceDir;
2390
2465
  }
2391
2466
  if (defaults && defaults.settings) {
2467
+ state.discordUrl = defaults.settings.discordWebhookUrl || '';
2468
+ state.teamsUrl = defaults.settings.teamsWebhookUrl || '';
2469
+ // Populate inputs if they exist (e.g. Settings page)
2392
2470
  const discord = $('discord');
2393
2471
  const teams = $('teams');
2394
- if (discord) discord.value = defaults.settings.discordWebhookUrl || '';
2395
- if (teams) teams.value = defaults.settings.teamsWebhookUrl || '';
2472
+ if (discord) discord.value = state.discordUrl;
2473
+ if (teams) teams.value = state.teamsUrl;
2396
2474
  }
2397
2475
  } catch (e) {
2398
2476
  // ignore
@@ -2449,6 +2527,27 @@
2449
2527
 
2450
2528
  setPill('ok', 'pronto');
2451
2529
 
2530
+ /* ── Global Keyboard Shortcuts ── */
2531
+ document.addEventListener('keydown', (e) => {
2532
+ // Ctrl/Cmd+Enter: Save & Process from anywhere
2533
+ if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') {
2534
+ e.preventDefault();
2535
+ if (typeof saveAndPlan === 'function') saveAndPlan();
2536
+ return;
2537
+ }
2538
+ // Ctrl/Cmd+K: Focus search/input on current page
2539
+ if ((e.ctrlKey || e.metaKey) && e.key === 'k') {
2540
+ e.preventDefault();
2541
+ const target = $('inboxText') || $('reportsFilter') || $('projectsFilter') || $('timelineFilter');
2542
+ if (target) target.focus();
2543
+ return;
2544
+ }
2545
+ // Escape: Blur active element
2546
+ if (e.key === 'Escape' && document.activeElement) {
2547
+ document.activeElement.blur();
2548
+ }
2549
+ });
2550
+
2452
2551
  // Expose handlers for inline onclick
2453
2552
  window.doInit = doInit;
2454
2553
  window.doUpdate = doUpdate;
package/cli/web.js CHANGED
@@ -1021,7 +1021,7 @@ function buildDocsHtml(safeDefault, appVersion) {
1021
1021
  <div class="frame">
1022
1022
  <div class="shell">
1023
1023
 
1024
- <aside class="rail">
1024
+ <aside class="rail" role="navigation" aria-label="Menu principal">
1025
1025
  <div class="railTop">
1026
1026
  <div class="railLogo">F</div>
1027
1027
  </div>
@@ -1053,7 +1053,7 @@ function buildDocsHtml(safeDefault, appVersion) {
1053
1053
  </div>
1054
1054
  </aside>
1055
1055
 
1056
- <main class="center">
1056
+ <main class="center" role="main">
1057
1057
  <div class="topbar">
1058
1058
  <div class="brandLine">
1059
1059
  <span class="spark"></span>
@@ -1160,7 +1160,7 @@ function buildHtml(safeDefault, appVersion) {
1160
1160
  <div class="frame">
1161
1161
  <div class="shell">
1162
1162
 
1163
- <aside class="rail">
1163
+ <aside class="rail" role="navigation" aria-label="Menu principal">
1164
1164
  <div class="railTop">
1165
1165
  <div class="railLogo">F</div>
1166
1166
  </div>
@@ -1190,7 +1190,7 @@ function buildHtml(safeDefault, appVersion) {
1190
1190
  </div>
1191
1191
  </aside>
1192
1192
 
1193
- <main class="center">
1193
+ <main class="center" role="main">
1194
1194
  <div class="topbar">
1195
1195
  <div class="brandLine">
1196
1196
  <span class="spark"></span>
@@ -1223,7 +1223,7 @@ function buildHtml(safeDefault, appVersion) {
1223
1223
  </div>
1224
1224
 
1225
1225
  <!-- Textarea -->
1226
- <textarea id="inboxText" placeholder="Cole updates, decisões, blockers... ou faça uma pergunta à Freya.&#10;&#10;▸ Salvar & Processar → extrai tarefas e blockers do texto&#10;▸ Perguntar → consulta o histórico via busca semântica (RAG)" style="resize:none; min-height: 160px; border-radius: 0; border-left: none; border-right: none; border-top: none; border-bottom: 1px solid var(--border); padding: 14px 16px; font-size: 13px; line-height: 1.6;"
1226
+ <textarea id="inboxText" aria-label="Entrada de texto para processar ou perguntar" placeholder="Cole updates, decisões, blockers... ou faça uma pergunta à Freya.&#10;&#10;▸ Salvar & Processar → extrai tarefas e blockers do texto&#10;▸ Perguntar → consulta o histórico via busca semântica (RAG)" style="resize:none; min-height: 160px; border-radius: 0; border-left: none; border-right: none; border-top: none; border-bottom: 1px solid var(--border); padding: 14px 16px; font-size: 13px; line-height: 1.6;"
1227
1227
  onkeydown="if((event.metaKey||event.ctrlKey)&&event.key==='Enter'){event.preventDefault();window.saveAndPlan();}"></textarea>
1228
1228
 
1229
1229
  <!-- Actions bar -->
@@ -1311,24 +1311,6 @@ function buildHtml(safeDefault, appVersion) {
1311
1311
  </div>
1312
1312
  </section>
1313
1313
 
1314
- <div class="panel">
1315
- <div class="panelHead"><b>Configurações de publicação</b></div>
1316
- <div class="panelBody">
1317
- <label>Discord webhook URL</label>
1318
- <input id="discord" placeholder="https://discord.com/api/webhooks/..." />
1319
- <div style="height:10px"></div>
1320
-
1321
- <label>Teams webhook URL</label>
1322
- <input id="teams" placeholder="https://..." />
1323
- <div class="help">Os webhooks ficam salvos na workspace em <code>data/settings/settings.json</code>.</div>
1324
-
1325
- <div style="height:10px"></div>
1326
- <label style="display:flex; align-items:center; gap:10px; user-select:none; margin: 6px 0 12px 0">
1327
- <input id="prettyPublish" type="checkbox" checked style="width:auto" onchange="togglePrettyPublish()" />
1328
- Publicação bonita (cards/embeds)
1329
- </label>
1330
- </div>
1331
- </div>
1332
1314
  </div>
1333
1315
  </main>
1334
1316
  </div>
@@ -1358,7 +1340,7 @@ function buildReportsHtml(safeDefault, appVersion) {
1358
1340
  <div class="frame">
1359
1341
  <div class="shell">
1360
1342
 
1361
- <aside class="rail">
1343
+ <aside class="rail" role="navigation" aria-label="Menu principal">
1362
1344
  <div class="railTop">
1363
1345
  <div class="railLogo">F</div>
1364
1346
  </div>
@@ -1388,7 +1370,7 @@ function buildReportsHtml(safeDefault, appVersion) {
1388
1370
  </div>
1389
1371
  </aside>
1390
1372
 
1391
- <main class="center reportsPage" id="reportsPage">
1373
+ <main class="center reportsPage" role="main" id="reportsPage">
1392
1374
  <div class="topbar">
1393
1375
  <div class="brandLine">
1394
1376
  <span class="spark"></span>
@@ -1471,7 +1453,7 @@ function buildProjectsHtml(safeDefault, appVersion) {
1471
1453
  <div class="frame">
1472
1454
  <div class="shell">
1473
1455
 
1474
- <aside class="rail">
1456
+ <aside class="rail" role="navigation" aria-label="Menu principal">
1475
1457
  <div class="railTop">
1476
1458
  <div class="railLogo">F</div>
1477
1459
  </div>
@@ -1500,7 +1482,7 @@ function buildProjectsHtml(safeDefault, appVersion) {
1500
1482
  </div>
1501
1483
  </aside>
1502
1484
 
1503
- <main class="center reportsPage" id="projectsPage">
1485
+ <main class="center reportsPage" role="main" id="projectsPage">
1504
1486
  <div class="topbar">
1505
1487
  <div class="brandLine">
1506
1488
  <span class="spark"></span>
@@ -1673,7 +1655,7 @@ function buildGraphHtml(safeDefault, appVersion) {
1673
1655
  <div class="frame">
1674
1656
  <div class="shell">
1675
1657
 
1676
- <aside class="rail">
1658
+ <aside class="rail" role="navigation" aria-label="Menu principal">
1677
1659
  <div class="railTop">
1678
1660
  <div class="railLogo">F</div>
1679
1661
  </div>
@@ -1702,7 +1684,7 @@ function buildGraphHtml(safeDefault, appVersion) {
1702
1684
  </div>
1703
1685
  </aside>
1704
1686
 
1705
- <main class="center reportsPage" id="graphPage">
1687
+ <main class="center reportsPage" role="main" id="graphPage">
1706
1688
  <div class="topbar">
1707
1689
  <div class="brandLine">
1708
1690
  <span class="spark"></span>
@@ -1764,7 +1746,7 @@ function buildCompanionHtml(safeDefault, appVersion) {
1764
1746
  <div class="frame">
1765
1747
  <div class="shell">
1766
1748
 
1767
- <aside class="rail">
1749
+ <aside class="rail" role="navigation" aria-label="Menu principal">
1768
1750
  <div class="railTop">
1769
1751
  <div class="railLogo">F</div>
1770
1752
  </div>
@@ -1793,7 +1775,7 @@ function buildCompanionHtml(safeDefault, appVersion) {
1793
1775
  </div>
1794
1776
  </aside>
1795
1777
 
1796
- <main class="center reportsPage" id="healthPage">
1778
+ <main class="center reportsPage" role="main" id="healthPage">
1797
1779
  <div class="topbar">
1798
1780
  <div class="brandLine">
1799
1781
  <span class="spark"></span>
@@ -3936,7 +3918,7 @@ function buildSettingsHtml(safeDefault, appVersion) {
3936
3918
  <div class="frame">
3937
3919
  <div class="shell">
3938
3920
 
3939
- <aside class="rail">
3921
+ <aside class="rail" role="navigation" aria-label="Menu principal">
3940
3922
  <div class="railTop">
3941
3923
  <div class="railLogo">F</div>
3942
3924
  </div>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cccarv82/freya",
3
- "version": "2.13.0",
3
+ "version": "2.13.1",
4
4
  "description": "Personal AI Assistant with local-first persistence",
5
5
  "scripts": {
6
6
  "health": "node scripts/validate-data.js && node scripts/validate-structure.js",
@@ -35,4 +35,4 @@
35
35
  "pdf-lib": "^1.17.1",
36
36
  "sql.js": "^1.12.0"
37
37
  }
38
- }
38
+ }