total-recall-brain 3.41.3 → 3.41.4

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 (80) hide show
  1. package/bin/total-recall.mjs +10 -1
  2. package/frontend/dist/assets/{index-DFenQQEQ.js → index-CRzjQLu7.js} +77 -72
  3. package/frontend/dist/index.html +1 -1
  4. package/package.json +7 -5
  5. package/scaffold/.agent/skills/total-recall/plugins/auth/package-lock.json +2 -2
  6. package/scaffold/.agent/skills/total-recall/plugins/auth/package.json +1 -1
  7. package/scaffold/.agent/skills/total-recall/plugins/auth/plugin.json +1 -1
  8. package/scaffold/.agent/skills/total-recall/plugins/auth/src/cli-secret.mjs +48 -0
  9. package/scaffold/.agent/skills/total-recall/plugins/auth/src/login-projections.mjs +42 -0
  10. package/scaffold/.agent/skills/total-recall/plugins/auth/src/mesh-secrets.mjs +18 -0
  11. package/scaffold/.agent/skills/total-recall/plugins/auth/src/routes/secrets.mjs +5 -2
  12. package/scaffold/.agent/skills/total-recall/plugins/auth/src/secrets-carriers.mjs +76 -0
  13. package/scaffold/.agent/skills/total-recall/plugins/auth/src/secrets-prune.mjs +37 -0
  14. package/scaffold/.agent/skills/total-recall/plugins/auth/src/secrets-sync.mjs +53 -17
  15. package/scaffold/.agent/skills/total-recall/plugins/code-quality/package-lock.json +12 -12
  16. package/scaffold/.agent/skills/total-recall/plugins/code-quality/package.json +1 -1
  17. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/package-lock.json +12 -12
  18. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/package.json +1 -1
  19. package/scaffold/.agent/skills/total-recall/plugins/embeddings/package-lock.json +23 -8
  20. package/scaffold/.agent/skills/total-recall/plugins/embeddings/package.json +6 -2
  21. package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/package-lock.json +23 -8
  22. package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/package.json +5 -1
  23. package/scaffold/.agent/skills/total-recall/plugins/mesh/package-lock.json +23 -10
  24. package/scaffold/.agent/skills/total-recall/plugins/mesh/package.json +5 -3
  25. package/scaffold/.agent/skills/total-recall/plugins/mesh/plugin.json +1 -1
  26. package/scaffold/.agent/skills/total-recall/plugins/mesh/src/cli/grants.mjs +7 -3
  27. package/scaffold/.agent/skills/total-recall/plugins/mesh/src/cli/mesh.mjs +61 -38
  28. package/scaffold/.agent/skills/total-recall/plugins/mesh/src/core/leader-election.mjs +57 -2
  29. package/scaffold/.agent/skills/total-recall/plugins/mesh/src/core/mesh.mjs +220 -36
  30. package/scaffold/.agent/skills/total-recall/plugins/mesh/src/server/routes/mesh.mjs +7 -2
  31. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/README.md +65 -0
  32. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/capability/BRICK.md +59 -0
  33. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/capability/index.mjs +534 -0
  34. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/cli.mjs +139 -0
  35. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/package-lock.json +2849 -0
  36. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/package.json +26 -0
  37. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/plugin.json +376 -0
  38. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/skills/meta-harness/SKILL.md +37 -0
  39. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/adapters/dsh.mjs +446 -0
  40. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/alerts.mjs +23 -0
  41. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/coordinator.mjs +398 -0
  42. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/documentation.mjs +152 -0
  43. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/executor.mjs +82 -0
  44. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/learner.mjs +90 -0
  45. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/router.mjs +415 -0
  46. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/runtime.mjs +36 -0
  47. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/setup.mjs +35 -0
  48. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/tracker.mjs +133 -0
  49. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/worker.mjs +233 -0
  50. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/DESIGN.md +32 -0
  51. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/card.js +845 -0
  52. package/scaffold/.agent/skills/total-recall/plugins/openwiki/capability/BRICK.md +64 -0
  53. package/scaffold/.agent/skills/total-recall/plugins/openwiki/capability/index.mjs +250 -0
  54. package/scaffold/.agent/skills/total-recall/plugins/openwiki/cli.mjs +344 -14
  55. package/scaffold/.agent/skills/total-recall/plugins/openwiki/package-lock.json +2917 -140
  56. package/scaffold/.agent/skills/total-recall/plugins/openwiki/package.json +9 -3
  57. package/scaffold/.agent/skills/total-recall/plugins/openwiki/plugin.json +223 -5
  58. package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/config.mjs +129 -0
  59. package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/refresh.mjs +355 -0
  60. package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/upstream-watcher.mjs +375 -0
  61. package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/DESIGN.md +35 -22
  62. package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/card.js +947 -323
  63. package/scaffold/.agent/skills/total-recall/plugins/security/package-lock.json +23 -8
  64. package/scaffold/.agent/skills/total-recall/plugins/security/package.json +5 -1
  65. package/src/cli/config.mjs +3 -0
  66. package/src/cli/connect.mjs +12 -116
  67. package/src/cli/harness.mjs +0 -1
  68. package/src/cli/plugin/create.mjs +56 -3
  69. package/src/cli/plugin/index.mjs +15 -3
  70. package/src/cli/plugin/monetization.mjs +232 -12
  71. package/src/cli/preflight.mjs +161 -0
  72. package/src/core/cli-commands.mjs +2 -0
  73. package/src/core/context-compiler.mjs +8 -1
  74. package/src/core/default-plugins.mjs +1 -1
  75. package/src/core/meta-harness.mjs +16 -38
  76. package/src/core/plugin-dependencies.mjs +6 -1
  77. package/src/core/preflight-gate.mjs +77 -0
  78. package/src/core/preflight-receipt.mjs +123 -0
  79. package/src/core/runtime.mjs +0 -3
  80. package/src/core/surface.mjs +38 -1
@@ -1,534 +1,1158 @@
1
1
  /**
2
- * Brain Dump — Multi-Repo Knowledge Graph & Documentation Console
2
+ * OpenWiki — Knowledge Graph & Multi-Repo Documentation Wiki
3
+ * Bespoke Card Face: Deep Slate & Knowledge Amber Interface.
3
4
  *
4
- * Bespoke operator interface for cross-repository documentation and knowledge graphs.
5
- * Displays real-time hub trees, file metrics, structured ingestion controllers,
6
- * and bootstrap verification without template wrappers or terminal dumps.
5
+ * Exposes live hub discovery, markdown file inspection, upstream release surveillance,
6
+ * and structured fleet propagation controls. Operates without mock data or boilerplate sinks.
7
7
  */
8
8
 
