@cccarv82/freya 2.3.13 → 2.5.0

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 (37) hide show
  1. package/.agent/rules/freya/agents/coach.mdc +7 -16
  2. package/.agent/rules/freya/agents/ingestor.mdc +1 -89
  3. package/.agent/rules/freya/agents/master.mdc +3 -0
  4. package/.agent/rules/freya/agents/oracle.mdc +7 -23
  5. package/cli/web-ui.css +965 -182
  6. package/cli/web-ui.js +551 -173
  7. package/cli/web.js +863 -536
  8. package/package.json +7 -4
  9. package/scripts/build-vector-index.js +85 -0
  10. package/scripts/export-obsidian.js +6 -16
  11. package/scripts/generate-blockers-report.js +5 -17
  12. package/scripts/generate-daily-summary.js +25 -58
  13. package/scripts/generate-executive-report.js +22 -204
  14. package/scripts/generate-sm-weekly-report.js +27 -92
  15. package/scripts/lib/DataLayer.js +92 -0
  16. package/scripts/lib/DataManager.js +198 -0
  17. package/scripts/lib/Embedder.js +59 -0
  18. package/scripts/lib/schema.js +23 -0
  19. package/scripts/migrate-v1-v2.js +184 -0
  20. package/scripts/validate-data.js +48 -51
  21. package/scripts/validate-structure.js +12 -58
  22. package/templates/base/scripts/build-vector-index.js +85 -0
  23. package/templates/base/scripts/export-obsidian.js +143 -0
  24. package/templates/base/scripts/generate-daily-summary.js +25 -58
  25. package/templates/base/scripts/generate-executive-report.js +14 -225
  26. package/templates/base/scripts/generate-sm-weekly-report.js +9 -91
  27. package/templates/base/scripts/index/build-index.js +13 -0
  28. package/templates/base/scripts/index/update-index.js +15 -0
  29. package/templates/base/scripts/lib/DataLayer.js +92 -0
  30. package/templates/base/scripts/lib/DataManager.js +198 -0
  31. package/templates/base/scripts/lib/Embedder.js +59 -0
  32. package/templates/base/scripts/lib/index-utils.js +407 -0
  33. package/templates/base/scripts/lib/schema.js +23 -0
  34. package/templates/base/scripts/lib/search-utils.js +183 -0
  35. package/templates/base/scripts/migrate-v1-v2.js +184 -0
  36. package/templates/base/scripts/validate-data.js +48 -51
  37. package/templates/base/scripts/validate-structure.js +10 -32
package/cli/web.js CHANGED
@@ -8,6 +8,8 @@ const { spawn } = require('child_process');
8
8
  const { searchWorkspace } = require('../scripts/lib/search-utils');
9
9
  const { searchIndex } = require('../scripts/lib/index-utils');
10
10
  const { initWorkspace } = require('./init');
11
+ const { defaultInstance: dl } = require('../scripts/lib/DataLayer');
12
+ const DataManager = require('../scripts/lib/DataManager');
11
13
 