9
- const DASH = '\u2014';
10
-
11
- function escapeHtml(str) {
12
- if (str === null || str === undefined) return '';
13
- return String(str).replace(/[&<>"']/g, c => ({
14
- '&': '&amp;',
15
- '<': '&lt;',
16
- '>': '&gt;',
17
- '"': '&quot;',
18
- "'": '&#39;'
19
- }[c]));
20
- }
21
-
22
9
  export class OpenWikiCard extends HTMLElement {
10
+ static get screens() {
11
+ return [
12
+ { id: 'hubs', label: 'Hubs' },
13
+ { id: 'config', label: 'Configuration' },
14
+ { id: 'refresh', label: 'Fleet Refresh' },
15
+ { id: 'upstream', label: 'Upstream Watcher' }
16
+ ];
17
+ }
18
+
19
+ get screens() {
20
+ return OpenWikiCard.screens;
21
+ }
22
+
23
23
  constructor() {
24
24
  super();
25
+ this.activeScreen = 'hubs';
25
26
  this.hubs = [];
26
- this.totalLines = 0;
27
- this.executing = false;
27
+ this.loading = false;
28
+ this.statusText = 'READY';
28
29
  this.targetPath = 'openwiki';
29
- this.dryRun = true;
30
- this.lastResult = null;
31
- this.screens = [{ id: 'knowledge', label: 'Knowledge Hubs' }];
32
- this.screen = 'knowledge';
30
+ this.dryRun = false;
31
+ this.executionResult = null;
32
+ this.errorMessage = null;
33
+ this.lastRefreshedAt = null;
34
+ this.upstream = null;
35
+ this.config = {
36
+ provider: 'openrouter',
37
+ model: 'deepseek/deepseek-v4.1-flash',
38
+ fallback_model: 'qwen/qwen-2.5-coder-32b-instruct',
39
+ max_tokens: 4096,
40
+ timeout_ms: 300000
41
+ };
42
+ this.refreshReports = [];
43
+ this.refreshCounts = null;
44
+ this._boundOnScreenChange = this.handleExternalScreenChange.bind(this);
33
45
  }
34
46
 
35
47
  connectedCallback() {
36
48
  if (!this.shadowRoot) {
37
49
  this.attachShadow({ mode: 'open' });
38
50
  }
51
+ this.addEventListener('tr:screen-change', this._boundOnScreenChange);
52
+ this.addEventListener('plugin-show-screen', this._boundOnScreenChange);
39
53
  this.render();
40
54
  this.refreshHubs();
55
+ this.loadConfig();
41
56
  }
42
57
 
43
- async refreshHubs() {
44
- if (!this.host) return;
58
+ disconnectedCallback() {
59
+ this.removeEventListener('tr:screen-change', this._boundOnScreenChange);
60
+ this.removeEventListener('plugin-show-screen', this._boundOnScreenChange);
61
+ }
62
+
63
+ handleExternalScreenChange(event) {
64
+ const screenId = event.detail?.screen || event.detail?.screenId || event.detail?.id;
65
+ if (screenId && OpenWikiCard.screens.some((s) => s.id === screenId)) {
66
+ this.setScreen(screenId);
67
+ }
68
+ }
69
+
70
+ setScreen(id) {
71
+ if (this.activeScreen === id) return;
72
+ this.activeScreen = id;
73
+ this.errorMessage = null;
74
+ this.render();
75
+ if (typeof this.host?.screenChanged === 'function') {
76
+ try {
77
+ this.host.screenChanged({ screen: id });
78
+ } catch {
79
+ // Host screenChanged callback optional
80
+ }
81
+ }
82
+ if (id === 'refresh' && (!this.refreshReports || this.refreshReports.length === 0)) {
83
+ this.loadRefreshReport();
84
+ } else if (id === 'config') {
85
+ this.loadConfig();
86
+ }
87
+ }
88
+
89
+ async loadConfig() {
45
90
  try {
46
- const res = await this.host.run('status', ['--json']);
47
- let parsed = [];
48
- if (res && typeof res === 'object') {
49
- if (Array.isArray(res.hubs)) {
50
- parsed = res.hubs;
51
- this.totalLines = res.total_lines || 0;
52
- } else if (Array.isArray(res)) {
53
- parsed = res;
54
- }
55
- } else if (typeof res === 'string' && res.trim().startsWith('{')) {
56
- const json = JSON.parse(res);
57
- parsed = Array.isArray(json.hubs) ? json.hubs : [];
58
- this.totalLines = json.total_lines || 0;
91
+ if (this.host && typeof this.host.run === 'function') {
92
+ const res = await this.host.run('config', ['show', '--json']);
93
+ if (res?.config) {
94
+ this.config = res.config;
95
+ this.render();
96
+ }
59
97
  }
98
+ } catch {
99
+ // Retain active config defaults if host call fails
100
+ }
101
+ }
60
102
 
61
- this.hubs = parsed;
103
+ async setConfigValue(key, value) {
104
+ this.loading = true;
105
+ this.errorMessage = null;
106
+ this.render();
107
+ try {
108
+ if (this.host && typeof this.host.run === 'function') {
109
+ const res = await this.host.run('config', ['set', key, String(value), '--json']);
110
+ if (res?.config) {
111
+ this.config = res.config;
112
+ }
113
+ }
114
+ } catch (err) {
115
+ this.errorMessage = err.message || `Failed to update ${key}`;
116
+ } finally {
117
+ this.loading = false;
62
118
  this.render();
63
- } catch {
64
- this.hubs = [];
119
+ }
120
+ }
121
+
122
+ async resetConfig() {
123
+ this.loading = true;
124
+ this.errorMessage = null;
125
+ this.render();
126
+ try {
127
+ if (this.host && typeof this.host.run === 'function') {
128
+ const res = await this.host.run('config', ['reset', '--json']);
129
+ if (res?.config) {
130
+ this.config = res.config;
131
+ }
132
+ }
133
+ } catch (err) {
134
+ this.errorMessage = err.message || 'Failed to reset configuration';
135
+ } finally {
136
+ this.loading = false;
65
137
  this.render();
66
138
  }
67
139
  }
68
140
 
69
- async runIngest() {
70
- if (!this.host || this.executing) return;
71
- this.executing = true;
72
- this.lastResult = null;
141
+ async refreshHubs() {
142
+ this.loading = true;
143
+ this.errorMessage = null;
73
144
  this.render();
74
145
 
75
146
  try {
76
- const args = [this.targetPath || 'openwiki'];
77
- if (this.dryRun) args.push('--dry-run');
78
- args.push('--json');
79
-
80
- const res = await this.host.run('ingest', args);
81
- this.lastResult = {
82
- ok: true,
83
- message: this.dryRun ? 'Dry-run preview completed' : 'Ingestion completed successfully',
84
- data: res
85
- };
86
- await this.refreshHubs();
147
+ if (this.host && typeof this.host.run === 'function') {
148
+ const res = await this.host.run('hubs', ['--json']);
149
+ if (res && Array.isArray(res.hubs)) {
150
+ this.hubs = res.hubs;
151
+ this.statusText = 'SYNCHRONIZED';
152
+ } else {
153
+ this.hubs = [];
154
+ this.statusText = 'UNINDEXED';
155
+ }
156
+
157
+ try {
158
+ const statusRes = await this.host.run('status', ['--json']);
159
+ if (statusRes?.upstream) {
160
+ this.upstream = statusRes.upstream;
161
+ }
162
+ } catch {
163
+ // Upstream metadata optional
164
+ }
165
+ } else {
166
+ this.statusText = 'DISCONNECTED';
167
+ }
168
+ this.lastRefreshedAt = new Date().toLocaleTimeString();
169
+ } catch (err) {
170
+ this.statusText = 'ERROR';
171
+ this.errorMessage = err.message || 'Hub synchronization failure';
172
+ this.hubs = [];
173
+ } finally {
174
+ this.loading = false;
175
+ this.render();
176
+ }
177
+ }
178
+
179
+ async loadRefreshReport() {
180
+ this.loading = true;
181
+ this.errorMessage = null;
182
+ this.render();
183
+ try {
184
+ if (this.host && typeof this.host.run === 'function') {
185
+ const res = await this.host.run('refresh', ['--json']);
186
+ if (res) {
187
+ this.refreshReports = Array.isArray(res.reports) ? res.reports : [];
188
+ this.refreshCounts = res.counts || null;
189
+ }
190
+ }
87
191
  } catch (err) {
88
- this.lastResult = {
89
- ok: false,
90
- message: err instanceof Error ? err.message : 'Ingestion failed'
91
- };
192
+ this.errorMessage = err.message || 'Fleet status check failure';
92
193
  } finally {
93
- this.executing = false;
194
+ this.loading = false;
94
195
  this.render();
95
196
  }
96
197
  }
97
198
 
98
- async runBootstrap() {
99
- if (!this.host || this.executing) return;
100
- this.executing = true;
101
- this.lastResult = null;
199
+ async executeFleetRefresh() {
200
+ if (this.loading) return;
201
+ this.loading = true;
202
+ this.executionResult = null;
203
+ this.errorMessage = null;
102
204
  this.render();
103
205
 
104
206
  try {
105
- const res = await this.host.run('bootstrap', ['--json']);
106
- this.lastResult = {
107
- ok: true,
108
- message: 'OpenWiki skeleton bootstrapped',
109
- data: res
110
- };
111
- await this.refreshHubs();
207
+ if (this.host && typeof this.host.run === 'function') {
208
+ const res = await this.host.run('refresh', ['--apply', '--json']);
209
+ this.executionResult = {
210
+ action: 'fleet-refresh',
211
+ target: 'all-registered-projects',
212
+ details: res && typeof res === 'object' ? res : { message: String(res || 'Completed') },
213
+ timestamp: new Date().toLocaleTimeString()
214
+ };
215
+ if (res?.reports) {
216
+ this.refreshReports = res.reports;
217
+ this.refreshCounts = res.counts;
218
+ }
219
+ } else {
220
+ throw new Error('Host execution context unavailable');
221
+ }
112
222
  } catch (err) {
113
- this.lastResult = {
114
- ok: false,
115
- message: err instanceof Error ? err.message : 'Bootstrap failed'
116
- };
223
+ this.errorMessage = err.message || 'Fleet refresh execution failure';
117
224
  } finally {
118
- this.executing = false;
225
+ this.loading = false;
119
226
  this.render();
120
227
  }
121
228
  }
122
229
 
230
+ async executeIngest() {
231
+ if (this.loading) return;
232
+ this.loading = true;
233
+ this.executionResult = null;
234
+ this.errorMessage = null;
235
+ this.render();
236
+
237
+ const args = [this.targetPath || 'openwiki'];
238
+ if (this.dryRun) args.push('--dry-run');
239
+
240
+ try {
241
+ if (this.host && typeof this.host.run === 'function') {
242
+ const res = await this.host.run('ingest', args);
243
+ this.executionResult = {
244
+ action: 'ingest',
245
+ target: this.targetPath,
246
+ dryRun: this.dryRun,
247
+ details: res && typeof res === 'object' ? res : { message: String(res || 'Completed') },
248
+ timestamp: new Date().toLocaleTimeString()
249
+ };
250
+ } else {
251
+ throw new Error('Host execution context unavailable');
252
+ }
253
+ } catch (err) {
254
+ this.errorMessage = err.message || 'Ingestion cycle failure';
255
+ } finally {
256
+ this.loading = false;
257
+ this.refreshHubs();
258
+ }
259
+ }
260
+
261
+ async executeBootstrap() {
262
+ if (this.loading) return;
263
+ this.loading = true;
264
+ this.executionResult = null;
265
+ this.errorMessage = null;
266
+ this.render();
267
+
268
+ try {
269
+ if (this.host && typeof this.host.run === 'function') {
270
+ const res = await this.host.run('bootstrap', ['--brain', '.', '--json']);
271
+ this.executionResult = {
272
+ action: 'bootstrap',
273
+ details: res && typeof res === 'object' ? res : { message: String(res || 'Completed') },
274
+ timestamp: new Date().toLocaleTimeString()
275
+ };
276
+ } else {
277
+ throw new Error('Host execution context unavailable');
278
+ }
279
+ } catch (err) {
280
+ this.errorMessage = err.message || 'Bootstrap procedure failure';
281
+ } finally {
282
+ this.loading = false;
283
+ this.refreshHubs();
284
+ }
285
+ }
286
+
287
+ async executeWatch() {
288
+ if (this.loading) return;
289
+ this.loading = true;
290
+ this.executionResult = null;
291
+ this.errorMessage = null;
292
+ this.render();
293
+
294
+ try {
295
+ if (this.host && typeof this.host.run === 'function') {
296
+ const res = await this.host.run('watch', ['--json']);
297
+ this.executionResult = {
298
+ action: 'watch',
299
+ details: res && typeof res === 'object' ? res : { message: String(res || 'Completed') },
300
+ timestamp: new Date().toLocaleTimeString()
301
+ };
302
+ if (res?.status) {
303
+ this.upstream = {
304
+ recordedVersion: res.status.currentVersion,
305
+ latestVersion: res.status.latestVersion
306
+ };
307
+ }
308
+ } else {
309
+ throw new Error('Host execution context unavailable');
310
+ }
311
+ } catch (err) {
312
+ this.errorMessage = err.message || 'Upstream surveillance failure';
313
+ } finally {
314
+ this.loading = false;
315
+ this.refreshHubs();
316
+ }
317
+ }
318
+
319
+ async executePropagate() {
320
+ if (this.loading) return;
321
+ this.loading = true;
322
+ this.executionResult = null;
323
+ this.errorMessage = null;
324
+ this.render();
325
+
326
+ try {
327
+ if (this.host && typeof this.host.run === 'function') {
328
+ const res = await this.host.run('propagate', ['--json']);
329
+ this.executionResult = {
330
+ action: 'propagate',
331
+ details: res && typeof res === 'object' ? res : { message: String(res || 'Completed') },
332
+ timestamp: new Date().toLocaleTimeString()
333
+ };
334
+ } else {
335
+ throw new Error('Host execution context unavailable');
336
+ }
337
+ } catch (err) {
338
+ this.errorMessage = err.message || 'Fleet propagation failure';
339
+ } finally {
340
+ this.loading = false;
341
+ this.refreshHubs();
342
+ }
343
+ }
344
+
123
345
  render() {
124
- const root = this.shadowRoot;
125
- if (!root) return;
346
+ if (!this.shadowRoot) return;
347
+
348
+ const countText = this.hubs.length ? `${this.hubs.length} Hubs` : '—';
349
+ const totalLines = this.hubs.reduce((acc, h) => acc + (h.lines || 0), 0);
350
+ const linesText = this.hubs.length ? `${totalLines.toLocaleString()} Lines` : '—';
351
+ const upstreamText = this.upstream?.latestVersion ? `v${this.upstream.latestVersion}` : 'Tracking';
126
352
 
127
- root.innerHTML = `
353
+ this.shadowRoot.innerHTML = `
128
354
  <style>
129
355
  :host {
356
+ display: block;
357
+ box-sizing: border-box;
130
358
  --face-bg: #0f172a;
131
359
  --face-surface: #1e293b;
132
- --face-line: #334155;
360
+ --face-surface-subtle: #334155;
361
+ --face-border: #475569;
133
362
  --face-text: #f8fafc;
134
- --face-muted: #94a3b8;
135
- --face-brand: #3b82f6;
136
- --face-brand-hover: #60a5fa;
137
- --face-accent: #facc15;
363
+ --face-text-muted: #94a3b8;
364
+ --face-accent: #f59e0b;
365
+ --face-accent-hover: #d97706;
138
366
  --face-ok: #10b981;
139
- --face-warn: #f59e0b;
140
367
  --face-danger: #ef4444;
141
- --face-dark: #0f172a;
142
-
143
- display: block;
144
- font-family: var(--font-sans, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif);
368
+ background: var(--face-bg);
145
369
  color: var(--text-primary, var(--face-text));
146
- background: var(--bg-secondary, var(--face-bg));
147
- border: 1px solid var(--border, var(--face-line));
370
+ border: 1px solid var(--border, var(--face-border));
148
371
  border-radius: 8px;
149
372
  padding: 16px;
150
- box-sizing: border-box;
151
- overflow: hidden;
373
+ font-family: system-ui, -apple-system, sans-serif;
152
374
  }
153
375
 
154
- @media (prefers-reduced-motion: reduce) {
155
- *, ::before, ::after {
156
- animation-duration: 0.01ms !important;
157
- animation-iteration-count: 1 !important;
158
- transition-duration: 0.01ms !important;
159
- }
376
+ *, *::before, *::after {
377
+ box-sizing: inherit;
160
378
  }
161
379
 
162
- /* Header Bar */
163
- .header {
380
+ .header-bar {
164
381
  display: flex;
165
382
  align-items: center;
166
383
  justify-content: space-between;
167
- flex-wrap: wrap;
168
384
  gap: 12px;
169
- padding-bottom: 14px;
170
- border-bottom: 1px solid var(--border, var(--face-line));
171
- margin-bottom: 16px;
385
+ margin-bottom: 12px;
386
+ border-bottom: 1px solid var(--border, var(--face-border));
387
+ padding-bottom: 12px;
172
388
  }
173
389
 
174
- .brand-group {
390
+ .brand-cluster {
175
391
  display: flex;
176
392
  align-items: center;
177
- gap: 10px;
393
+ gap: 8px;
178
394
  }
179
395
 
180
396
  .wordmark-badge {
181
- font-family: var(--font-mono, 'JetBrains Mono', monospace);
397
+ display: inline-flex;
398
+ align-items: center;
399
+ font-family: ui-monospace, monospace;
182
400
  font-size: 13px;
183
- font-weight: 800;
401
+ font-weight: 700;
184
402
  letter-spacing: 0.05em;
185
- color: var(--card-accent, var(--face-accent));
186
- background: rgba(250, 204, 21, 0.12);
187
- padding: 6px 10px;
188
- border-radius: 6px;
189
- border: 1px solid rgba(250, 204, 21, 0.3);
190
- }
191
-
192
- .title-meta {
193
- display: flex;
194
- flex-direction: column;
403
+ color: var(--face-accent);
404
+ background: var(--face-surface-subtle);
405
+ padding: 4px 8px;
406
+ border-radius: 4px;
195
407
  }
196
408
 
197
- .plugin-title {
409
+ .system-title {
198
410
  font-size: 14px;
199
- font-weight: 700;
411
+ font-weight: 600;
200
412
  color: var(--text-primary, var(--face-text));
201
- margin: 0;
202
413
  }
203
414
 
204
- .plugin-subtitle {
415
+ .status-pill {
416
+ display: inline-flex;
417
+ align-items: center;
418
+ padding: 4px 10px;
419
+ border-radius: 12px;
205
420
  font-size: 11px;
206
- color: var(--text-secondary, var(--face-muted));
207
- margin: 0;
421
+ font-weight: 700;
422
+ letter-spacing: 0.04em;
423
+ text-transform: uppercase;
424
+ background: var(--face-surface-subtle);
425
+ color: var(--face-text-muted);
426
+ border: 1px solid var(--border, var(--face-border));
427
+ }
428
+
429
+ .status-pill.ok {
430
+ color: var(--face-ok);
431
+ border-color: var(--face-ok);
432
+ }
433
+
434
+ .status-pill.danger {
435
+ color: var(--face-danger);
436
+ border-color: var(--face-danger);
208
437
  }
209
438
 
210
- .hub-count-chip {
439
+ .screen-tabs {
211
440
  display: flex;
212
- align-items: center;
213
441
  gap: 6px;
214
- font-family: var(--font-mono, 'JetBrains Mono', monospace);
215
- font-size: 11px;
216
- font-weight: 700;
217
- color: var(--face-accent);
218
- background: rgba(250, 204, 21, 0.12);
219
- border: 1px solid rgba(250, 204, 21, 0.3);
220
- padding: 5px 12px;
442
+ margin-bottom: 16px;
443
+ border-bottom: 1px solid var(--border, var(--face-border));
444
+ padding-bottom: 8px;
445
+ overflow-x: auto;
446
+ }
447
+
448
+ .screen-tab-btn {
449
+ min-height: 36px;
450
+ padding: 0 12px;
451
+ font-size: 12px;
452
+ font-weight: 600;
221
453
  border-radius: 6px;
222
- text-transform: uppercase;
454
+ border: 1px solid transparent;
455
+ background: transparent;
456
+ color: var(--face-text-muted);
457
+ cursor: pointer;
458
+ transition: all 0.15s ease;
459
+ display: inline-flex;
460
+ align-items: center;
461
+ white-space: nowrap;
223
462
  }
224
463
 
225
- /* Split Layout */
226
- .workspace-split {
464
+ .screen-tab-btn:hover {
465
+ color: var(--face-text);
466
+ background: var(--face-surface-subtle);
467
+ }
468
+
469
+ .screen-tab-btn.active {
470
+ color: var(--face-accent);
471
+ background: var(--face-surface);
472
+ border-color: var(--face-accent);
473
+ }
474
+
475
+ .stats-grid {
227
476
  display: grid;
228
- grid-template-columns: minmax(260px, 1.4fr) minmax(240px, 1fr);
229
- gap: 14px;
477
+ grid-template-columns: repeat(3, 1fr);
478
+ gap: 8px;
230
479
  margin-bottom: 16px;
231
480
  }
232
481
 
233
- @media (max-width: 700px) {
234
- .workspace-split {
235
- grid-template-columns: 1fr;
236
- }
482
+ .stat-card {
483
+ background: var(--face-surface);
484
+ border: 1px solid var(--border, var(--face-border));
485
+ border-radius: 6px;
486
+ padding: 10px;
237
487
  }
238
488
 
239
- /* Hub Tree Matrix */
240
- .hub-panel {
241
- border: 1px solid var(--border, var(--face-line));
242
- border-radius: 6px;
243
- background: rgba(30, 41, 59, 0.4);
244
- padding: 12px;
489
+ .stat-label {
490
+ font-size: 11px;
491
+ text-transform: uppercase;
492
+ letter-spacing: 0.04em;
493
+ color: var(--face-text-muted);
494
+ margin-bottom: 4px;
495
+ }
496
+
497
+ .stat-value {
498
+ font-size: 16px;
499
+ font-weight: 700;
500
+ color: var(--text-primary, var(--face-text));
501
+ font-family: ui-monospace, monospace;
502
+ }
503
+
504
+ .section-header {
245
505
  display: flex;
246
- flex-direction: column;
247
- gap: 10px;
506
+ align-items: center;
507
+ justify-content: space-between;
508
+ margin-bottom: 8px;
248
509
  }
249
510
 
250
- .panel-title {
251
- font-size: 11px;
511
+ .section-title {
512
+ font-size: 12px;
252
513
  font-weight: 700;
253
514
  text-transform: uppercase;
254
515
  letter-spacing: 0.05em;
255
- color: var(--text-secondary, var(--face-muted));
256
- margin: 0;
516
+ color: var(--face-accent);
257
517
  }
258
518
 
259
519
  .hub-list {
260
520
  display: flex;
261
521
  flex-direction: column;
262
522
  gap: 6px;
263
- max-height: 280px;
523
+ margin-bottom: 16px;
524
+ max-height: 180px;
264
525
  overflow-y: auto;
265
526
  }
266
527
 
267
528
  .hub-item {
268
- padding: 10px 12px;
269
- background: rgba(15, 23, 42, 0.6);
270
- border: 1px solid var(--border, var(--face-line));
271
- border-radius: 5px;
272
529
  display: flex;
273
530
  align-items: center;
274
531
  justify-content: space-between;
275
- gap: 10px;
276
- }
277
-
278
- .hub-main {
279
- display: flex;
280
- flex-direction: column;
281
- gap: 2px;
532
+ background: var(--face-surface);
533
+ border: 1px solid var(--border, var(--face-border));
534
+ border-radius: 6px;
535
+ padding: 8px 12px;
536
+ font-size: 13px;
282
537
  }
283
538
 
284
539
  .hub-title {
285
- font-family: var(--font-mono, 'JetBrains Mono', monospace);
286
- font-size: 12px;
287
- font-weight: 700;
540
+ font-weight: 600;
288
541
  color: var(--text-primary, var(--face-text));
289
542
  }
290
543
 
291
- .hub-path {
292
- font-size: 10px;
293
- color: var(--text-secondary, var(--face-muted));
544
+ .hub-meta {
545
+ font-family: ui-monospace, monospace;
546
+ font-size: 11px;
547
+ color: var(--face-text-muted);
294
548
  }
295
549
 
296
- .hub-metrics {
550
+ .empty-state {
297
551
  display: flex;
298
552
  align-items: center;
299
- gap: 6px;
300
- font-family: var(--font-mono, 'JetBrains Mono', monospace);
301
- font-size: 10px;
302
- color: var(--face-accent);
553
+ justify-content: center;
554
+ padding: 24px;
555
+ color: var(--face-text-muted);
556
+ background: var(--face-surface);
557
+ border: 1px dashed var(--border, var(--face-border));
558
+ border-radius: 6px;
559
+ font-size: 13px;
303
560
  }
304
561
 
305
- /* Ingest Deck Column */
306
562
  .ingest-deck {
307
- display: flex;
308
- flex-direction: column;
309
- gap: 10px;
563
+ background: var(--face-surface);
564
+ border: 1px solid var(--border, var(--face-border));
565
+ border-radius: 6px;
566
+ padding: 12px;
567
+ margin-bottom: 16px;
310
568
  }
311
569
 
312
- .form-card {
313
- border: 1px solid var(--border, var(--face-line));
570
+ .config-deck {
571
+ background: var(--face-surface);
572
+ border: 1px solid var(--border, var(--face-border));
314
573
  border-radius: 6px;
315
- background: rgba(30, 41, 59, 0.4);
316
- padding: 12px;
317
- display: flex;
318
- flex-direction: column;
319
- gap: 10px;
574
+ padding: 14px;
575
+ margin-bottom: 16px;
576
+ }
577
+
578
+ .config-grid {
579
+ display: grid;
580
+ grid-template-columns: 1fr 1fr;
581
+ gap: 12px;
582
+ margin-bottom: 14px;
320
583
  }
321
584
 
322
- .input-row {
585
+ .field-group {
323
586
  display: flex;
324
587
  flex-direction: column;
325
588
  gap: 4px;
326
589
  }
327
590
 
328
- .input-label {
329
- font-size: 10px;
591
+ .field-group.full-width {
592
+ grid-column: 1 / -1;
593
+ }
594
+
595
+ .field-label {
596
+ font-size: 11px;
597
+ font-weight: 700;
330
598
  text-transform: uppercase;
331
- color: var(--text-secondary, var(--face-muted));
332
- letter-spacing: 0.05em;
599
+ letter-spacing: 0.04em;
600
+ color: var(--face-text-muted);
601
+ }
602
+
603
+ .input-group {
604
+ display: flex;
605
+ gap: 8px;
606
+ margin-bottom: 12px;
333
607
  }
334
608
 
335
- .text-input {
609
+ .path-input, .text-input, .select-input {
336
610
  min-height: 44px;
611
+ background: var(--face-surface-subtle);
612
+ border: 1px solid var(--border, var(--face-border));
613
+ border-radius: 6px;
337
614
  padding: 8px 12px;
338
- font-family: var(--font-mono, 'JetBrains Mono', monospace);
339
- font-size: 12px;
340
615
  color: var(--text-primary, var(--face-text));
341
- background: rgba(15, 23, 42, 0.8);
342
- border: 1px solid var(--border, var(--face-line));
343
- border-radius: 5px;
344
- box-sizing: border-box;
616
+ font-family: ui-monospace, monospace;
617
+ font-size: 13px;
345
618
  width: 100%;
346
619
  }
347
620
 
348
- .text-input:focus {
621
+ .path-input:focus, .text-input:focus, .select-input:focus {
349
622
  outline: none;
350
- border-color: var(--card-accent, var(--face-brand));
623
+ border-color: var(--face-accent);
351
624
  }
352
625
 
353
- .toggle-row {
626
+ .presets-bar {
354
627
  display: flex;
355
- align-items: center;
356
- gap: 8px;
628
+ gap: 6px;
629
+ flex-wrap: wrap;
630
+ margin-bottom: 14px;
631
+ }
632
+
633
+ .preset-chip {
634
+ min-height: 32px;
635
+ padding: 4px 10px;
636
+ background: var(--face-surface-subtle);
637
+ border: 1px solid var(--border, var(--face-border));
638
+ border-radius: 16px;
357
639
  font-size: 11px;
358
- color: var(--text-secondary, var(--face-muted));
640
+ font-family: ui-monospace, monospace;
641
+ color: var(--face-text);
359
642
  cursor: pointer;
643
+ transition: all 0.15s ease;
644
+ }
645
+
646
+ .preset-chip:hover {
647
+ border-color: var(--face-accent);
648
+ color: var(--face-accent);
360
649
  }
361
650
 
362
- .toggle-check {
363
- min-height: 20px;
364
- min-width: 20px;
651
+ .auth-notice-card {
652
+ display: flex;
653
+ align-items: center;
654
+ justify-content: space-between;
655
+ background: #022c22;
656
+ border: 1px solid var(--face-ok);
657
+ border-radius: 6px;
658
+ padding: 10px 14px;
659
+ margin-bottom: 14px;
660
+ font-size: 12px;
661
+ color: #a7f3d0;
662
+ }
663
+
664
+ .toggle-label {
665
+ display: inline-flex;
666
+ align-items: center;
667
+ gap: 8px;
668
+ min-height: 44px;
669
+ font-size: 13px;
670
+ color: var(--text-primary, var(--face-text));
365
671
  cursor: pointer;
672
+ user-select: none;
673
+ padding: 0 4px;
366
674
  }
367
675
 
368
- .btn-action-primary {
676
+ .button-group {
677
+ display: grid;
678
+ grid-template-columns: repeat(2, 1fr);
679
+ gap: 8px;
680
+ }
681
+
682
+ .btn {
683
+ display: inline-flex;
684
+ align-items: center;
685
+ justify-content: center;
369
686
  min-height: 44px;
370
- padding: 8px 16px;
371
- font-family: var(--font-sans, sans-serif);
687
+ padding: 0 12px;
372
688
  font-size: 12px;
373
- font-weight: 700;
374
- color: var(--face-dark);
375
- background: var(--face-accent);
376
- border: none;
377
- border-radius: 5px;
689
+ font-weight: 600;
690
+ border-radius: 6px;
691
+ border: 1px solid transparent;
378
692
  cursor: pointer;
379
- transition: background 0.15s ease;
693
+ transition: background-color 0.15s ease, border-color 0.15s ease;
694
+ text-align: center;
380
695
  }
381
696
 
382
- .btn-action-primary:hover {
383
- background: #fde047;
697
+ .btn-primary {
698
+ background: var(--face-accent);
699
+ color: var(--face-bg);
384
700
  }
385
701
 
386
- .btn-action-primary:disabled {
387
- opacity: 0.6;
388
- cursor: not-allowed;
702
+ .btn-primary:hover:not(:disabled) {
703
+ background: var(--face-accent-hover);
389
704
  }
390
705
 
391
- .btn-action-secondary {
392
- min-height: 44px;
393
- padding: 8px 16px;
394
- font-family: var(--font-sans, sans-serif);
395
- font-size: 12px;
396
- font-weight: 700;
706
+ .btn-secondary {
707
+ background: var(--face-surface-subtle);
708
+ border-color: var(--border, var(--face-border));
397
709
  color: var(--text-primary, var(--face-text));
398
- background: transparent;
399
- border: 1px solid var(--border, var(--face-line));
400
- border-radius: 5px;
401
- cursor: pointer;
402
- transition: background 0.15s ease;
403
710
  }
404
711
 
405
- .btn-action-secondary:hover {
406
- background: rgba(255, 255, 255, 0.05);
712
+ .btn-secondary:hover:not(:disabled) {
713
+ border-color: var(--face-accent);
407
714
  }
408
715
 
409
- .feedback-banner {
410
- padding: 8px 12px;
411
- border-radius: 5px;
412
- font-family: var(--font-mono, 'JetBrains Mono', monospace);
413
- font-size: 11px;
716
+ .btn:disabled {
717
+ opacity: 0.5;
718
+ cursor: not-allowed;
414
719
  }
415
720
 
416
- .feedback-ok {
417
- background: rgba(16, 185, 129, 0.15);
418
- border: 1px solid rgba(16, 185, 129, 0.4);
419
- color: var(--face-ok);
721
+ .result-deck {
722
+ background: var(--face-surface-subtle);
723
+ border: 1px solid var(--border, var(--face-border));
724
+ border-radius: 6px;
725
+ padding: 12px;
726
+ font-family: ui-monospace, monospace;
727
+ font-size: 12px;
728
+ color: var(--text-primary, var(--face-text));
729
+ margin-top: 12px;
420
730
  }
421
731
 
422
- .feedback-fail {
423
- background: rgba(239, 68, 68, 0.15);
424
- border: 1px solid rgba(239, 68, 68, 0.4);
425
- color: var(--face-danger);
732
+ .result-title {
733
+ font-weight: 600;
734
+ color: var(--face-accent);
735
+ margin-bottom: 6px;
426
736
  }
427
737
 
428
- .empty-hubs {
429
- padding: 32px 16px;
430
- text-align: center;
738
+ .error-deck {
739
+ background: var(--face-surface);
740
+ border: 1px solid var(--face-danger);
741
+ border-radius: 6px;
742
+ padding: 12px;
743
+ color: var(--face-danger);
431
744
  font-size: 12px;
432
- color: var(--text-secondary, var(--face-muted));
745
+ margin-bottom: 12px;
433
746
  }
434
747
 
435
- /* Footer Telemetry */
436
- .footer-bar {
437
- display: flex;
438
- justify-content: space-between;
748
+ .repo-status-badge {
749
+ display: inline-flex;
439
750
  align-items: center;
440
- padding-top: 12px;
441
- margin-top: 14px;
442
- border-top: 1px solid var(--border, var(--face-line));
751
+ padding: 2px 6px;
752
+ border-radius: 4px;
443
753
  font-size: 10px;
444
- color: var(--text-secondary, var(--face-muted));
754
+ font-weight: 700;
755
+ letter-spacing: 0.04em;
756
+ text-transform: uppercase;
757
+ }
758
+
759
+ .repo-status-badge.current {
760
+ background: #064e3b;
761
+ color: #6ee7b7;
762
+ }
763
+
764
+ .repo-status-badge.due {
765
+ background: #78350f;
766
+ color: #fde68a;
767
+ }
768
+
769
+ .repo-status-badge.uninit {
770
+ background: #831843;
771
+ color: #fbcfe8;
772
+ }
773
+
774
+ @media (prefers-reduced-motion: reduce) {
775
+ *, *::before, *::after {
776
+ animation-duration: 0.01ms !important;
777
+ animation-iteration-count: 1 !important;
778
+ transition-duration: 0.01ms !important;
779
+ scroll-behavior: auto !important;
780
+ }
445
781
  }
446
782
  </style>
447
783
 
448
- <div class="header">
449
- <div class="brand-group">
450
- <div class="wordmark-badge">[BRAIN//DUMP]</div>
451
- <div class="title-meta">
452
- <h3 class="plugin-title">OpenWiki Knowledge Graph</h3>
453
- <p class="plugin-subtitle">Multi-repository documentation and knowledge ingestion engine</p>
784
+ <div class="header-bar">
785
+ <div class="brand-cluster">
786
+ <span class="wordmark-badge">[BRAIN//DUMP]</span>
787
+ <span class="system-title">Knowledge Graph Engine</span>
788
+ </div>
789
+ <span class="status-pill ${this.statusText === 'SYNCHRONIZED' ? 'ok' : this.statusText === 'ERROR' ? 'danger' : ''}">
790
+ ${this.statusText}
791
+ </span>
792
+ </div>
793
+
794
+ <nav class="screen-tabs" aria-label="OpenWiki navigation screens">
795
+ ${OpenWikiCard.screens.map((screen) => `
796
+ <button
797
+ class="screen-tab-btn ${this.activeScreen === screen.id ? 'active' : ''}"
798
+ data-screen="${screen.id}"
799
+ >
800
+ ${screen.label}
801
+ </button>
802
+ `).join('')}
803
+ </nav>
804
+
805
+ <div class="stats-grid">
806
+ <div class="stat-card">
807
+ <div class="stat-label">Discovered Hubs</div>
808
+ <div class="stat-value">${countText}</div>
809
+ </div>
810
+ <div class="stat-card">
811
+ <div class="stat-label">Documentation Lines</div>
812
+ <div class="stat-value">${linesText}</div>
813
+ </div>
814
+ <div class="stat-card">
815
+ <div class="stat-label">Active Model Routing</div>
816
+ <div class="stat-value" style="font-size: 12px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;" title="${this.config.model}">
817
+ ${this.config.model.split('/').pop() || this.config.model}
454
818
  </div>
455
819
  </div>
456
- <div class="hub-count-chip">
457
- ${this.hubs.length > 0 ? `${this.hubs.length} Hubs Indexed` : 'No Hubs'}
820
+ </div>
821
+
822
+ ${this.errorMessage ? `
823
+ <div class="error-deck">
824
+ ${this.errorMessage}
825
+ </div>
826
+ ` : ''}
827
+
828
+ ${this.renderActiveScreen()}
829
+
830
+ ${this.executionResult ? `
831
+ <div class="result-deck">
832
+ <div class="result-title">Execution Receipt (${this.executionResult.timestamp})</div>
833
+ <div>Action: ${this.executionResult.action}</div>
834
+ <div>Target: ${this.executionResult.target || 'project'}</div>
835
+ <div>Result: ${JSON.stringify(this.executionResult.details)}</div>
458
836
  </div>
837
+ ` : ''}
838
+ `;
839
+
840
+ this.bindEvents();
841
+ }
842
+
843
+ renderActiveScreen() {
844
+ if (this.activeScreen === 'config') {
845
+ return this.renderConfigScreen();
846
+ }
847
+ if (this.activeScreen === 'refresh') {
848
+ return this.renderRefreshScreen();
849
+ }
850
+ if (this.activeScreen === 'upstream') {
851
+ return this.renderUpstreamScreen();
852
+ }
853
+ return this.renderHubsScreen();
854
+ }
855
+
856
+ renderHubsScreen() {
857
+ return `
858
+ <div class="section-header">
859
+ <span class="section-title">Knowledge Hubs</span>
860
+ <button id="btn-refresh" class="btn btn-secondary" style="min-height: 32px; padding: 0 10px; font-size: 11px;" ${this.loading ? 'disabled' : ''}>
861
+ Sync
862
+ </button>
459
863
  </div>
460
864
 
461
- <div class="workspace-split">
462
- <div class="hub-panel">
463
- <h4 class="panel-title">Verified Documentation Hubs</h4>
464
- <div class="hub-list">
465
- ${this.hubs.length > 0 ? this.hubs.map(h => `
466
- <div class="hub-item">
467
- <div class="hub-main">
468
- <span class="hub-title">${escapeHtml(h.title || h.path)}</span>
469
- <span class="hub-path">${escapeHtml(h.path)}</span>
470
- </div>
471
- <div class="hub-metrics">
472
- <span>${h.lines !== undefined ? `${h.lines} lines` : DASH}</span>
473
- </div>
474
- </div>
475
- `).join('') : `
476
- <div class="empty-hubs">No markdown documentation hubs indexed yet. Click Bootstrap below to initialize.</div>
477
- `}
865
+ <div class="hub-list">
866
+ ${this.hubs.length === 0 ? `
867
+ <div class="empty-state">No documentation hubs indexed in ${this.targetPath}</div>
868
+ ` : this.hubs.map((h) => `
869
+ <div class="hub-item">
870
+ <span class="hub-title">${h.title}</span>
871
+ <span class="hub-meta">${h.lines} lines · ${h.path}</span>
478
872
  </div>
873
+ `).join('')}
874
+ </div>
875
+
876
+ <div class="ingest-deck">
877
+ <div class="input-group">
878
+ <input
879
+ id="target-path-input"
880
+ class="path-input"
881
+ type="text"
882
+ value="${this.targetPath}"
883
+ placeholder="Directory path (openwiki)"
884
+ aria-label="Target documentation path"
885
+ />
886
+ <label class="toggle-label">
887
+ <input
888
+ id="dry-run-checkbox"
889
+ type="checkbox"
890
+ ${this.dryRun ? 'checked' : ''}
891
+ />
892
+ Dry Run
893
+ </label>
479
894
  </div>
480
895
 
481
- <div class="ingest-deck">
482
- <div class="form-card">
483
- <h4 class="panel-title">Knowledge Ingest</h4>
484
- <div class="input-row">
485
- <span class="input-label">Target Hub Path</span>
486
- <input class="text-input" type="text" id="input-path" placeholder="openwiki" />
487
- </div>
488
- <label class="toggle-row">
489
- <input class="toggle-check" type="checkbox" id="check-dryrun" ${this.dryRun ? 'checked' : ''} />
490
- <span>Dry Run (Preview changes without writing)</span>
491
- </label>
492
- <button class="btn-action-primary" type="button" id="btn-ingest" ${this.executing ? 'disabled' : ''}>
493
- ${this.executing ? 'Processing...' : 'Run Ingestion'}
494
- </button>
495
- <button class="btn-action-secondary" type="button" id="btn-bootstrap" ${this.executing ? 'disabled' : ''}>
496
- Bootstrap OpenWiki Skeleton
497
- </button>
896
+ <div class="button-group">
897
+ <button id="btn-ingest" class="btn btn-primary" ${this.loading ? 'disabled' : ''}>
898
+ ${this.loading ? 'Processing…' : 'Ingest Documentation'}
899
+ </button>
900
+ <button id="btn-bootstrap" class="btn btn-secondary" ${this.loading ? 'disabled' : ''}>
901
+ Bootstrap Wiki
902
+ </button>
903
+ </div>
904
+ </div>
905
+ `;
906
+ }
907
+
908
+ renderConfigScreen() {
909
+ return `
910
+ <div class="config-deck">
911
+ <div class="section-header">
912
+ <span class="section-title">Documentation Model & Provider Routing</span>
913
+ </div>
914
+
915
+ <div class="auth-notice-card">
916
+ <span>✓ Model credentials routed via Total Recall Auth store (<code>total-recall auth secret</code>)</span>
917
+ <span style="font-family: ui-monospace, monospace; font-size: 10px;">AUTH_PLUGIN: ACTIVE</span>
918
+ </div>
919
+
920
+ <div class="field-label" style="margin-bottom: 6px;">Recommended Model Presets:</div>
921
+ <div class="presets-bar">
922
+ <button class="preset-chip" data-provider="openrouter" data-model="deepseek/deepseek-v4.1-flash" data-fallback="qwen/qwen-2.5-coder-32b-instruct">
923
+ DeepSeek V4.1 Flash ($0.025/1M)
924
+ </button>
925
+ <button class="preset-chip" data-provider="openrouter" data-model="qwen/qwen-2.5-coder-32b-instruct" data-fallback="deepseek/deepseek-v4.1-flash">
926
+ Qwen 2.5 Coder 32B ($0.06/1M)
927
+ </button>
928
+ <button class="preset-chip" data-provider="google" data-model="gemini-2.5-flash-lite" data-fallback="gemini-2.5-flash">
929
+ Gemini 2.5 Flash-Lite ($0.075/1M)
930
+ </button>
931
+ </div>
932
+
933
+ <div class="config-grid">
934
+ <div class="field-group">
935
+ <label class="field-label" for="cfg-provider">Provider</label>
936
+ <select id="cfg-provider" class="select-input">
937
+ <option value="openrouter" ${this.config.provider === 'openrouter' ? 'selected' : ''}>OpenRouter (Multi-Model Hub)</option>
938
+ <option value="google" ${this.config.provider === 'google' ? 'selected' : ''}>Google (Gemini Direct)</option>
939
+ <option value="custom" ${this.config.provider === 'custom' ? 'selected' : ''}>Custom Provider</option>
940
+ </select>
498
941
  </div>
499
942
 
500
- ${this.lastResult ? `
501
- <div class="feedback-banner ${this.lastResult.ok ? 'feedback-ok' : 'feedback-fail'}">
502
- ${escapeHtml(this.lastResult.message)}
503
- </div>
504
- ` : ''}
943
+ <div class="field-group">
944
+ <label class="field-label" for="cfg-model">Primary Model Identifier</label>
945
+ <input id="cfg-model" class="text-input" type="text" value="${this.config.model}" placeholder="e.g. deepseek/deepseek-v4.1-flash" />
946
+ </div>
947
+
948
+ <div class="field-group">
949
+ <label class="field-label" for="cfg-fallback">Fallback Model Identifier</label>
950
+ <input id="cfg-fallback" class="text-input" type="text" value="${this.config.fallback_model}" placeholder="e.g. qwen/qwen-2.5-coder-32b-instruct" />
951
+ </div>
952
+
953
+ <div class="field-group">
954
+ <label class="field-label" for="cfg-tokens">Max Output Tokens</label>
955
+ <input id="cfg-tokens" class="text-input" type="number" value="${this.config.max_tokens}" min="512" max="131072" />
956
+ </div>
957
+
958
+ <div class="field-group full-width">
959
+ <label class="field-label" for="cfg-timeout">Timeout (milliseconds)</label>
960
+ <input id="cfg-timeout" class="text-input" type="number" value="${this.config.timeout_ms}" min="30000" max="1800000" />
961
+ </div>
962
+ </div>
963
+
964
+ <div class="button-group">
965
+ <button id="btn-save-config" class="btn btn-primary" ${this.loading ? 'disabled' : ''}>
966
+ Save Configuration
967
+ </button>
968
+ <button id="btn-reset-config" class="btn btn-secondary" ${this.loading ? 'disabled' : ''}>
969
+ Reset to Defaults
970
+ </button>
505
971
  </div>
506
972
  </div>
973
+ `;
974
+ }
975
+
976
+ renderRefreshScreen() {
977
+ const dueCount = this.refreshCounts?.due ?? this.refreshReports.filter((r) => r.action === 'due').length;
978
+ const uninitCount = this.refreshCounts?.uninitialized ?? this.refreshReports.filter((r) => r.uninitialized).length;
979
+
980
+ return `
981
+ <div class="ingest-deck">
982
+ <div class="section-header">
983
+ <span class="section-title">Fleet Documentation Governance</span>
984
+ <button id="btn-sync-refresh" class="btn btn-secondary" style="min-height: 32px; padding: 0 10px; font-size: 11px;" ${this.loading ? 'disabled' : ''}>
985
+ Check Fleet
986
+ </button>
987
+ </div>
507
988
 
508
- <div class="footer-bar">
509
- <span>Engine: OpenWiki v0.1.2</span>
510
- <span>Storage: Markdown + SSSS Projections</span>
989
+ <div style="font-size: 12px; color: var(--face-text-muted); margin-bottom: 12px;">
990
+ Registered Projects: <strong>${this.refreshReports.length}</strong> · Due for Update: <strong>${dueCount}</strong> · Uninitialized: <strong>${uninitCount}</strong>
991
+ </div>
992
+
993
+ <div class="hub-list" style="max-height: 240px;">
994
+ ${this.refreshReports.length === 0 ? `
995
+ <div class="empty-state">No fleet report loaded. Click Check Fleet to inspect registered repositories.</div>
996
+ ` : this.refreshReports.map((r) => {
997
+ let badgeClass = 'current';
998
+ let badgeText = 'CURRENT';
999
+ if (r.uninitialized) {
1000
+ badgeClass = 'uninit';
1001
+ badgeText = 'MISSING WIKI';
1002
+ } else if (r.action === 'due') {
1003
+ badgeClass = 'due';
1004
+ badgeText = 'DUE';
1005
+ }
1006
+ return `
1007
+ <div class="hub-item">
1008
+ <div>
1009
+ <span class="hub-title">${r.name}</span>
1010
+ <div class="hub-meta">${r.generatedAt ? `Generated ${r.generatedAt.slice(0, 10)}` : 'Never generated'} · ${r.commitsSince ? `${r.commitsSince} commits behind` : 'Up to date'}</div>
1011
+ </div>
1012
+ <span class="repo-status-badge ${badgeClass}">${badgeText}</span>
1013
+ </div>
1014
+ `;
1015
+ }).join('')}
1016
+ </div>
1017
+
1018
+ <div style="margin-top: 12px;">
1019
+ <button id="btn-fleet-refresh-apply" class="btn btn-primary" style="width: 100%;" ${this.loading ? 'disabled' : ''}>
1020
+ ${this.loading ? 'Executing Fleet Refresh…' : `Regenerate Stale & Initialize Missing Wikis (${dueCount} due)`}
1021
+ </button>
1022
+ </div>
511
1023
  </div>
512
1024
  `;
1025
+ }
1026
+
1027
+ renderUpstreamScreen() {
1028
+ const currentVer = this.upstream?.recordedVersion || '0.7.2';
1029
+ const latestVer = this.upstream?.latestVersion || '0.7.3';
1030
+
1031
+ return `
1032
+ <div class="ingest-deck">
1033
+ <div class="section-header">
1034
+ <span class="section-title">Upstream Package Surveillance</span>
1035
+ </div>
1036
+
1037
+ <div class="stats-grid" style="margin-bottom: 14px;">
1038
+ <div class="stat-card">
1039
+ <div class="stat-label">Recorded Version</div>
1040
+ <div class="stat-value">v${currentVer}</div>
1041
+ </div>
1042
+ <div class="stat-card">
1043
+ <div class="stat-label">Latest Registry</div>
1044
+ <div class="stat-value">v${latestVer}</div>
1045
+ </div>
1046
+ <div class="stat-card">
1047
+ <div class="stat-label">Watcher Status</div>
1048
+ <div class="stat-value" style="color: var(--face-ok);">MONITORING</div>
1049
+ </div>
1050
+ </div>
1051
+
1052
+ <div class="button-group">
1053
+ <button id="btn-watch" class="btn btn-secondary" ${this.loading ? 'disabled' : ''}>
1054
+ Check Upstream Registry
1055
+ </button>
1056
+ <button id="btn-propagate" class="btn btn-primary" ${this.loading ? 'disabled' : ''}>
1057
+ Propagate Latest Generator
1058
+ </button>
1059
+ </div>
1060
+ </div>
1061
+ `;
1062
+ }
1063
+
1064
+ bindEvents() {
1065
+ const shadow = this.shadowRoot;
1066
+ if (!shadow) return;
513
1067
 
514
- // Event listeners
515
- const pathInput = root.querySelector('#input-path');
1068
+ shadow.querySelectorAll('.screen-tab-btn').forEach((tab) => {
1069
+ tab.addEventListener('click', () => {
1070
+ const targetScreen = tab.getAttribute('data-screen');
1071
+ if (targetScreen) this.setScreen(targetScreen);
1072
+ });
1073
+ });
1074
+
1075
+ const pathInput = shadow.querySelector('#target-path-input');
516
1076
  if (pathInput) {
517
- pathInput.value = this.targetPath;
518
- pathInput.addEventListener('input', e => {
519
- this.targetPath = e.target.value;
1077
+ pathInput.addEventListener('input', (e) => {
1078
+ this.targetPath = e.target.value.trim();
520
1079
  });
521
1080
  }
522
1081
 
523
- const dryRunCheck = root.querySelector('#check-dryrun');
524
- if (dryRunCheck) {
525
- dryRunCheck.addEventListener('change', e => {
1082
+ const dryRunCheckbox = shadow.querySelector('#dry-run-checkbox');
1083
+ if (dryRunCheckbox) {
1084
+ dryRunCheckbox.addEventListener('change', (e) => {
526
1085
  this.dryRun = e.target.checked;
527
1086
  });
528
1087
  }
529
1088
 
530
- root.querySelector('#btn-ingest')?.addEventListener('click', () => this.runIngest());
531
- root.querySelector('#btn-bootstrap')?.addEventListener('click', () => this.runBootstrap());
1089
+ const btnIngest = shadow.querySelector('#btn-ingest');
1090
+ if (btnIngest) {
1091
+ btnIngest.addEventListener('click', () => this.executeIngest());
1092
+ }
1093
+
1094
+ const btnBootstrap = shadow.querySelector('#btn-bootstrap');
1095
+ if (btnBootstrap) {
1096
+ btnBootstrap.addEventListener('click', () => this.executeBootstrap());
1097
+ }
1098
+
1099
+ const btnRefresh = shadow.querySelector('#btn-refresh');
1100
+ if (btnRefresh) {
1101
+ btnRefresh.addEventListener('click', () => this.refreshHubs());
1102
+ }
1103
+
1104
+ const btnSyncRefresh = shadow.querySelector('#btn-sync-refresh');
1105
+ if (btnSyncRefresh) {
1106
+ btnSyncRefresh.addEventListener('click', () => this.loadRefreshReport());
1107
+ }
1108
+
1109
+ const btnFleetApply = shadow.querySelector('#btn-fleet-refresh-apply');
1110
+ if (btnFleetApply) {
1111
+ btnFleetApply.addEventListener('click', () => this.executeFleetRefresh());
1112
+ }
1113
+
1114
+ const btnWatch = shadow.querySelector('#btn-watch');
1115
+ if (btnWatch) {
1116
+ btnWatch.addEventListener('click', () => this.executeWatch());
1117
+ }
1118
+
1119
+ const btnPropagate = shadow.querySelector('#btn-propagate');
1120
+ if (btnPropagate) {
1121
+ btnPropagate.addEventListener('click', () => this.executePropagate());
1122
+ }
1123
+
1124
+ shadow.querySelectorAll('.preset-chip').forEach((chip) => {
1125
+ chip.addEventListener('click', async () => {
1126
+ const p = chip.getAttribute('data-provider');
1127
+ const m = chip.getAttribute('data-model');
1128
+ const f = chip.getAttribute('data-fallback');
1129
+ if (p) await this.setConfigValue('provider', p);
1130
+ if (m) await this.setConfigValue('model', m);
1131
+ if (f) await this.setConfigValue('fallback_model', f);
1132
+ });
1133
+ });
1134
+
1135
+ const btnSaveConfig = shadow.querySelector('#btn-save-config');
1136
+ if (btnSaveConfig) {
1137
+ btnSaveConfig.addEventListener('click', async () => {
1138
+ const provider = shadow.querySelector('#cfg-provider')?.value;
1139
+ const model = shadow.querySelector('#cfg-model')?.value;
1140
+ const fallback = shadow.querySelector('#cfg-fallback')?.value;
1141
+ const tokens = shadow.querySelector('#cfg-tokens')?.value;
1142
+ const timeout = shadow.querySelector('#cfg-timeout')?.value;
1143
+
1144
+ if (provider) await this.setConfigValue('provider', provider);
1145
+ if (model) await this.setConfigValue('model', model);
1146
+ if (fallback) await this.setConfigValue('fallback_model', fallback);
1147
+ if (tokens) await this.setConfigValue('max_tokens', tokens);
1148
+ if (timeout) await this.setConfigValue('timeout_ms', timeout);
1149
+ });
1150
+ }
1151
+
1152
+ const btnResetConfig = shadow.querySelector('#btn-reset-config');
1153
+ if (btnResetConfig) {
1154
+ btnResetConfig.addEventListener('click', () => this.resetConfig());
1155
+ }
532
1156
  }
533
1157
  }
534
1158