12
14
  function readAppVersion() {
13
15
  const pkgPath = path.join(__dirname, '..', 'package.json');
@@ -943,6 +945,153 @@ function timelineHtml(defaultDir) {
943
945
  return buildTimelineHtml(safeDefault, APP_VERSION);
944
946
  }
945
947
 
948
+ function settingsHtml(defaultDir) {
949
+ const safeDefault = String(defaultDir || './freya').replace(/\\/g, '\\\\').replace(/"/g, '\\"');
950
+ return buildSettingsHtml(safeDefault, APP_VERSION);
951
+ }
952
+
953
+ function docsHtml(defaultDir) {
954
+ const safeDefault = String(defaultDir || './freya').replace(/\\/g, '\\\\').replace(/"/g, '\\"');
955
+ return buildDocsHtml(safeDefault, APP_VERSION);
956
+ }
957
+
958
+ function buildDocsHtml(safeDefault, appVersion) {
959
+ const safeVersion = escapeHtml(appVersion || 'unknown');
960
+ return `<!doctype html>
961
+ <html>
962
+ <head>
963
+ <meta charset="utf-8" />
964
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
965
+ <title>FREYA Documentation</title>
966
+ <link rel="stylesheet" href="/app.css" />
967
+ <style>
968
+ .docs-content {
969
+ max-width: 900px;
970
+ margin: 0 auto;
971
+ padding: 40px 20px;
972
+ line-height: 1.6;
973
+ }
974
+ .docs-card {
975
+ background: var(--paper);
976
+ border: 1px solid var(--border);
977
+ border-radius: 12px;
978
+ padding: 30px;
979
+ margin-bottom: 24px;
980
+ box-shadow: 0 4px 20px rgba(0,0,0,0.05);
981
+ }
982
+ .docs-h1 { color: var(--primary); margin-top: 0; }
983
+ .docs-h2 { border-bottom: 1px solid var(--border); padding-bottom: 8px; margin-top: 32px; color: var(--text); }
984
+ .docs-code { background: var(--bg2); padding: 2px 6px; border-radius: 4px; font-family: monospace; font-size: 0.9em; }
985
+ .docs-pre { background: var(--bg2); padding: 16px; border-radius: 8px; overflow-x: auto; font-family: monospace; border: 1px solid var(--border); }
986
+ .docs-ul { padding-left: 20px; }
987
+ .docs-ul li { margin-bottom: 10px; }
988
+ .docs-strong { color: var(--accent); }
989
+ </style>
990
+ </head>
991
+ <body data-page="docs">
992
+ <div class="app">
993
+ <div class="frame">
994
+ <div class="shell">
995
+
996
+ <aside class="rail">
997
+ <div class="railTop">
998
+ <div class="railLogo">F</div>
999
+ </div>
1000
+ <div class="railNav">
1001
+ <button class="railBtn" id="railDashboard" type="button" title="Dashboard">
1002
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="7" height="9"></rect><rect x="14" y="3" width="7" height="5"></rect><rect x="14" y="12" width="7" height="9"></rect><rect x="3" y="16" width="7" height="5"></rect></svg>
1003
+ </button>
1004
+ <button class="railBtn" id="railReports" type="button" title="Relatórios">
1005
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path><polyline points="14 2 14 8 20 8"></polyline><line x1="16" y1="13" x2="8" y2="13"></line><line x1="16" y1="17" x2="8" y2="17"></line><polyline points="10 9 9 9 8 9"></polyline></svg>
1006
+ </button>
1007
+ <button class="railBtn" id="railCompanion" type="button" title="Companion">
1008
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><polygon points="16.24 7.76 14.12 14.12 7.76 16.24 9.88 9.88 16.24 7.76"></polygon></svg>
1009
+ </button>
1010
+ <button class="railBtn" id="railProjects" type="button" title="Projects">
1011
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"></path></svg>
1012
+ </button>
1013
+ <button class="railBtn" id="railTimeline" type="button" title="Timeline">
1014
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><polyline points="12 6 12 12 16 14"></polyline></svg>
1015
+ </button>
1016
+ <button class="railBtn" id="railGraph" type="button" title="Grafo">
1017
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="18" cy="5" r="3"></circle><circle cx="6" cy="12" r="3"></circle><circle cx="18" cy="19" r="3"></circle><line x1="8.59" y1="13.51" x2="15.42" y2="17.49"></line><line x1="15.41" y1="6.51" x2="8.59" y2="10.49"></line></svg>
1018
+ </button>
1019
+ <button class="railBtn active" id="railDocs" type="button" title="Documentação">
1020
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"></path><path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z"></path></svg>
1021
+ </button>
1022
+ </div>
1023
+ <div class="railBottom">
1024
+ <button id="railSettings" class="railBtn" title="Configurações" onclick="if (document.body.dataset.page !== 'settings') window.location.href='/settings';">
1025
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"></circle><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"></path></svg>
1026
+ </button>
1027
+ </div>
1028
+ </aside>
1029
+
1030
+ <main class="center">
1031
+ <div class="topbar">
1032
+ <div class="brandLine">
1033
+ <span class="spark"></span>
1034
+ <div class="brandStack">
1035
+ <div class="brand">FREYA</div>
1036
+ <div class="brandSub">Documentação Oficial</div>
1037
+ </div>
1038
+ </div>
1039
+ <div class="topActions">
1040
+ <span class="chip clickable" onclick="window.location.href='/docs'" style="background: var(--primary); color: white;">Docs</span>
1041
+ <span class="chip" id="chipVersion">v${safeVersion}</span>
1042
+ <span class="chip" id="chipPort">127.0.0.1:3872</span>
1043
+ </div>
1044
+ </div>
1045
+
1046
+ <div class="centerBody">
1047
+ <div class="docs-content">
1048
+ <div class="docs-card">
1049
+ <h1 class="docs-h1">📚 Guia do Usuário FREYA</h1>
1050
+ <p>Bem-vindo à FREYA (<strong class="docs-strong">Fast Reporting & Executive Yield Assistant</strong>), sua ferramenta local-first para gestão de status e relatórios executivos.</p>
1051
+
1052
+ <h2 class="docs-h2">🚀 Começo Rápido (CLI)</h2>
1053
+ <p>A FREYA nasceu no terminal. Use os comandos abaixo no seu diretório de trabalho:</p>
1054
+ <ul class="docs-ul">
1055
+ <li><code class="docs-code">freya task "Sua tarefa"</code>: Cria uma tarefa pendente.</li>
1056
+ <li><code class="docs-code">freya blocker "Impedimento"</code>: Registra um bloqueio crítico.</li>
1057
+ <li><code class="docs-code">freya status "Update"</code>: Adiciona item ao histórico de um projeto.</li>
1058
+ <li><code class="docs-code">freya daily</code>: Abre seu log diário para edição.</li>
1059
+ </ul>
1060
+
1061
+ <h2 class="docs-h2">💻 Dashboard Web</h2>
1062
+ <ul class="docs-ul">
1063
+ <li><strong class="docs-strong">Inbox & Prompt</strong>: Cole textos e a FREYA extrai tarefas/bloqueios via análise inteligente.</li>
1064
+ <li><strong class="docs-strong">Oracle (RAG)</strong>: Use o chat para perguntar sobre seu histórico local.</li>
1065
+ <li><strong class="docs-strong">Hoje</strong>: Visualize suas pendências por categoria (DO_NOW, SCHEDULE, etc).</li>
1066
+ </ul>
1067
+
1068
+ <h2 class="docs-h2">📊 Relatórios Automatizados</h2>
1069
+ <p>Gere documentos Markdown prontos para uso:</p>
1070
+ <ul class="docs-ul">
1071
+ <li><strong class="docs-strong">Executivo</strong>: Visão geral para gerência.</li>
1072
+ <li><strong class="docs-strong">SM Weekly</strong>: Indicadores de fluxo e aging.</li>
1073
+ <li><strong class="docs-strong">Blockers</strong>: Lista focada em impedimentos.</li>
1074
+ </ul>
1075
+ <div class="docs-pre">Dica: Use os Webhooks para publicar direto no Discord ou Microsoft Teams.</div>
1076
+
1077
+ <h2 class="docs-h2">⚙️ Configurações</h2>
1078
+ <p>Configure sua workspace, webhooks de notificação e regras de <strong class="docs-strong">Smart Mapping</strong> para associar updates a projetos automaticamente.</p>
1079
+ </div>
1080
+ </div>
1081
+ </div>
1082
+ </main>
1083
+ </div>
1084
+ </div>
1085
+ </div>
1086
+
1087
+ <script>
1088
+ window.__FREYA_DEFAULT_DIR = "${safeDefault}";
1089
+ </script>
1090
+ <script src="/app.js"></script>
1091
+ </body>
1092
+ </html>`;
1093
+ }
1094
+
946
1095
  function buildHtml(safeDefault, appVersion) {
947
1096
  const safeVersion = escapeHtml(appVersion || 'unknown');
948
1097
  return `<!doctype html>
@@ -963,15 +1112,30 @@ function buildHtml(safeDefault, appVersion) {
963
1112
  <div class="railLogo">F</div>
964
1113
  </div>
965
1114
  <div class="railNav">
966
- <button class="railBtn active" id="railDashboard" type="button" title="Dashboard">D</button>
967
- <button class="railBtn" id="railReports" type="button" title="Relatórios">R</button>
968
- <button class="railBtn" id="railCompanion" type="button" title="Companion">C</button>
969
- <button class="railBtn" id="railProjects" type="button" title="Projects">P</button>
970
- <button class="railBtn" id="railTimeline" type="button" title="Timeline">T</button>
971
- <button class="railBtn" id="railGraph" type="button" title="Grafo">G</button>
1115
+ <button class="railBtn active" id="railDashboard" type="button" title="Dashboard">
1116
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="7" height="9"></rect><rect x="14" y="3" width="7" height="5"></rect><rect x="14" y="12" width="7" height="9"></rect><rect x="3" y="16" width="7" height="5"></rect></svg>
1117
+ </button>
1118
+ <button class="railBtn" id="railReports" type="button" title="Relatórios">
1119
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path><polyline points="14 2 14 8 20 8"></polyline><line x1="16" y1="13" x2="8" y2="13"></line><line x1="16" y1="17" x2="8" y2="17"></line><polyline points="10 9 9 9 8 9"></polyline></svg>
1120
+ </button>
1121
+ <button class="railBtn" id="railCompanion" type="button" title="Companion">
1122
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><polygon points="16.24 7.76 14.12 14.12 7.76 16.24 9.88 9.88 16.24 7.76"></polygon></svg>
1123
+ </button>
1124
+ <button class="railBtn" id="railProjects" type="button" title="Projects">
1125
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"></path></svg>
1126
+ </button>
1127
+ <button class="railBtn" id="railTimeline" type="button" title="Timeline">
1128
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><polyline points="12 6 12 12 16 14"></polyline></svg>
1129
+ </button>
1130
+ <button class="railBtn" id="railGraph" type="button" title="Grafo">
1131
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="18" cy="5" r="3"></circle><circle cx="6" cy="12" r="3"></circle><circle cx="18" cy="19" r="3"></circle><line x1="8.59" y1="13.51" x2="15.42" y2="17.49"></line><line x1="15.41" y1="6.51" x2="8.59" y2="10.49"></line></svg>
1132
+ </button>
1133
+ <button class="railBtn" id="railDocs" type="button" title="Documentação">
1134
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"></path><path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z"></path></svg>
1135
+ </button>
972
1136
  </div>
973
- <div class="railBottom">
974
- <div class="railStatus" id="railStatus" title="status"></div>
1137
+ <div class="railBottom">\n <button id="railSettings" class="railBtn" title="Configurações" onclick="if (document.body.dataset.page !== \'settings\') window.location.href=\'/settings\';"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"></circle><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"></path></svg></button>
1138
+
975
1139
  </div>
976
1140
  </aside>
977
1141
 
@@ -985,66 +1149,80 @@ function buildHtml(safeDefault, appVersion) {
985
1149
  </div>
986
1150
  </div>
987
1151
  <div class="topActions">
1152
+ <span class="chip clickable" onclick="window.location.href='/docs'">Docs</span>
988
1153
  <span class="chip" id="chipVersion">v${safeVersion}</span>
989
1154
  <span class="chip" id="chipPort">127.0.0.1:3872</span>
990
1155
  </div>
991
1156
  </div>
992
1157
 
993
1158
  <div class="centerBody">
994
- <section class="promptShell">
995
- <div class="promptBar">
996
- <div class="promptMeta">
997
- <div class="promptTitle">Prompt</div>
998
- <div id="status" class="small">pronto</div>
999
- </div>
1000
- <textarea id="inboxText" rows="3" placeholder="Cole updates do dia (status, blockers, decisões, ideias) ou faça uma pergunta..."></textarea>
1001
- <div class="promptActions">
1002
- <button class="btn primary" type="button" onclick="saveAndPlan()">Salvar + Processar (Agents)</button>
1003
- <button class="btn" type="button" onclick="runSuggestedReports()">Rodar relatórios sugeridos</button>
1004
- <button class="btn" type="button" onclick="askFreya()">Perguntar à Freya</button>
1159
+ <section style="margin-bottom: 24px; display: grid; grid-template-columns: 1fr 1fr; gap: 16px; height: 380px; max-height: 450px;">
1160
+ <div class="promptBar" style="width: 100%; border-radius: 20px; height: 100%; display: flex; flex-direction: column; justify-content: space-between; overflow: hidden;">
1161
+ <div style="flex: 1; display: flex; flex-direction: column;">
1162
+ <div class="promptMeta">
1163
+ <div class="promptTitle" style="display: flex; align-items: center; gap: 8px;">
1164
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="color: var(--primary)"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path><polyline points="17 8 12 3 7 8"></polyline><line x1="12" y1="3" x2="12" y2="15"></line></svg>
1165
+ <span>Inbox & Prompt</span>
1166
+ </div>
1167
+ <div id="status" class="small">pronto</div>
1168
+ </div>
1169
+ <textarea id="inboxText" placeholder="Cole updates do dia (status, blockers, decisões, ideias) ou faça uma pergunta para a Freya..." style="resize:none; flex: 1; min-height: 0;"></textarea>
1005
1170
  </div>
1006
- <div class="promptToggles">
1007
- <label class="toggleRow">
1008
- <input id="autoApply" type="checkbox" checked style="width:auto" onchange="toggleAutoApply()" />
1009
- Auto-apply plan
1010
- </label>
1011
- <label class="toggleRow">
1012
- <input id="autoRunReports" type="checkbox" style="width:auto" onchange="toggleAutoRunReports()" />
1013
- Auto-run suggested reports
1014
- </label>
1171
+ <div style="display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 12px; margin-top: 12px;">
1172
+ <div class="promptActions" style="margin: 0;">
1173
+ <button class="btn primary small" type="button" onclick="saveAndPlan()">Salvar + Processar</button>
1174
+ <button class="btn small" type="button" onclick="runSuggestedReports()">Rodar relatórios sugeridos</button>
1175
+ </div>
1176
+ <div class="promptToggles" style="margin: 0;">
1177
+ <label class="toggleRow">
1178
+ <input id="autoApply" type="checkbox" checked style="width:auto; margin: 0;" onchange="toggleAutoApply()" />
1179
+ Auto-apply
1180
+ </label>
1181
+ <label class="toggleRow">
1182
+ <input id="autoRunReports" type="checkbox" style="width:auto; margin: 0;" onchange="toggleAutoRunReports()" />
1183
+ Auto-run reports
1184
+ </label>
1185
+ </div>
1015
1186
  </div>
1016
1187
  </div>
1017
- </section>
1018
1188
 
1019
- <section class="utilityGrid" id="reportsSection">
1020
- <div class="utilityCard">
1021
- <div class="utilityHead">Área de trabalho</div>
1022
- <div class="sidePath" id="sidePath">./freya</div>
1023
- <div class="row" style="grid-template-columns: 1fr auto">
1024
- <input id="dir" placeholder="./freya" />
1025
- <button class="btn small" type="button" onclick="pickDir()">Browse</button>
1189
+ <div class="panel" style="height: 100%; display: flex; flex-direction: column;">
1190
+ <div class="panelHead">
1191
+ <b style="display: flex; align-items: center; gap: 8px;">
1192
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"></path></svg>
1193
+ Consultar Oracle
1194
+ </b>
1195
+ <div class="stack">
1196
+ <button class="btn small" type="button" onclick="exportChatObsidian()">Exportar Log</button>
1197
+ </div>
1026
1198
  </div>
1027
- <div class="stack" style="margin-top:10px">
1028
- <button class="btn" type="button" onclick="doUpdate()">Sincronizar workspace</button>
1029
- <button class="btn" type="button" onclick="doMigrate()">Migrar dados</button>
1199
+ <div class="panelBody" style="flex:1; display:flex; flex-direction:column; padding:0; background:var(--glass-bg); min-height:0;">
1200
+ <div id="chatThread" style="flex:1; overflow-y:auto; overflow-x:hidden; padding:12px; display:flex; flex-direction:column; gap:8px;"></div>
1201
+ <div style="padding: 12px; border-top: 1px solid var(--glass-border); display:flex; gap: 8px; background: var(--paper);">
1202
+ <input type="text" id="oracleInput" autocomplete="off" style="flex:1;" placeholder="Pergunte algo à Freya..." onkeydown="if(event.key==='Enter') window.askFreyaInline()" />
1203
+ <button class="btn primary small" type="button" onclick="window.askFreyaInline()">Enviar</button>
1204
+ </div>
1030
1205
  </div>
1031
- <div style="height:10px"></div>
1032
- <div class="help"><b>Sync workspace</b>: atualiza scripts/templates/agents na pasta <code>freya</code> sem sobrescrever <code>data/</code> e <code>logs/</code>.</div>
1033
- <div class="help"><b>Migrate data</b>: ajusta formatos/schemaVersion quando uma versão nova exige.</div>
1034
1206
  </div>
1207
+ </section>
1035
1208
 
1036
- <div class="utilityCard">
1037
- <div class="utilityHead">Relatórios rápidos</div>
1038
- <div class="cardsMini">
1039
- <button class="miniCard" type="button" onclick="runReport('status')"><span class="miniIcon">E</span><span>Executivo</span></button>
1040
- <button class="miniCard" type="button" onclick="runReport('sm-weekly')"><span class="miniIcon">S</span><span>SM semanal</span></button>
1041
- <button class="miniCard" type="button" onclick="runReport('blockers')"><span class="miniIcon warn">B</span><span>Bloqueios</span></button>
1042
- <button class="miniCard" type="button" onclick="runReport('daily')"><span class="miniIcon">D</span><span>Daily</span></button>
1209
+ <section class="utilityGrid" id="dashboardControls" style="display:grid; grid-template-columns: 1fr; gap: 16px; margin-bottom: 24px;">
1210
+ <div class="utilityCard" style="display:flex; flex-wrap: wrap; justify-content: space-between; align-items: center; gap: 16px;">
1211
+ <div>
1212
+ <div class="utilityHead" style="margin-bottom: 4px; color: var(--text);">Relatórios Rápidos</div>
1213
+ <div class="help" style="margin: 0;">Gere relatórios sintéticos instantaneamente</div>
1214
+ </div>
1215
+ <div style="display:flex; gap: 8px; flex-wrap: wrap;">
1216
+ <button class="btn" style="min-width: 100px; padding: 6px 12px; font-weight: 500;" type="button" onclick="runReport('status')">Executivo</button>
1217
+ <button class="btn" style="min-width: 100px; padding: 6px 12px; font-weight: 500;" type="button" onclick="runReport('sm-weekly')">SM Semanal</button>
1218
+ <button class="btn" style="min-width: 100px; padding: 6px 12px; font-weight: 500; border-color: rgba(239, 68, 68, 0.4); color: #f87171;" type="button" onclick="runReport('blockers')">Bloqueios</button>
1219
+ <button class="btn" style="min-width: 100px; padding: 6px 12px; font-weight: 500;" type="button" onclick="runReport('daily')">Daily</button>
1043
1220
  </div>
1044
- <div class="help" style="margin-top:8px">Clique para gerar e atualizar o preview/publicação.</div>
1045
1221
  </div>
1046
1222
  </section>
1047
1223
 
1224
+
1225
+
1048
1226
  <div class="centerHead">
1049
1227
  <div>
1050
1228
  <h1 style="margin:0">Seu dia em um painel</h1>
@@ -1056,63 +1234,40 @@ function buildHtml(safeDefault, appVersion) {
1056
1234
  </div>
1057
1235
 
1058
1236
  <div class="midGrid">
1059
- <section class="panel">
1060
- <div class="panelHead">
1061
- <b>Hoje</b>
1237
+ <!-- Hoje Panel gets more prominent focus -->
1238
+ <section class="panel midSpan">
1239
+ <div class="panelHead" style="background: linear-gradient(90deg, var(--paper2), var(--paper)); border-left: 4px solid var(--accent)">
1240
+ <b style="color: var(--text); font-size: 14px;">Foco de Hoje</b>
1062
1241
  <div class="stack">
1063
1242
  <button class="btn small" type="button" onclick="refreshToday()">Atualizar</button>
1064
1243
  </div>
1065
1244
  </div>
1066
- <div class="panelBody panelScroll">
1067
- <div class="small" style="margin-bottom:8px; opacity:.8">Fazer agora</div>
1068
- <div id="tasksList" style="display:grid; gap:8px"></div>
1069
- <div style="height:12px"></div>
1070
- <div class="small" style="margin-bottom:8px; opacity:.8">Bloqueios abertos</div>
1071
- <div id="blockersList" style="display:grid; gap:8px"></div>
1072
- <div style="height:12px"></div>
1073
- <div class="small" style="margin-bottom:8px; opacity:.8">Insights de blockers</div>
1074
- <div id="blockersInsights" class="help"></div>
1075
- <div style="margin-top:8px"><button class="btn small" type="button" onclick="refreshBlockersInsights()">Atualizar insights</button></div>
1245
+ <div class="panelBody" style="display: flex; flex-direction: column; gap: 16px;">
1246
+ <div id="swimlaneContainer" style="display:flex; flex-direction: column; gap: 12px;"></div>
1247
+
1248
+ <div style="border-top: 1px solid var(--border); padding-top: 16px;">
1249
+ <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px;">
1250
+ <div class="small" style="opacity:.8; font-weight: 600; text-transform: uppercase;">Insights Globais de Bloqueios</div>
1251
+ <button class="btn small" type="button" onclick="refreshBlockersInsights()">Atualizar insights</button>
1252
+ </div>
1253
+ <div id="blockersInsights" class="help"></div>
1254
+ </div>
1076
1255
  </div>
1077
1256
  </section>
1078
1257
 
1079
1258
  <section class="panel">
1080
1259
  <div class="panelHead">
1081
- <b>Relatórios</b>
1260
+ <b>Relatórios Recentes</b>
1082
1261
  <div class="stack">
1083
1262
  <button class="btn small" type="button" onclick="refreshReports()">Atualizar</button>
1084
1263
  </div>
1085
1264
  </div>
1086
- <div class="panelBody panelScroll">
1087
- <input id="reportsFilter" placeholder="filter (ex: daily, executive, 2026-01-29)" style="width:100%; margin-bottom:10px" oninput="renderReportsList()" />
1265
+ <div class="panelBody panelScroll" style="max-height: 300px;">
1266
+ <input id="reportsFilter" placeholder="Filtrar relatórios..." style="width:100%; margin-bottom:10px" oninput="renderReportsList()" />
1088
1267
  <div id="reportsList" style="display:grid; gap:8px"></div>
1089
- <div class="help">Últimos relatórios em <code>docs/reports</code>. Clique para abrir o preview.</div>
1090
- </div>
1091
- </section>
1092
-
1093
- <section class="panel midSpan">
1094
- <div class="panelHead">
1095
- <b>Preview</b>
1096
- <div class="stack">
1097
- <button class="btn small" type="button" onclick="copyOut()">Copy</button>
1098
- <button class="btn small" type="button" onclick="applyPlan()">Apply plan</button>
1099
- <button class="btn small" type="button" onclick="copyPath()">Copy path</button>
1100
- <button class="btn small" type="button" onclick="openSelected()">Open file</button>
1101
- <button class="btn small" type="button" onclick="downloadSelected()">Download .md</button>
1102
- <button class="btn small" type="button" onclick="clearOut()">Clear</button>
1103
- </div>
1104
- </div>
1105
- <div class="panelBody">
1106
- <div id="reportPreview" class="log md" style="font-family: var(--sans);"></div>
1107
- <div class="help">O preview renderiza Markdown básico (headers, listas, code). O botão Copy copia o conteúdo completo.</div>
1108
1268
  </div>
1109
1269
  </section>
1110
1270
  </div>
1111
-
1112
- <details class="devDrawer" id="devDrawer">
1113
- <summary>Developer (modo avançado)</summary>
1114
- <div class="devBody">
1115
- <div class="devGrid">
1116
1271
  <div class="panel">
1117
1272
  <div class="panelHead"><b>Configurações de publicação</b></div>
1118
1273
  <div class="panelBody">
@@ -1128,61 +1283,6 @@ function buildHtml(safeDefault, appVersion) {
1128
1283
  <label style="display:flex; align-items:center; gap:10px; user-select:none; margin: 6px 0 12px 0">
1129
1284
  <input id="prettyPublish" type="checkbox" checked style="width:auto" onchange="togglePrettyPublish()" />
1130
1285
  Publicação bonita (cards/embeds)
1131
- </label>
1132
-
1133
- <div class="stack">
1134
- <button class="btn" type="button" onclick="saveSettings()">Salvar configurações</button>
1135
- <button class="btn" type="button" onclick="publish('discord')">Publicar selecionado → Discord</button>
1136
- <button class="btn" type="button" onclick="publish('teams')">Publicar selecionado → Teams</button>
1137
- </div>
1138
- </div>
1139
- </div>
1140
-
1141
- <div class="panel">
1142
- <div class="panelHead"><b>Slugs & Export</b></div>
1143
- <div class="panelBody">
1144
- <label>Regras de slug do projeto</label>
1145
- <textarea id="slugRules" rows="8" placeholder="{ \"rules\": [ { \"contains\": \"fideliza\", \"slug\": \"vivo/fidelizacao\" } ] }"></textarea>
1146
- <div class="help">Regras usadas pra inferir <code>projectSlug</code>. Formato JSON (objeto com <code>rules</code>).</div>
1147
- <div class="stack" style="margin-top:10px">
1148
- <button class="btn" type="button" onclick="reloadSlugRules()">Recarregar regras</button>
1149
- <button class="btn" type="button" onclick="saveSlugRules()">Salvar regras</button>
1150
- <button class="btn" type="button" onclick="exportObsidian()">Exportar notas (Obsidian)</button>
1151
- </div>
1152
- </div>
1153
- </div>
1154
-
1155
- <div class="panel">
1156
- <div class="panelHead"><b>Debug</b></div>
1157
- <div class="panelBody">
1158
- <div class="help">Logs ficam em <code>logs/</code> e debug traces em <code>.debuglogs/</code> dentro da workspace.</div>
1159
- <div class="help">Use <b>Open file</b> / <b>Copy path</b> no Preview para abrir/compartilhar o relatório selecionado.</div>
1160
- <div class="stack" style="margin-top:10px">
1161
- <button class="btn" type="button" onclick="rebuildIndex()">Rebuild search index</button>
1162
- </div>
1163
- </div>
1164
- </div>
1165
- </div>
1166
- </div>
1167
- </details>
1168
- </div>
1169
- </main>
1170
-
1171
- <aside class="chatPane">
1172
- <div class="chatHead">
1173
- <div>
1174
- <div class="chatTitle">Conversa</div>
1175
- <div class="chatSub">Cole seus updates e deixe os Agents planejar/aplicar.</div>
1176
- </div>
1177
- </div>
1178
-
1179
- <div class="chatThread" id="chatThread">
1180
- <div class="bubble assistant">
1181
- <div class="bubbleMeta">FREYA</div>
1182
- <div class="bubbleBody">Cole seus updates (status, blockers, decisões, ideias) e clique em <b>Save + Process</b>.</div>
1183
- </div>
1184
- </div>
1185
- </aside>
1186
1286
 
1187
1287
  </div>
1188
1288
  </div>
@@ -1216,15 +1316,30 @@ function buildReportsHtml(safeDefault, appVersion) {
1216
1316
  <div class="railLogo">F</div>
1217
1317
  </div>
1218
1318
  <div class="railNav">
1219
- <button class="railBtn" id="railDashboard" type="button" title="Dashboard">D</button>
1220
- <button class="railBtn active" id="railReports" type="button" title="Relatórios">R</button>
1221
- <button class="railBtn" id="railCompanion" type="button" title="Companion">C</button>
1222
- <button class="railBtn" id="railProjects" type="button" title="Projects">P</button>
1223
- <button class="railBtn" id="railTimeline" type="button" title="Timeline">T</button>
1224
- <button class="railBtn" id="railGraph" type="button" title="Grafo">G</button>
1319
+ <button class="railBtn" id="railDashboard" type="button" title="Dashboard">
1320
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="7" height="9"></rect><rect x="14" y="3" width="7" height="5"></rect><rect x="14" y="12" width="7" height="9"></rect><rect x="3" y="16" width="7" height="5"></rect></svg>
1321
+ </button>
1322
+ <button class="railBtn active" id="railReports" type="button" title="Relatórios">
1323
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path><polyline points="14 2 14 8 20 8"></polyline><line x1="16" y1="13" x2="8" y2="13"></line><line x1="16" y1="17" x2="8" y2="17"></line><polyline points="10 9 9 9 8 9"></polyline></svg>
1324
+ </button>
1325
+ <button class="railBtn" id="railCompanion" type="button" title="Companion">
1326
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><polygon points="16.24 7.76 14.12 14.12 7.76 16.24 9.88 9.88 16.24 7.76"></polygon></svg>
1327
+ </button>
1328
+ <button class="railBtn" id="railProjects" type="button" title="Projects">
1329
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"></path></svg>
1330
+ </button>
1331
+ <button class="railBtn" id="railTimeline" type="button" title="Timeline">
1332
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><polyline points="12 6 12 12 16 14"></polyline></svg>
1333
+ </button>
1334
+ <button class="railBtn" id="railGraph" type="button" title="Grafo">
1335
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="18" cy="5" r="3"></circle><circle cx="6" cy="12" r="3"></circle><circle cx="18" cy="19" r="3"></circle><line x1="8.59" y1="13.51" x2="15.42" y2="17.49"></line><line x1="15.41" y1="6.51" x2="8.59" y2="10.49"></line></svg>
1336
+ </button>
1337
+ <button class="railBtn" id="railDocs" type="button" title="Documentação">
1338
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"></path><path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z"></path></svg>
1339
+ </button>
1225
1340
  </div>
1226
- <div class="railBottom">
1227
- <div class="railStatus" id="railStatus" title="status"></div>
1341
+ <div class="railBottom">\n <button id="railSettings" class="railBtn" title="Configurações">\n <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"></circle><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"></path></svg>\n </button>
1342
+
1228
1343
  </div>
1229
1344
  </aside>
1230
1345
 
@@ -1238,6 +1353,7 @@ function buildReportsHtml(safeDefault, appVersion) {
1238
1353
  </div>
1239
1354
  </div>
1240
1355
  <div class="topActions">
1356
+ <span class="chip clickable" onclick="window.location.href='/docs'">Docs</span>
1241
1357
  <span class="chip" id="chipVersion">v${safeVersion}</span>
1242
1358
  <span class="chip" id="chipPort">127.0.0.1:3872</span>
1243
1359
  </div>
@@ -1256,8 +1372,12 @@ function buildReportsHtml(safeDefault, appVersion) {
1256
1372
  </div>
1257
1373
  </section>
1258
1374
 
1259
- <section class="reportsTools">
1260
- <input id="reportsFilter" placeholder="filtrar (ex: daily, executive, 2026-01-29)" oninput="renderReportsPage()" />
1375
+ <section class="reportsTools" style="display:flex; gap:12px; align-items:center;">
1376
+ <div class="tabs" style="display:flex; gap:8px;" id="reportsTabs">
1377
+ <button class="pill ok" id="tabChrono" onclick="setReportsTab('chrono')">Cronológico</button>
1378
+ <button class="pill" id="tabType" onclick="setReportsTab('type')">Por Tipo</button>
1379
+ </div>
1380
+ <input id="reportsFilter" style="flex:1" placeholder="filtrar (ex: daily, executive, 2026-01-29)" oninput="renderReportsPage()" />
1261
1381
  </section>
1262
1382
 
1263
1383
  <section class="reportsGrid" id="reportsGrid"></section>
@@ -1296,15 +1416,29 @@ function buildProjectsHtml(safeDefault, appVersion) {
1296
1416
  <div class="railLogo">F</div>
1297
1417
  </div>
1298
1418
  <div class="railNav">
1299
- <button class="railBtn" id="railDashboard" type="button" title="Dashboard">D</button>
1300
- <button class="railBtn" id="railReports" type="button" title="Relatorios">R</button>
1301
- <button class="railBtn" id="railCompanion" type="button" title="Companion">C</button>
1302
- <button class="railBtn active" id="railProjects" type="button" title="Projects">P</button>
1303
- <button class="railBtn" id="railTimeline" type="button" title="Timeline">T</button>
1304
- <button class="railBtn" id="railGraph" type="button" title="Grafo">G</button>
1419
+ <button class="railBtn" id="railDashboard" type="button" title="Dashboard">
1420
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="7" height="9"></rect><rect x="14" y="3" width="7" height="5"></rect><rect x="14" y="12" width="7" height="9"></rect><rect x="3" y="16" width="7" height="5"></rect></svg>
1421
+ </button>
1422
+ <button class="railBtn" id="railReports" type="button" title="Relatorios">
1423
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path><polyline points="14 2 14 8 20 8"></polyline><line x1="16" y1="13" x2="8" y2="13"></line><line x1="16" y1="17" x2="8" y2="17"></line><polyline points="10 9 9 9 8 9"></polyline></svg>
1424
+ </button>
1425
+ <button class="railBtn" id="railCompanion" type="button" title="Companion">
1426
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><polygon points="16.24 7.76 14.12 14.12 7.76 16.24 9.88 9.88 16.24 7.76"></polygon></svg>
1427
+ </button>
1428
+ <button class="railBtn active" id="railProjects" type="button" title="Projects">
1429
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"></path></svg>
1430
+ </button>
1431
+ <button class="railBtn" id="railTimeline" type="button" title="Timeline">
1432
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><polyline points="12 6 12 12 16 14"></polyline></svg>
1433
+ </button>
1434
+ <button class="railBtn" id="railGraph" type="button" title="Grafo">
1435
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="18" cy="5" r="3"></circle><circle cx="6" cy="12" r="3"></circle><circle cx="18" cy="19" r="3"></circle><line x1="8.59" y1="13.51" x2="15.42" y2="17.49"></line><line x1="15.41" y1="6.51" x2="8.59" y2="10.49"></line></svg>
1436
+ </button>
1437
+ <button class="railBtn" id="railDocs" type="button" title="Documentação">
1438
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"></path><path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z"></path></svg>
1439
+ </button>
1305
1440
  </div>
1306
- <div class="railBottom">
1307
- <div class="railStatus" id="railStatus" title="status"></div>
1441
+ <div class="railBottom">\n <button id="railSettings" class="railBtn" title="Configurações" onclick="if (document.body.dataset.page !== 'settings') window.location.href='/settings';"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"></circle><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"></path></svg></button>\n
1308
1442
  </div>
1309
1443
  </aside>
1310
1444
 
@@ -1318,6 +1452,7 @@ function buildProjectsHtml(safeDefault, appVersion) {
1318
1452
  </div>
1319
1453
  </div>
1320
1454
  <div class="topActions">
1455
+ <span class="chip clickable" onclick="window.location.href='/docs'">Docs</span>
1321
1456
  <span class="chip" id="chipVersion">v${safeVersion}</span>
1322
1457
  <span class="chip" id="chipPort">127.0.0.1:3872</span>
1323
1458
  </div>
@@ -1376,12 +1511,27 @@ function buildTimelineHtml(safeDefault, appVersion) {
1376
1511
  <div class=\"railLogo\">F</div>
1377
1512
  </div>
1378
1513
  <div class=\"railNav\">
1379
- <button class=\"railBtn\" id=\"railDashboard\" type=\"button\" title=\"Dashboard\">D</button>
1380
- <button class=\"railBtn\" id=\"railReports\" type=\"button\" title=\"Relatorios\">R</button>
1381
- <button class=\"railBtn\" id=\"railCompanion\" type=\"button\" title=\"Companion\">C</button>
1382
- <button class=\"railBtn\" id=\"railProjects\" type=\"button\" title=\"Projects\">P</button>
1383
- <button class=\"railBtn active\" id=\"railTimeline\" type=\"button\" title=\"Timeline\">T</button>
1384
- <button class=\"railBtn\" id=\"railGraph\" type=\"button\" title=\"Grafo\">G</button>
1514
+ <button class=\"railBtn\" id=\"railDashboard\" type=\"button\" title=\"Dashboard\">
1515
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="7" height="9"></rect><rect x="14" y="3" width="7" height="5"></rect><rect x="14" y="12" width="7" height="9"></rect><rect x="3" y="16" width="7" height="5"></rect></svg>
1516
+ </button>
1517
+ <button class=\"railBtn\" id=\"railReports\" type=\"button\" title=\"Relatorios\">
1518
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path><polyline points="14 2 14 8 20 8"></polyline><line x1="16" y1="13" x2="8" y2="13"></line><line x1="16" y1="17" x2="8" y2="17"></line><polyline points="10 9 9 9 8 9"></polyline></svg>
1519
+ </button>
1520
+ <button class=\"railBtn\" id=\"railCompanion\" type=\"button\" title=\"Companion\">
1521
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><polygon points="16.24 7.76 14.12 14.12 7.76 16.24 9.88 9.88 16.24 7.76"></polygon></svg>
1522
+ </button>
1523
+ <button class=\"railBtn\" id=\"railProjects\" type=\"button\" title=\"Projects\">
1524
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"></path></svg>
1525
+ </button>
1526
+ <button class=\"railBtn active\" id=\"railTimeline\" type=\"button\" title=\"Timeline\">
1527
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><polyline points="12 6 12 12 16 14"></polyline></svg>
1528
+ </button>
1529
+ <button class=\"railBtn\" id=\"railGraph\" type=\"button\" title=\"Grafo\">
1530
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="18" cy="5" r="3"></circle><circle cx="6" cy="12" r="3"></circle><circle cx="18" cy="19" r="3"></circle><line x1="8.59" y1="13.51" x2="15.42" y2="17.49"></line><line x1="15.41" y1="6.51" x2="8.59" y2="10.49"></line></svg>
1531
+ </button>
1532
+ <button class="railBtn" id="railDocs" type="button" title="Documentação">
1533
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"></path><path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z"></path></svg>
1534
+ </button>
1385
1535
  </div>
1386
1536
  <div class=\"railBottom\">
1387
1537
  <div class=\"railStatus\" id=\"railStatus\" title=\"status\"></div>
@@ -1398,6 +1548,7 @@ function buildTimelineHtml(safeDefault, appVersion) {
1398
1548
  </div>
1399
1549
  </div>
1400
1550
  <div class=\"topActions\">
1551
+ <span class="chip clickable" onclick="window.location.href='/docs'">Docs</span>
1401
1552
  <span class=\"chip\" id=\"chipVersion\">v${safeVersion}</span>
1402
1553
  <span class=\"chip\" id=\"chipPort\">127.0.0.1:3872</span>
1403
1554
  </div>
@@ -1471,15 +1622,29 @@ function buildGraphHtml(safeDefault, appVersion) {
1471
1622
  <div class="railLogo">F</div>
1472
1623
  </div>
1473
1624
  <div class="railNav">
1474
- <button class="railBtn" id="railDashboard" type="button" title="Dashboard">D</button>
1475
- <button class="railBtn" id="railReports" type="button" title="Relatorios">R</button>
1476
- <button class="railBtn" id="railCompanion" type="button" title="Companion">C</button>
1477
- <button class="railBtn" id="railProjects" type="button" title="Projects">P</button>
1478
- <button class="railBtn" id="railTimeline" type="button" title="Timeline">T</button>
1479
- <button class="railBtn active" id="railGraph" type="button" title="Grafo">G</button>
1625
+ <button class="railBtn" id="railDashboard" type="button" title="Dashboard">
1626
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="7" height="9"></rect><rect x="14" y="3" width="7" height="5"></rect><rect x="14" y="12" width="7" height="9"></rect><rect x="3" y="16" width="7" height="5"></rect></svg>
1627
+ </button>
1628
+ <button class="railBtn" id="railReports" type="button" title="Relatorios">
1629
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path><polyline points="14 2 14 8 20 8"></polyline><line x1="16" y1="13" x2="8" y2="13"></line><line x1="16" y1="17" x2="8" y2="17"></line><polyline points="10 9 9 9 8 9"></polyline></svg>
1630
+ </button>
1631
+ <button class="railBtn" id="railCompanion" type="button" title="Companion">
1632
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><polygon points="16.24 7.76 14.12 14.12 7.76 16.24 9.88 9.88 16.24 7.76"></polygon></svg>
1633
+ </button>
1634
+ <button class="railBtn" id="railProjects" type="button" title="Projects">
1635
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"></path></svg>
1636
+ </button>
1637
+ <button class="railBtn" id="railTimeline" type="button" title="Timeline">
1638
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><polyline points="12 6 12 12 16 14"></polyline></svg>
1639
+ </button>
1640
+ <button class="railBtn active" id="railGraph" type="button" title="Grafo">
1641
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="18" cy="5" r="3"></circle><circle cx="6" cy="12" r="3"></circle><circle cx="18" cy="19" r="3"></circle><line x1="8.59" y1="13.51" x2="15.42" y2="17.49"></line><line x1="15.41" y1="6.51" x2="8.59" y2="10.49"></line></svg>
1642
+ </button>
1643
+ <button class="railBtn" id="railDocs" type="button" title="Documentação">
1644
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"></path><path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z"></path></svg>
1645
+ </button>
1480
1646
  </div>
1481
- <div class="railBottom">
1482
- <div class="railStatus" id="railStatus" title="status"></div>
1647
+ <div class="railBottom">\n <button id="railSettings" class="railBtn" title="Configurações" onclick="if (document.body.dataset.page !== 'settings') window.location.href='/settings';"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"></circle><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"></path></svg></button>\n
1483
1648
  </div>
1484
1649
  </aside>
1485
1650
 
@@ -1493,6 +1658,7 @@ function buildGraphHtml(safeDefault, appVersion) {
1493
1658
  </div>
1494
1659
  </div>
1495
1660
  <div class="topActions">
1661
+ <span class="chip clickable" onclick="window.location.href='/docs'">Docs</span>
1496
1662
  <span class="chip" id="chipVersion">v${safeVersion}</span>
1497
1663
  <span class="chip" id="chipPort">127.0.0.1:3872</span>
1498
1664
  </div>
@@ -1549,15 +1715,29 @@ function buildCompanionHtml(safeDefault, appVersion) {
1549
1715
  <div class="railLogo">F</div>
1550
1716
  </div>
1551
1717
  <div class="railNav">
1552
- <button class="railBtn" id="railDashboard" type="button" title="Dashboard">D</button>
1553
- <button class="railBtn" id="railReports" type="button" title="Relatórios">R</button>
1554
- <button class="railBtn active" id="railCompanion" type="button" title="Companion">C</button>
1555
- <button class="railBtn" id="railProjects" type="button" title="Projects">P</button>
1556
- <button class="railBtn" id="railTimeline" type="button" title="Timeline">T</button>
1557
- <button class="railBtn" id="railGraph" type="button" title="Grafo">G</button>
1718
+ <button class="railBtn" id="railDashboard" type="button" title="Dashboard">
1719
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="7" height="9"></rect><rect x="14" y="3" width="7" height="5"></rect><rect x="14" y="12" width="7" height="9"></rect><rect x="3" y="16" width="7" height="5"></rect></svg>
1720
+ </button>
1721
+ <button class="railBtn" id="railReports" type="button" title="Relatórios">
1722
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path><polyline points="14 2 14 8 20 8"></polyline><line x1="16" y1="13" x2="8" y2="13"></line><line x1="16" y1="17" x2="8" y2="17"></line><polyline points="10 9 9 9 8 9"></polyline></svg>
1723
+ </button>
1724
+ <button class="railBtn active" id="railCompanion" type="button" title="Companion">
1725
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><polygon points="16.24 7.76 14.12 14.12 7.76 16.24 9.88 9.88 16.24 7.76"></polygon></svg>
1726
+ </button>
1727
+ <button class="railBtn" id="railProjects" type="button" title="Projects">
1728
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"></path></svg>
1729
+ </button>
1730
+ <button class="railBtn" id="railTimeline" type="button" title="Timeline">
1731
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><polyline points="12 6 12 12 16 14"></polyline></svg>
1732
+ </button>
1733
+ <button class="railBtn" id="railGraph" type="button" title="Grafo">
1734
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="18" cy="5" r="3"></circle><circle cx="6" cy="12" r="3"></circle><circle cx="18" cy="19" r="3"></circle><line x1="8.59" y1="13.51" x2="15.42" y2="17.49"></line><line x1="15.41" y1="6.51" x2="8.59" y2="10.49"></line></svg>
1735
+ </button>
1736
+ <button class="railBtn" id="railDocs" type="button" title="Documentação">
1737
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"></path><path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z"></path></svg>
1738
+ </button>
1558
1739
  </div>
1559
- <div class="railBottom">
1560
- <div class="railStatus" id="railStatus" title="status"></div>
1740
+ <div class="railBottom">\n <button id="railSettings" class="railBtn" title="Configurações" onclick="if (document.body.dataset.page !== 'settings') window.location.href='/settings';"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"></circle><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"></path></svg></button>\n
1561
1741
  </div>
1562
1742
  </aside>
1563
1743
 
@@ -1571,6 +1751,7 @@ function buildCompanionHtml(safeDefault, appVersion) {
1571
1751
  </div>
1572
1752
  </div>
1573
1753
  <div class="topActions">
1754
+ <span class="chip clickable" onclick="window.location.href='/docs'">Docs</span>
1574
1755
  <span class="chip" id="chipVersion">v${safeVersion}</span>
1575
1756
  <span class="chip" id="chipPort">127.0.0.1:3872</span>
1576
1757
  </div>
@@ -1582,81 +1763,107 @@ function buildCompanionHtml(safeDefault, appVersion) {
1582
1763
  <section class="reportsHeader">
1583
1764
  <div>
1584
1765
  <div class="reportsTitle">Scrum Master Companion</div>
1585
- <div class="reportsSubtitle">Painel rapido para gerar relatorios e checar pendencias.</div>
1766
+ <div class="reportsSubtitle">Painel rápido para gerar relatórios e checar pendências.</div>
1586
1767
  </div>
1587
1768
  <div class="reportsActions">
1588
- <button class="btn small" type="button" onclick="refreshHealthChecklist()">Atualizar</button> </div>
1769
+ <button class="btn small" type="button" onclick="refreshHealthChecklist()">Atualizar Painel</button>
1770
+ </div>
1589
1771
  </section>
1590
1772
 
1591
- <section class="reportsTools" style="grid-template-columns: repeat(auto-fit,minmax(180px,1fr));">
1773
+ <section class="reportsTools" style="display: grid; grid-template-columns: repeat(auto-fit,minmax(180px,1fr)); gap: 12px; margin-bottom: 24px;">
1592
1774
  <button class="btn" type="button" onclick="runReport('daily')">Gerar Daily</button>
1593
1775
  <button class="btn" type="button" onclick="runReport('blockers')">Gerar Blockers</button>
1594
1776
  <button class="btn" type="button" onclick="runReport('status')">Gerar Status</button>
1595
1777
  <button class="btn" type="button" onclick="runReport('sm-weekly')">Gerar SM Weekly</button>
1596
1778
  </section>
1597
1779
 
1598
- <section class="reportsGrid" id="healthChecklist"></section>
1780
+ <!-- BENTO GRID LAYOUT -->
1781
+ <div style="display: grid; grid-template-columns: 1fr 1.5fr; gap: 24px; align-items: start;">
1782
+
1783
+ <!-- Left Column / Block 1 -->
1784
+ <div style="display: flex; flex-direction: column; gap: 16px;">
1785
+ <section class="panel">
1786
+ <div class="panelHead" style="display:flex; align-items:center; justify-content:space-between; gap:10px">
1787
+ <b>Resumo Executivo</b>
1788
+ <button class="btn small" type="button" onclick="refreshExecutiveSummary()">↻</button>
1789
+ </div>
1790
+ <div class="panelBody">
1791
+ <div id="executiveSummary" class="help"></div>
1792
+ </div>
1793
+ </section>
1794
+
1795
+ <section class="panel">
1796
+ <div class="panelHead" style="display:flex; align-items:center; justify-content:space-between; gap:10px">
1797
+ <b>Radar de Risco</b>
1798
+ <button class="btn small" type="button" onclick="refreshRiskRadar()">↻</button>
1799
+ </div>
1800
+ <div class="panelBody">
1801
+ <div id="riskRadarBox"></div>
1802
+ </div>
1803
+ </section>
1599
1804
 
1600
- <section class="panel" style="margin-top:16px">
1601
- <div class="panelHead" style="display:flex; align-items:center; justify-content:space-between; gap:10px">
1602
- <b>Qualidade de Log</b>
1603
- <button class="btn small" type="button" onclick="refreshQualityScore()">Atualizar</button>
1604
- </div>
1605
- <div class="panelBody">
1606
- <div id="qualityScoreCard"></div>
1805
+ <section class="panel">
1806
+ <div class="panelHead" style="display:flex; align-items:center; justify-content:space-between; gap:10px">
1807
+ <b>Task Heatmap</b>
1808
+ </div>
1809
+ <div class="panelBody">
1810
+ <div id="heatmapGrid"></div>
1811
+ </div>
1812
+ </section>
1607
1813
  </div>
1608
- </section>
1609
1814
 
1610
- <section class="panel" style="margin-top:16px">
1611
- <div class="panelHead" style="display:flex; align-items:center; justify-content:space-between; gap:10px">
1612
- <b>Resumo Executivo</b>
1613
- <button class="btn small" type="button" onclick="refreshExecutiveSummary()">Atualizar</button>
1614
- </div>
1615
- <div class="panelBody">
1616
- <div id="executiveSummary" class="help"></div>
1617
- </div>
1618
- </section>
1619
-
1620
- <section class="panel" style="margin-top:16px">
1621
- <div class="panelHead" style="display:flex; align-items:center; justify-content:space-between; gap:10px">
1622
- <b>Anomalias</b>
1623
- <button class="btn small" type="button" onclick="refreshAnomalies()">Atualizar</button>
1624
- </div>
1625
- <div class="panelBody">
1626
- <div id="anomaliesBox"></div>
1627
- </div>
1628
- </section>
1815
+ <!-- Right Column / Block 2 -->
1816
+ <div style="display: flex; flex-direction: column; gap: 16px;">
1817
+ <section class="panel">
1818
+ <div class="panelHead" style="display:flex; align-items:center; justify-content:space-between; gap:10px">
1819
+ <b style="color: var(--accent);">Saúde & Checklists</b>
1820
+ </div>
1821
+ <div class="panelBody" style="padding: 0;">
1822
+ <section class="reportsGrid" id="healthChecklist" style="gap: 0; border: none; box-shadow: none;"></section>
1823
+ </div>
1824
+ </section>
1629
1825
 
1630
- <section class="panel" style="margin-top:16px">
1631
- <div class="panelHead" style="display:flex; align-items:center; justify-content:space-between; gap:10px">
1632
- <b>Radar de Risco</b>
1633
- <button class="btn small" type="button" onclick="refreshRiskRadar()">Atualizar</button>
1634
- </div>
1635
- <div class="panelBody">
1636
- <div id="riskRadarBox"></div>
1637
- </div>
1638
- </section>
1826
+ <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 16px;">
1827
+ <section class="panel">
1828
+ <div class="panelHead" style="display:flex; align-items:center; justify-content:space-between; gap:10px">
1829
+ <b>Qualidade de Log</b>
1830
+ <button class="btn small" type="button" onclick="refreshQualityScore()">↻</button>
1831
+ </div>
1832
+ <div class="panelBody">
1833
+ <div id="qualityScoreCard"></div>
1834
+ </div>
1835
+ </section>
1639
1836
 
1640
- <section class="panel" style="margin-top:16px">
1641
- <div class="panelHead"><b>Incident Radar</b></div>
1642
- <div class="panelBody">
1643
- <div id="incidentsBox" class="log md" style="font-family: var(--sans);"></div>
1837
+ <section class="panel">
1838
+ <div class="panelHead" style="display:flex; align-items:center; justify-content:space-between; gap:10px">
1839
+ <b>Anomalias</b>
1840
+ <button class="btn small" type="button" onclick="refreshAnomalies()">↻</button>
1841
+ </div>
1842
+ <div class="panelBody">
1843
+ <div id="anomaliesBox"></div>
1844
+ </div>
1845
+ </section>
1846
+ </div>
1644
1847
  </div>
1645
- </section>
1848
+
1849
+ <!-- Full Width Span -->
1850
+ <div style="grid-column: 1 / -1; display: flex; flex-direction: column; gap: 16px;">
1851
+ <section class="panel">
1852
+ <div class="panelHead"><b>Incident Radar</b></div>
1853
+ <div class="panelBody">
1854
+ <div id="incidentsBox" class="log md" style="font-family: var(--sans);"></div>
1855
+ </div>
1856
+ </section>
1646
1857
 
1647
- <section class="panel" style="margin-top:16px">
1648
- <div class="panelHead"><b>Task Heatmap</b></div>
1649
- <div class="panelBody">
1650
- <div id="heatmapGrid"></div>
1858
+ <section class="panel">
1859
+ <div class="panelHead"><b>Saída de Relatório</b></div>
1860
+ <div class="panelBody">
1861
+ <div id="reportPreview" class="log md" style="font-family: var(--sans);"></div>
1862
+ </div>
1863
+ </section>
1651
1864
  </div>
1652
- </section>
1653
1865
 
1654
- <section class="panel" style="margin-top:16px">
1655
- <div class="panelHead"><b>Saida</b></div>
1656
- <div class="panelBody">
1657
- <div id="reportPreview" class="log md" style="font-family: var(--sans);"></div>
1658
- </div>
1659
- </section>
1866
+ </div>
1660
1867
  </div>
1661
1868
  </main>
1662
1869
 
@@ -2073,6 +2280,22 @@ async function cmdWeb({ port, dir, open, dev }) {
2073
2280
  return;
2074
2281
  }
2075
2282
 
2283
+ if (req.method === 'GET' && req.url === '/settings') {
2284
+ try { res.__freyaDebug.workspaceDir = normalizeWorkspaceDir(dir || './freya'); } catch { }
2285
+ const body = settingsHtml(dir || './freya');
2286
+ res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8', 'Cache-Control': 'no-store' });
2287
+ res.end(body);
2288
+ return;
2289
+ }
2290
+
2291
+ if (req.method === 'GET' && req.url === '/docs') {
2292
+ try { res.__freyaDebug.workspaceDir = normalizeWorkspaceDir(dir || './freya'); } catch { }
2293
+ const body = docsHtml(dir || './freya');
2294
+ res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8', 'Cache-Control': 'no-store' });
2295
+ res.end(body);
2296
+ return;
2297
+ }
2298
+
2076
2299
  if (req.method === 'GET' && req.url === '/app.css') {
2077
2300
  const css = fs.readFileSync(path.join(__dirname, 'web-ui.css'), 'utf8');
2078
2301
  res.writeHead(200, { 'Content-Type': 'text/css; charset=utf-8', 'Cache-Control': 'no-store' });
@@ -2166,34 +2389,23 @@ async function cmdWeb({ port, dir, open, dev }) {
2166
2389
  }
2167
2390
 
2168
2391
  if (req.url === '/api/projects/list') {
2169
- const base = path.join(workspaceDir, 'data', 'Clients');
2392
+ const rawProjects = dl.db.prepare('SELECT * FROM projects').all();
2170
2393
  const items = [];
2171
- if (exists(base)) {
2172
- const stack = [base];
2173
- while (stack.length) {
2174
- const dirp = stack.pop();
2175
- const entries = fs.readdirSync(dirp, { withFileTypes: true });
2176
- for (const ent of entries) {
2177
- const full = path.join(dirp, ent.name);
2178
- if (ent.isDirectory()) stack.push(full);
2179
- else if (ent.isFile() && ent.name === 'status.json') {
2180
- const doc = readJsonOrNull(full) || {};
2181
- const rel = path.relative(base, path.dirname(full)).replace(/\\/g, '/');
2182
- items.push({
2183
- slug: rel,
2184
- client: doc.client || null,
2185
- program: doc.program || null,
2186
- stream: doc.stream || null,
2187
- project: doc.project || null,
2188
- active: doc.active !== false,
2189
- currentStatus: doc.currentStatus || '',
2190
- lastUpdated: doc.lastUpdated || '',
2191
- tags: Array.isArray(doc.tags) ? doc.tags : [],
2192
- historyCount: Array.isArray(doc.history) ? doc.history.length : 0
2193
- });
2194
- }
2195
- }
2196
- }
2394
+ for (const p of rawProjects) {
2395
+ const h = dl.db.prepare('SELECT count(*) as count FROM project_status_history WHERE project_id = ?').get(p.id);
2396
+ const historyCount = h ? h.count : 0;
2397
+ const latestStatus = dl.db.prepare('SELECT status_text FROM project_status_history WHERE project_id = ? ORDER BY date DESC LIMIT 1').get(p.id);
2398
+
2399
+ items.push({
2400
+ slug: p.slug,
2401
+ client: p.client,
2402
+ project: p.name,
2403
+ active: p.is_active === 1,
2404
+ currentStatus: latestStatus ? latestStatus.status_text : '',
2405
+ lastUpdated: p.updated_at,
2406
+ historyCount: historyCount,
2407
+ tags: []
2408
+ });
2197
2409
  }
2198
2410
  items.sort((a, b) => String(b.lastUpdated || '').localeCompare(String(a.lastUpdated || '')));
2199
2411
  return safeJson(res, 200, { ok: true, projects: items });
@@ -2213,68 +2425,33 @@ async function cmdWeb({ port, dir, open, dev }) {
2213
2425
  };
2214
2426
 
2215
2427
  // 1. Load Projects
2216
- const base = path.join(workspaceDir, 'data', 'Clients');
2217
- if (exists(base)) {
2218
- const stack = [base];
2219
- while (stack.length) {
2220
- const dirp = stack.pop();
2221
- const entries = fs.readdirSync(dirp, { withFileTypes: true });
2222
- for (const ent of entries) {
2223
- const full = path.join(dirp, ent.name);
2224
- if (ent.isDirectory()) stack.push(full);
2225
- else if (ent.isFile() && ent.name === 'status.json') {
2226
- const doc = readJsonOrNull(full) || {};
2227
- const slug = path.relative(base, path.dirname(full)).replace(/\\/g, '/');
2228
- addNode(slug, slug, 'project');
2229
-
2230
- // Link tags
2231
- const tags = Array.isArray(doc.tags) ? doc.tags : [];
2232
- for (const t of tags) {
2233
- const tid = 'tag:' + String(t).trim().toLowerCase();
2234
- addNode(tid, String(t).trim(), 'tag');
2235
- edges.push({ from: slug, to: tid });
2236
- }
2237
- }
2238
- }
2239
- }
2428
+ const rawProjects = dl.db.prepare('SELECT slug FROM projects').all();
2429
+ for (const p of rawProjects) {
2430
+ if (p && p.slug) addNode(p.slug, p.slug, 'project');
2240
2431
  }
2241
2432
 
2242
2433
  // 2. Load Tasks
2243
- const taskFile = path.join(workspaceDir, 'data', 'tasks', 'task-log.json');
2244
- const taskDoc = readJsonOrNull(taskFile) || { tasks: [] };
2245
- const tasks = Array.isArray(taskDoc.tasks) ? taskDoc.tasks : [];
2246
-
2247
- for (const t of tasks) {
2248
- // Only show pending high/medium tasks for clarity, or unassigned ones.
2249
- if (t.status === 'COMPLETED') continue;
2250
-
2434
+ const rawTasks = dl.db.prepare('SELECT * FROM tasks WHERE status != "COMPLETED"').all();
2435
+ for (const t of rawTasks) {
2251
2436
  const tid = 'task:' + t.id;
2252
- addNode(tid, t.title || 'Tarefa', 'task');
2437
+ addNode(tid, t.description || 'Tarefa', 'task');
2253
2438
 
2254
- const slug = t.projectSlug || null;
2255
- if (slug && nodeIds.has(slug)) {
2256
- edges.push({ from: tid, to: slug });
2439
+ if (t.project_slug && nodeIds.has(t.project_slug)) {
2440
+ edges.push({ from: tid, to: t.project_slug });
2257
2441
  } else {
2258
- // Connect to an "Unassigned" node
2259
2442
  addNode('unassigned', 'Sin Asignar', 'unassigned');
2260
2443
  edges.push({ from: tid, to: 'unassigned' });
2261
2444
  }
2262
2445
  }
2263
2446
 
2264
2447
  // 3. Load Blockers
2265
- const blockerFile = path.join(workspaceDir, 'data', 'blockers', 'blocker-log.json');
2266
- const blockerDoc = readJsonOrNull(blockerFile) || { blockers: [] };
2267
- const blockers = Array.isArray(blockerDoc.blockers) ? blockerDoc.blockers : [];
2268
-
2269
- for (const b of blockers) {
2270
- if (String(b.status || '').toUpperCase() !== 'OPEN') continue;
2271
-
2448
+ const openBlockers = dl.db.prepare('SELECT * FROM blockers WHERE status = "OPEN"').all();
2449
+ for (const b of openBlockers) {
2272
2450
  const bid = 'blocker:' + b.id;
2273
2451
  addNode(bid, 'BLQ: ' + (b.title || 'Blocker'), 'blocker');
2274
2452
 
2275
- const slug = b.projectSlug || null;
2276
- if (slug && nodeIds.has(slug)) {
2277
- edges.push({ from: bid, to: slug });
2453
+ if (b.project_slug && nodeIds.has(b.project_slug)) {
2454
+ edges.push({ from: bid, to: b.project_slug });
2278
2455
  } else {
2279
2456
  addNode('unassigned_blockers', 'Bloqueios Soltos', 'unassigned');
2280
2457
  edges.push({ from: bid, to: 'unassigned_blockers' });
@@ -2832,8 +3009,8 @@ async function cmdWeb({ port, dir, open, dev }) {
2832
3009
  const schema = {
2833
3010
  actions: [
2834
3011
  { type: 'append_daily_log', text: '<string>' },
2835
- { type: 'create_task', description: '<string>', priority: 'HIGH|MEDIUM|LOW', category: 'DO_NOW|SCHEDULE|DELEGATE|IGNORE', projectSlug: '<string optional>' },
2836
- { type: 'create_blocker', title: '<string>', severity: 'CRITICAL|HIGH|MEDIUM|LOW', notes: '<string>', projectSlug: '<string optional>' },
3012
+ { type: 'create_task', description: '<string>', priority: 'HIGH|MEDIUM|LOW', category: 'DO_NOW|SCHEDULE|DELEGATE|IGNORE', projectSlug: '<string optional>', streamSlug: '<string optional, ex: nome da frente ou stream>' },
3013
+ { type: 'create_blocker', title: '<string>', severity: 'CRITICAL|HIGH|MEDIUM|LOW', notes: '<string>', projectSlug: '<string optional>', streamSlug: '<string optional, ex: nome da frente ou stream>' },
2837
3014
  { type: 'suggest_report', name: 'daily|status|sm-weekly|blockers' },
2838
3015
  { type: 'oracle_query', query: '<string>' }
2839
3016
  ]
@@ -2991,18 +3168,6 @@ async function cmdWeb({ port, dir, open, dev }) {
2991
3168
  if (!Array.isArray(actions) || actions.length === 0) {
2992
3169
  return safeJson(res, 400, { error: 'Plan has no actions[]' });
2993
3170
  }
2994
-
2995
- const taskFile = path.join(workspaceDir, 'data', 'tasks', 'task-log.json');
2996
- const blockerFile = path.join(workspaceDir, 'data', 'blockers', 'blocker-log.json');
2997
-
2998
- const taskLog = readJsonOrNull(taskFile) || { schemaVersion: 1, tasks: [] };
2999
- if (!Array.isArray(taskLog.tasks)) taskLog.tasks = [];
3000
- if (typeof taskLog.schemaVersion !== 'number') taskLog.schemaVersion = 1;
3001
-
3002
- const blockerLog = readJsonOrNull(blockerFile) || { schemaVersion: 1, blockers: [] };
3003
- if (!Array.isArray(blockerLog.blockers)) blockerLog.blockers = [];
3004
- if (typeof blockerLog.schemaVersion !== 'number') blockerLog.schemaVersion = 1;
3005
-
3006
3171
  function normalizeWhitespace(t) {
3007
3172
  return String(t || '').replace(/\s+/g, ' ').trim();
3008
3173
  }
@@ -3015,27 +3180,11 @@ async function cmdWeb({ port, dir, open, dev }) {
3015
3180
  return crypto.createHash('sha1').update(String(text || ''), 'utf8').digest('hex');
3016
3181
  }
3017
3182
 
3018
- function within24h(iso) {
3019
- try {
3020
- const ms = Date.parse(iso);
3021
- if (!Number.isFinite(ms)) return false;
3022
- return (Date.now() - ms) <= 24 * 60 * 60 * 1000;
3023
- } catch {
3024
- return false;
3025
- }
3026
- }
3027
-
3028
- const existingTaskKeys24h = new Set(
3029
- taskLog.tasks
3030
- .filter((t) => t && within24h(t.createdAt))
3031
- .map((t) => sha1(normalizeTextForKey(t.description)))
3032
- );
3183
+ const recentTasks = dl.db.prepare("SELECT description FROM tasks WHERE created_at >= datetime('now', '-1 day')").all();
3184
+ const existingTaskKeys24h = new Set(recentTasks.map(t => sha1(normalizeTextForKey(t.description))));
3033
3185
 
3034
- const existingBlockerKeys24h = new Set(
3035
- blockerLog.blockers
3036
- .filter((b) => b && within24h(b.createdAt))
3037
- .map((b) => sha1(normalizeTextForKey(b.title)))
3038
- );
3186
+ const recentBlockers = dl.db.prepare("SELECT title FROM blockers WHERE created_at >= datetime('now', '-1 day')").all();
3187
+ const existingBlockerKeys24h = new Set(recentBlockers.map(b => sha1(normalizeTextForKey(b.title))));
3039
3188
 
3040
3189
  const now = new Date().toISOString();
3041
3190
  const applyMode = String(payload.mode || 'all').trim();
@@ -3067,71 +3216,72 @@ async function cmdWeb({ port, dir, open, dev }) {
3067
3216
 
3068
3217
  const validTaskCats = new Set(['DO_NOW', 'SCHEDULE', 'DELEGATE', 'IGNORE']);
3069
3218
 
3070
- for (const a of actions) {
3071
- if (!a || typeof a !== 'object') continue;
3072
- const type = String(a.type || '').trim();
3219
+ const insertTask = dl.db.prepare(`INSERT INTO tasks (id, project_slug, description, category, status, metadata) VALUES (?, ?, ?, ?, ?, ?)`);
3220
+ const insertBlocker = dl.db.prepare(`INSERT INTO blockers (id, project_slug, title, severity, status, owner, next_action, metadata) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`);
3221
+
3222
+ // Execute everything inside a transaction for the apply process
3223
+ const applyTx = dl.db.transaction((actionsToApply) => {
3224
+ for (const a of actionsToApply) {
3225
+ if (!a || typeof a !== 'object') continue;
3226
+ const type = String(a.type || '').trim();
3227
+
3228
+ if (type === 'create_task') {
3229
+ if (applyMode !== 'all' && applyMode !== 'tasks') continue;
3230
+ const description = normalizeWhitespace(a.description);
3231
+ if (!description) continue;
3232
+ const projectSlug = String(a.projectSlug || '').trim() || inferProjectSlug(description, slugMap);
3233
+ const streamSlug = String(a.streamSlug || '').trim();
3234
+ const key = sha1(normalizeTextForKey((projectSlug ? projectSlug + ' ' : '') + description));
3235
+ if (existingTaskKeys24h.has(key)) { applied.tasksSkipped++; continue; }
3236
+ const category = validTaskCats.has(String(a.category || '').trim()) ? String(a.category).trim() : 'DO_NOW';
3237
+ const priority = normPriority(a.priority);
3238
+
3239
+ const id = makeId('t');
3240
+ const metadata = JSON.stringify({ priority, streamSlug });
3241
+ insertTask.run(id, projectSlug || null, description, category, 'PENDING', metadata);
3242
+
3243
+ applied.tasks++;
3244
+ existingTaskKeys24h.add(key); // prevent duplicates within same batch
3245
+ continue;
3246
+ }
3073
3247
 
3074
- if (type === 'create_task') {
3075
- if (applyMode !== 'all' && applyMode !== 'tasks') continue;
3076
- const description = normalizeWhitespace(a.description);
3077
- if (!description) continue;
3078
- const projectSlug = String(a.projectSlug || '').trim() || inferProjectSlug(description, slugMap);
3079
- const key = sha1(normalizeTextForKey((projectSlug ? projectSlug + ' ' : '') + description));
3080
- if (existingTaskKeys24h.has(key)) { applied.tasksSkipped++; continue; }
3081
- const category = validTaskCats.has(String(a.category || '').trim()) ? String(a.category).trim() : 'DO_NOW';
3082
- const priority = normPriority(a.priority);
3083
- const task = {
3084
- id: makeId('t'),
3085
- description,
3086
- category,
3087
- status: 'PENDING',
3088
- createdAt: now,
3089
- };
3090
- if (projectSlug) task.projectSlug = projectSlug;
3091
- if (priority) task.priority = priority;
3092
- taskLog.tasks.push(task);
3093
- applied.tasks++;
3094
- continue;
3095
- }
3248
+ if (type === 'create_blocker') {
3249
+ if (applyMode !== 'all' && applyMode !== 'blockers') continue;
3250
+ const title = normalizeWhitespace(a.title);
3251
+ const notes = normalizeWhitespace(a.notes);
3252
+ if (!title) continue;
3253
+ const projectSlug = String(a.projectSlug || '').trim() || inferProjectSlug(title + ' ' + notes, slugMap);
3254
+ const streamSlug = String(a.streamSlug || '').trim();
3255
+ const key = sha1(normalizeTextForKey((projectSlug ? projectSlug + ' ' : '') + title));
3256
+ if (existingBlockerKeys24h.has(key)) { applied.blockersSkipped++; continue; }
3257
+ const severity = normSeverity(a.severity);
3258
+
3259
+ const id = makeId('b');
3260
+ const metadata = JSON.stringify({ streamSlug, description: notes || title });
3261
+ insertBlocker.run(id, projectSlug || null, title, severity, 'OPEN', null, null, metadata);
3262
+
3263
+ applied.blockers++;
3264
+ existingBlockerKeys24h.add(key); // prevent duplicates within same batch
3265
+ continue;
3266
+ }
3096
3267
 
3097
- if (type === 'create_blocker') {
3098
- if (applyMode !== 'all' && applyMode !== 'blockers') continue;
3099
- const title = normalizeWhitespace(a.title);
3100
- const projectSlug = String(a.projectSlug || '').trim() || inferProjectSlug(title + ' ' + normalizeWhitespace(a.notes), slugMap);
3101
- const key = sha1(normalizeTextForKey((projectSlug ? projectSlug + ' ' : '') + title));
3102
- if (existingBlockerKeys24h.has(key)) { applied.blockersSkipped++; continue; }
3103
- const notes = normalizeWhitespace(a.notes);
3104
- if (!title) continue;
3105
- const severity = normSeverity(a.severity);
3106
- const blocker = {
3107
- id: makeId('b'),
3108
- title,
3109
- description: notes || title,
3110
- createdAt: now,
3111
- status: 'OPEN',
3112
- severity,
3113
- };
3114
- if (projectSlug) blocker.projectSlug = projectSlug;
3115
- blockerLog.blockers.push(blocker);
3116
- applied.blockers++;
3117
- continue;
3118
- }
3268
+ if (type === 'suggest_report') {
3269
+ const name = String(a.name || '').trim();
3270
+ if (name) applied.reportsSuggested.push(name);
3271
+ continue;
3272
+ }
3119
3273
 
3120
- if (type === 'suggest_report') {
3121
- const name = String(a.name || '').trim();
3122
- if (name) applied.reportsSuggested.push(name);
3123
- continue;
3274
+ if (type === 'oracle_query') {
3275
+ const query = String(a.query || '').trim();
3276
+ if (query) applied.oracleQueries.push(query);
3277
+ continue;
3278
+ }
3124
3279
  }
3280
+ });
3125
3281
 
3126
- if (type === 'oracle_query') {
3127
- const query = String(a.query || '').trim();
3128
- if (query) applied.oracleQueries.push(query);
3129
- continue;
3130
- }
3131
- }
3282
+ applyTx(actions);
3132
3283
 
3133
3284
  // Auto-suggest reports when planner didn't include any
3134
- // (keeps UX consistent: if you created a blocker, at least suggest blockers)
3135
3285
  if (!applied.reportsSuggested.length) {
3136
3286
  const sug = [];
3137
3287
  sug.push('daily');
@@ -3143,10 +3293,6 @@ async function cmdWeb({ port, dir, open, dev }) {
3143
3293
  applied.reportsSuggested = Array.from(new Set(applied.reportsSuggested.map((s) => String(s).trim()).filter(Boolean)));
3144
3294
  }
3145
3295
 
3146
- // Persist
3147
- writeJson(taskFile, taskLog);
3148
- writeJson(blockerFile, blockerLog);
3149
-
3150
3296
  return safeJson(res, 200, { ok: true, applied });
3151
3297
  }
3152
3298
 
@@ -3237,28 +3383,51 @@ async function cmdWeb({ port, dir, open, dev }) {
3237
3383
  const query = String(payload.query || '').trim();
3238
3384
  if (!query) return safeJson(res, 400, { error: 'Missing query' });
3239
3385
 
3240
- const copilotResult = await copilotSearch(workspaceDir, query, { limit: 8 });
3241
- const indexMatches = searchIndex(workspaceDir, query, { limit: 12 });
3242
- const baseMatches = indexMatches.length
3243
- ? indexMatches
3244
- : searchWorkspace(workspaceDir, query, { limit: 12 });
3245
-
3246
- if (copilotResult.ok) {
3247
- const merged = mergeMatches(copilotResult.matches || [], baseMatches, 8);
3248
- const answer = buildChatAnswer(
3249
- query,
3250
- merged.matches,
3251
- copilotResult.summary,
3252
- copilotResult.evidence,
3253
- copilotResult.answer,
3254
- merged.total
3255
- );
3256
- return safeJson(res, 200, { ok: true, sessionId, answer, matches: merged.matches });
3386
+ const workspaceRulesBase = path.join(workspaceDir, '.agent', 'rules', 'freya');
3387
+ const packagedRulesBase = path.join(__dirname, '..', '.agent', 'rules', 'freya');
3388
+ const rulesBase = exists(workspaceRulesBase) ? workspaceRulesBase : packagedRulesBase;
3389
+
3390
+ const files = [
3391
+ path.join(rulesBase, 'freya.mdc'),
3392
+ path.join(rulesBase, 'agents', 'master.mdc'),
3393
+ path.join(rulesBase, 'agents', 'oracle.mdc')
3394
+ ].filter(exists);
3395
+
3396
+ const rulesText = files.map((p) => {
3397
+ const rel = path.relative(workspaceDir, p).replace(/\\/g, '/');
3398
+ return `\n\n---\nFILE: ${rel}\n---\n` + fs.readFileSync(p, 'utf8');
3399
+ }).join('');
3400
+
3401
+ // V2 RAG Context
3402
+ const dm = new DataManager(workspaceDir, path.join(workspaceDir, 'logs'));
3403
+ const ragResults = await dm.semanticSearch(query, 12);
3404
+ let ragContext = '';
3405
+ if (ragResults.length > 0) {
3406
+ ragContext = '\n\n[MEMÓRIA DE LONGO PRAZO RECUPERADA (RAG VIA SQLITE)]\n';
3407
+ for (const r of ragResults) {
3408
+ ragContext += `\n---\nFONTE: ${r.reference_type} -> ID: ${r.reference_id} (Score: ${r.score.toFixed(3)})\nCONTEÚDO:\n${r.text_chunk}\n`;
3409
+ }
3257
3410
  }
3258
3411
 
3259
- const merged = mergeMatches(baseMatches, [], 8);
3260
- const answer = buildChatAnswer(query, merged.matches, '', [], '', merged.total);
3261
- return safeJson(res, 200, { ok: true, sessionId, answer, matches: merged.matches });
3412
+ const prompt = `Você é o agente Oracle do sistema F.R.E.Y.A.\n\nSiga estritamente os arquivos de regras abaixo.\nResponda de forma analítica e consultiva.\n${ragContext}\n\nREGRAS:${rulesText}\n\nCONSULTA DO USUÁRIO:\n${query}\n`;
3413
+
3414
+ const cmd = process.env.COPILOT_CMD || 'copilot';
3415
+
3416
+ try {
3417
+ // Removed --allow-all-tools and --add-dir to force reliance on RAG context
3418
+ const r = await run(cmd, ['-s', '--no-color', '--stream', 'off', '-p', prompt], workspaceDir);
3419
+ const out = (r.stdout + r.stderr).trim();
3420
+ if (r.code !== 0) {
3421
+ return safeJson(res, 200, { ok: false, answer: 'Falha na busca do agente Oracle:\n' + (out || 'Exit code != 0'), sessionId });
3422
+ }
3423
+ return safeJson(res, 200, { ok: true, answer: out, sessionId });
3424
+ } catch (e) {
3425
+ return safeJson(res, 200, {
3426
+ ok: false,
3427
+ answer: `Agente indisponível (cmd: ${cmd}).\nDetalhes:\n` + (e.message || String(e)),
3428
+ sessionId
3429
+ });
3430
+ }
3262
3431
  }
3263
3432
 
3264
3433
  // Chat persistence (per session)
@@ -3361,52 +3530,48 @@ async function cmdWeb({ port, dir, open, dev }) {
3361
3530
  const cat = payload.category ? String(payload.category).trim() : null;
3362
3531
  const status = payload.status ? String(payload.status).trim() : null;
3363
3532
 
3364
- const file = path.join(workspaceDir, 'data', 'tasks', 'task-log.json');
3365
- const doc = readJsonOrNull(file) || { schemaVersion: 1, tasks: [] };
3366
- const tasks = Array.isArray(doc.tasks) ? doc.tasks.slice() : [];
3367
-
3368
- const filtered = tasks
3369
- .filter((t) => {
3370
- if (!t || typeof t !== 'object') return false;
3371
- if (cat && String(t.category || '').trim() !== cat) return false;
3372
- if (status && String(t.status || '').trim() !== status) return false;
3373
- return true;
3374
- })
3375
- .sort((a, b) => {
3376
- const pa = String(a.priority || '').toLowerCase();
3377
- const pb = String(b.priority || '').toLowerCase();
3378
- const rank = (p) => (p === 'high' ? 0 : p === 'medium' ? 1 : p === 'low' ? 2 : 3);
3379
- const ra = rank(pa), rb = rank(pb);
3380
- if (ra !== rb) return ra - rb;
3381
- return String(b.createdAt || '').localeCompare(String(a.createdAt || ''));
3382
- })
3383
- .slice(0, limit);
3384
-
3385
- return safeJson(res, 200, { ok: true, tasks: filtered });
3533
+ let query = 'SELECT * FROM tasks WHERE 1=1';
3534
+ const params = [];
3535
+
3536
+ if (cat) { query += ' AND category = ?'; params.push(cat); }
3537
+ if (status) { query += ' AND status = ?'; params.push(status); }
3538
+
3539
+ query += ` ORDER BY
3540
+ CASE JSON_EXTRACT(metadata, '$.priority')
3541
+ WHEN 'high' THEN 0 WHEN 'medium' THEN 1 WHEN 'low' THEN 2 ELSE 3
3542
+ END ASC,
3543
+ created_at DESC
3544
+ LIMIT ?`;
3545
+ params.push(limit);
3546
+
3547
+ const rawTasks = dl.db.prepare(query).all(...params);
3548
+ const tasks = rawTasks.map(t => {
3549
+ const meta = t.metadata ? JSON.parse(t.metadata) : {};
3550
+ return {
3551
+ id: t.id,
3552
+ description: t.description,
3553
+ category: t.category,
3554
+ status: t.status,
3555
+ createdAt: t.created_at,
3556
+ completedAt: t.completed_at,
3557
+ projectSlug: t.project_slug,
3558
+ priority: meta.priority,
3559
+ streamSlug: meta.streamSlug
3560
+ };
3561
+ });
3562
+
3563
+ return safeJson(res, 200, { ok: true, tasks });
3386
3564
  }
3387
3565
 
3388
3566
  if (req.url === '/api/tasks/complete') {
3389
3567
  const id = String(payload.id || '').trim();
3390
3568
  if (!id) return safeJson(res, 400, { error: 'Missing id' });
3391
3569
 
3392
- const file = path.join(workspaceDir, 'data', 'tasks', 'task-log.json');
3393
- const doc = readJsonOrNull(file) || { schemaVersion: 1, tasks: [] };
3394
- const tasks = Array.isArray(doc.tasks) ? doc.tasks : [];
3395
-
3396
- const now = isoNow();
3397
- let updated = null;
3398
- for (const t of tasks) {
3399
- if (t && t.id === id) {
3400
- t.status = 'COMPLETED';
3401
- t.completedAt = now;
3402
- updated = t;
3403
- break;
3404
- }
3405
- }
3570
+ const now = new Date().toISOString();
3571
+ const info = dl.db.prepare(`UPDATE tasks SET status = 'COMPLETED', completed_at = ? WHERE id = ?`).run(now, id);
3406
3572
 
3407
- if (!updated) return safeJson(res, 404, { error: 'Task not found' });
3408
- writeJson(file, doc);
3409
- return safeJson(res, 200, { ok: true, task: updated });
3573
+ if (info.changes === 0) return safeJson(res, 404, { error: 'Task not found' });
3574
+ return safeJson(res, 200, { ok: true, task: { id, status: 'COMPLETED', completedAt: now } });
3410
3575
  }
3411
3576
 
3412
3577
 
@@ -3415,22 +3580,26 @@ async function cmdWeb({ port, dir, open, dev }) {
3415
3580
  if (!id) return safeJson(res, 400, { error: 'Missing id' });
3416
3581
  const patch = payload.patch && typeof payload.patch === 'object' ? payload.patch : {};
3417
3582
 
3418
- const file = path.join(workspaceDir, 'data', 'tasks', 'task-log.json');
3419
- const doc = readJsonOrNull(file) || { schemaVersion: 1, tasks: [] };
3420
- const tasks = Array.isArray(doc.tasks) ? doc.tasks : [];
3583
+ let queryUpdates = [];
3584
+ let params = [];
3421
3585
 
3422
- let updated = null;
3423
- for (const t of tasks) {
3424
- if (t && t.id === id) {
3425
- if (typeof patch.projectSlug === 'string') t.projectSlug = patch.projectSlug.trim() || undefined;
3426
- if (typeof patch.category === 'string') t.category = patch.category.trim();
3427
- updated = t;
3428
- break;
3429
- }
3586
+ if (typeof patch.projectSlug === 'string') {
3587
+ queryUpdates.push('project_slug = ?');
3588
+ params.push(patch.projectSlug.trim() || null);
3430
3589
  }
3431
- if (!updated) return safeJson(res, 404, { error: 'Task not found' });
3432
- writeJson(file, doc);
3433
- return safeJson(res, 200, { ok: true, task: updated });
3590
+ if (typeof patch.category === 'string') {
3591
+ queryUpdates.push('category = ?');
3592
+ params.push(patch.category.trim());
3593
+ }
3594
+
3595
+ if (queryUpdates.length === 0) return safeJson(res, 200, { ok: true, task: { id } });
3596
+
3597
+ params.push(id);
3598
+ const updateQuery = `UPDATE tasks SET ${queryUpdates.join(', ')} WHERE id = ?`;
3599
+ const info = dl.db.prepare(updateQuery).run(...params);
3600
+
3601
+ if (info.changes === 0) return safeJson(res, 404, { error: 'Task not found' });
3602
+ return safeJson(res, 200, { ok: true, task: { id, ...patch } });
3434
3603
  }
3435
3604
 
3436
3605
  if (req.url === '/api/health/checklist') {
@@ -3442,13 +3611,11 @@ async function cmdWeb({ port, dir, open, dev }) {
3442
3611
  return safeJson(res, 200, { ok: false, needsInit: true, error: 'Workspace not initialized', items: [] });
3443
3612
  }
3444
3613
 
3445
- const tasksFile = path.join(workspaceDir, 'data', 'tasks', 'task-log.json');
3446
- const blockersFile = path.join(workspaceDir, 'data', 'blockers', 'blocker-log.json');
3447
- const tasksDoc = readJsonOrNull(tasksFile) || { schemaVersion: 1, tasks: [] };
3448
- const blockersDoc = readJsonOrNull(blockersFile) || { schemaVersion: 1, blockers: [] };
3614
+ const pendingTasksRes = dl.db.prepare('SELECT count(*) as count FROM tasks WHERE status = "PENDING" AND category = "DO_NOW"').get();
3615
+ const openBlockersRes = dl.db.prepare('SELECT count(*) as count FROM blockers WHERE status = "OPEN"').get();
3449
3616
 
3450
- const pendingTasks = (tasksDoc.tasks || []).filter((t) => t && t.status === 'PENDING' && t.category === 'DO_NOW').length;
3451
- const openBlockers = (blockersDoc.blockers || []).filter((b) => b && String(b.status || '').trim() === 'OPEN').length;
3617
+ const pendingTasks = pendingTasksRes ? pendingTasksRes.count : 0;
3618
+ const openBlockers = openBlockersRes ? openBlockersRes.count : 0;
3452
3619
 
3453
3620
  const today = isoDate();
3454
3621
  const reports = listReports(workspaceDir);
@@ -3457,7 +3624,7 @@ async function cmdWeb({ port, dir, open, dev }) {
3457
3624
  const items = [
3458
3625
  { label: 'Blockers abertos', status: openBlockers > 0 ? 'warn' : 'ok', detail: `${openBlockers} aberto(s)` },
3459
3626
  { label: 'Tarefas DO_NOW pendentes', status: pendingTasks > 0 ? 'warn' : 'ok', detail: `${pendingTasks} pendente(s)` },
3460
- { label: 'Relatorios de hoje', status: reportsToday > 0 ? 'ok' : 'warn', detail: `${reportsToday} gerado(s)` }
3627
+ { label: 'Relatórios de hoje', status: reportsToday > 0 ? 'ok' : 'warn', detail: `${reportsToday} gerado(s)` }
3461
3628
  ];
3462
3629
 
3463
3630
  return safeJson(res, 200, { ok: true, items, stats: { pendingTasks, openBlockers, reportsToday, reportsTotal: reports.length } });
@@ -3507,11 +3674,18 @@ async function cmdWeb({ port, dir, open, dev }) {
3507
3674
  }
3508
3675
 
3509
3676
  if (req.url === '/api/blockers/summary') {
3510
- const file = path.join(workspaceDir, 'data', 'blockers', 'blocker-log.json');
3511
- const doc = readJsonOrNull(file) || { schemaVersion: 1, blockers: [] };
3512
- const blockers = Array.isArray(doc.blockers) ? doc.blockers : [];
3677
+ const open = dl.db.prepare(`
3678
+ SELECT * FROM blockers
3679
+ WHERE status IN ('OPEN', 'MITIGATING')
3680
+ `).all().map(b => ({
3681
+ id: b.id,
3682
+ title: b.title,
3683
+ severity: b.severity,
3684
+ status: b.status,
3685
+ projectSlug: b.project_slug,
3686
+ createdAt: b.created_at
3687
+ }));
3513
3688
 
3514
- const open = blockers.filter((b) => b && String(b.status || '').trim() === 'OPEN');
3515
3689
  const sevCount = { CRITICAL: 0, HIGH: 0, MEDIUM: 0, LOW: 0, OTHER: 0 };
3516
3690
  const now = Date.now();
3517
3691
  let older7 = 0;
@@ -3530,7 +3704,7 @@ async function cmdWeb({ port, dir, open, dev }) {
3530
3704
  .slice(0, 5)
3531
3705
  .map((b) => ({
3532
3706
  id: b.id,
3533
- title: b.title || b.description || 'Sem titulo',
3707
+ title: b.title || 'Sem titulo',
3534
3708
  severity: b.severity || 'UNKNOWN',
3535
3709
  projectSlug: b.projectSlug || null
3536
3710
  }));
@@ -3549,34 +3723,40 @@ async function cmdWeb({ port, dir, open, dev }) {
3549
3723
  const limit = Math.max(1, Math.min(50, Number(payload.limit || 10)));
3550
3724
  const status = payload.status ? String(payload.status).trim() : 'OPEN';
3551
3725
 
3552
- const file = path.join(workspaceDir, 'data', 'blockers', 'blocker-log.json');
3553
- const doc = readJsonOrNull(file) || { schemaVersion: 1, blockers: [] };
3554
- const blockers = Array.isArray(doc.blockers) ? doc.blockers.slice() : [];
3555
-
3556
- const sevRank = (s) => {
3557
- const v = String(s || '').toUpperCase();
3558
- if (v === 'CRITICAL') return 0;
3559
- if (v === 'HIGH') return 1;
3560
- if (v === 'MEDIUM') return 2;
3561
- if (v === 'LOW') return 3;
3562
- return 9;
3563
- };
3726
+ let query = 'SELECT * FROM blockers WHERE 1=1';
3727
+ const params = [];
3564
3728
 
3565
- const filtered = blockers
3566
- .filter((b) => {
3567
- if (!b || typeof b !== 'object') return false;
3568
- if (status && String(b.status || '').trim() !== status) return false;
3569
- return true;
3570
- })
3571
- .sort((a, b) => {
3572
- const ra = sevRank(a.severity);
3573
- const rb = sevRank(b.severity);
3574
- if (ra !== rb) return ra - rb;
3575
- return String(a.createdAt || '').localeCompare(String(b.createdAt || ''));
3576
- })
3577
- .slice(0, limit);
3578
-
3579
- return safeJson(res, 200, { ok: true, blockers: filtered });
3729
+ if (status) {
3730
+ query += ' AND status = ?';
3731
+ params.push(status);
3732
+ }
3733
+
3734
+ query += ` ORDER BY
3735
+ CASE severity
3736
+ WHEN 'CRITICAL' THEN 0 WHEN 'HIGH' THEN 1 WHEN 'MEDIUM' THEN 2 WHEN 'LOW' THEN 3 ELSE 9
3737
+ END ASC,
3738
+ created_at ASC
3739
+ LIMIT ?`;
3740
+ params.push(limit);
3741
+
3742
+ const rawBlockers = dl.db.prepare(query).all(...params);
3743
+ const blockers = rawBlockers.map(b => {
3744
+ const meta = b.metadata ? JSON.parse(b.metadata) : {};
3745
+ return {
3746
+ id: b.id,
3747
+ title: b.title,
3748
+ severity: b.severity,
3749
+ status: b.status,
3750
+ createdAt: b.created_at,
3751
+ resolvedAt: b.resolved_at,
3752
+ projectSlug: b.project_slug,
3753
+ owner: b.owner,
3754
+ nextAction: b.next_action,
3755
+ streamSlug: meta.streamSlug
3756
+ };
3757
+ });
3758
+
3759
+ return safeJson(res, 200, { ok: true, blockers });
3580
3760
  }
3581
3761
 
3582
3762
  if (req.url === '/api/blockers/update') {
@@ -3584,21 +3764,22 @@ async function cmdWeb({ port, dir, open, dev }) {
3584
3764
  if (!id) return safeJson(res, 400, { error: 'Missing id' });
3585
3765
  const patch = payload.patch && typeof payload.patch === 'object' ? payload.patch : {};
3586
3766
 
3587
- const file = path.join(workspaceDir, 'data', 'blockers', 'blocker-log.json');
3588
- const doc = readJsonOrNull(file) || { schemaVersion: 1, blockers: [] };
3589
- const blockers = Array.isArray(doc.blockers) ? doc.blockers : [];
3767
+ let queryUpdates = [];
3768
+ let params = [];
3590
3769
 
3591
- let updated = null;
3592
- for (const b of blockers) {
3593
- if (b && b.id === id) {
3594
- if (typeof patch.projectSlug === 'string') b.projectSlug = patch.projectSlug.trim() || undefined;
3595
- updated = b;
3596
- break;
3597
- }
3770
+ if (typeof patch.projectSlug === 'string') {
3771
+ queryUpdates.push('project_slug = ?');
3772
+ params.push(patch.projectSlug.trim() || null);
3598
3773
  }
3599
- if (!updated) return safeJson(res, 404, { error: 'Blocker not found' });
3600
- writeJson(file, doc);
3601
- return safeJson(res, 200, { ok: true, blocker: updated });
3774
+
3775
+ if (queryUpdates.length === 0) return safeJson(res, 200, { ok: true, blocker: { id } });
3776
+
3777
+ params.push(id);
3778
+ const updateQuery = `UPDATE blockers SET ${queryUpdates.join(', ')} WHERE id = ?`;
3779
+ const info = dl.db.prepare(updateQuery).run(...params);
3780
+
3781
+ if (info.changes === 0) return safeJson(res, 404, { error: 'Blocker not found' });
3782
+ return safeJson(res, 200, { ok: true, blocker: { id, ...patch } });
3602
3783
  }
3603
3784
 
3604
3785
  if (req.url === '/api/report') {
@@ -3689,4 +3870,150 @@ async function cmdWeb({ port, dir, open, dev }) {
3689
3870
  if (open) openBrowser(url);
3690
3871
  }
3691
3872
 
3873
+ function buildSettingsHtml(safeDefault, appVersion) {
3874
+ const safeVersion = escapeHtml(appVersion || 'unknown');
3875
+ return `<!doctype html>
3876
+ <html>
3877
+ <head>
3878
+ <meta charset="utf-8" />
3879
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
3880
+ <title>FREYA Settings</title>
3881
+ <link rel="stylesheet" href="/app.css" />
3882
+ </head>
3883
+ <body data-page="settings">
3884
+ <div class="app">
3885
+ <div class="frame">
3886
+ <div class="shell">
3887
+
3888
+ <aside class="rail">
3889
+ <div class="railTop">
3890
+ <div class="railLogo">F</div>
3891
+ </div>
3892
+ <div class="railNav">
3893
+ <button class="railBtn" id="railDashboard" type="button" title="Dashboard">
3894
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="7" height="9"></rect><rect x="14" y="3" width="7" height="5"></rect><rect x="14" y="12" width="7" height="9"></rect><rect x="3" y="16" width="7" height="5"></rect></svg>
3895
+ </button>
3896
+ <button class="railBtn" id="railReports" type="button" title="Relatórios">
3897
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path><polyline points="14 2 14 8 20 8"></polyline><line x1="16" y1="13" x2="8" y2="13"></line><line x1="16" y1="17" x2="8" y2="17"></line><polyline points="10 9 9 9 8 9"></polyline></svg>
3898
+ </button>
3899
+ <button class="railBtn" id="railCompanion" type="button" title="Companion">
3900
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><polygon points="16.24 7.76 14.12 14.12 7.76 16.24 9.88 9.88 16.24 7.76"></polygon></svg>
3901
+ </button>
3902
+ <button class="railBtn" id="railProjects" type="button" title="Projects">
3903
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"></path></svg>
3904
+ </button>
3905
+ <button class="railBtn" id="railTimeline" type="button" title="Timeline">
3906
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><polyline points="12 6 12 12 16 14"></polyline></svg>
3907
+ </button>
3908
+ <button class="railBtn" id="railGraph" type="button" title="Grafo">
3909
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="18" cy="5" r="3"></circle><circle cx="6" cy="12" r="3"></circle><circle cx="18" cy="19" r="3"></circle><line x1="8.59" y1="13.51" x2="15.42" y2="17.49"></line><line x1="15.41" y1="6.51" x2="8.59" y2="10.49"></line></svg>
3910
+ </button>
3911
+ <button class="railBtn" id="railDocs" type="button" title="Documentação">
3912
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"></path><path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z"></path></svg>
3913
+ </button>
3914
+ </div>
3915
+ <div class="railBottom">
3916
+ <button id="railSettings" class="railBtn active" title="Configurações">
3917
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"></circle><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"></path></svg>
3918
+ </button>
3919
+ </div>
3920
+ </aside>
3921
+
3922
+ <main class="center" id="settingsPage">
3923
+ <div class="topbar">
3924
+ <div class="brandLine">
3925
+ <span class="spark"></span>
3926
+ <div class="brandStack">
3927
+ <div class="brand">FREYA</div>
3928
+ <div class="brandSub">Configurações Avançadas</div>
3929
+ </div>
3930
+ </div>
3931
+ <div class="topActions">
3932
+ <span class="chip clickable" onclick="window.location.href='/docs'">Docs</span>
3933
+ <span class="chip" id="chipVersion">v${safeVersion}</span>
3934
+ <button class="btn icon" type="button" onclick="window.toggleTheme()" title="Alternar tema claro/escuro">
3935
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon-sun"><circle cx="12" cy="12" r="5"></circle><line x1="12" y1="1" x2="12" y2="3"></line><line x1="12" y1="21" x2="12" y2="23"></line><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line><line x1="1" y1="12" x2="3" y2="12"></line><line x1="21" y1="12" x2="23" y2="12"></line><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line></svg>
3936
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon-moon" style="display:none"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path></svg>
3937
+ </button>
3938
+ </div>
3939
+ </div>
3940
+ <section class="utilityGrid" style="margin-bottom: 24px;">
3941
+ <div class="utilityCard">
3942
+ <div class="utilityHead">Área de trabalho local</div>
3943
+ <div class="sidePath" id="sidePath">${safeDefault}</div>
3944
+ <div class="row" style="grid-template-columns: 1fr auto">
3945
+ <input id="dir" placeholder="./freya" value="${safeDefault}" />
3946
+ <button class="btn small" type="button" onclick="window.pickDir()">Procurar</button>
3947
+ </div>
3948
+ <div class="stack" style="margin-top:10px">
3949
+ <button class="btn" type="button" onclick="window.doUpdate()">Sincronizar workspace</button>
3950
+ <button class="btn" type="button" onclick="window.doMigrate()">Migrar dados base</button>
3951
+ </div>
3952
+ <div style="height:10px"></div>
3953
+ <div class="help"><b>Sincronizar workspace</b>: Atualiza scripts/templates da suite FREYA sem afetar <code>data</code>.</div>
3954
+ <div class="help"><b>Migrar dados</b>: Se alguma versão quebrar compatibilidade, ele recria o log.</div>
3955
+ </div>
3956
+
3957
+ <div class="utilityCard">
3958
+ <div class="utilityHead">Interface & Sistema</div>
3959
+ <div style="display:flex; flex-direction:column; gap:8px;">
3960
+ <label style="display:flex; align-items:center; gap:10px; user-select:none; margin: 6px 0 12px 0">
3961
+ <input id="prettyPublish" type="checkbox" checked style="width:auto" onchange="window.togglePrettyPublish()" />
3962
+ Publicação bonita (cards/embeds em RTF)
3963
+ </label>
3964
+ <div class="stack" style="margin-top:10px">
3965
+ <button class="btn" type="button" onclick="window.rebuildIndex()">Reconstruir Índice de Busca (Copilot)</button>
3966
+ <button class="btn" type="button" onclick="window.exportObsidian()">Exportar Notas (Obsidian Markdown)</button>
3967
+ </div>
3968
+ <div class="help">Usa Copilot/RagIndex para buscas conversacionais.</div>
3969
+ </div>
3970
+ </div>
3971
+ </section>
3972
+
3973
+ <div class="devGrid">
3974
+ <div class="panel">
3975
+ <div class="panelHead"><b>Configuração de Notificações</b></div>
3976
+ <div class="panelBody">
3977
+ <label>Discord webhook URL</label>
3978
+ <input id="discord" placeholder="https://discord.com/api/webhooks/..." />
3979
+ <div style="height:10px"></div>
3980
+
3981
+ <label>Teams webhook URL</label>
3982
+ <input id="teams" placeholder="https://..." />
3983
+
3984
+ <div class="stack" style="margin-top:20px;">
3985
+ <button class="btn primary" type="button" onclick="window.saveSettings()">Salvar Configurações</button>
3986
+ </div>
3987
+ </div>
3988
+ </div>
3989
+
3990
+ <div class="panel">
3991
+ <div class="panelHead"><b>Mapeamento Smart de Projetos</b></div>
3992
+ <div class="panelBody">
3993
+ <label>Regras de inferência (JSON)</label>
3994
+ <textarea id="slugRules" rows="8" placeholder="{ \"rules\": [ { \"contains\": \"fideliza\", \"slug\": \"vivo/fidelizacao\" } ] }"></textarea>
3995
+ <div class="help">Usado pra inferir <code>projectSlug</code>.</div>
3996
+ <div class="stack" style="margin-top:10px">
3997
+ <button class="btn" type="button" onclick="window.reloadSlugRules()">Recarregar regras</button>
3998
+ <button class="btn primary" type="button" onclick="window.saveSlugRules()">Salvar regras</button>
3999
+ </div>
4000
+ </div>
4001
+ </div>
4002
+ </div>
4003
+
4004
+ </div>
4005
+ </main>
4006
+ </div>
4007
+ </div>
4008
+ </div>
4009
+
4010
+ <script>
4011
+ window.__FREYA_DEFAULT_DIR = "${safeDefault}";
4012
+ </script>
4013
+ <script src="/app.js"></script>
4014
+ </body>
4015
+ </html>`;
4016
+ }
4017
+
3692
4018
  module.exports = { cmdWeb };
4019
+