fraim 2.0.203 → 2.0.204

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 (79) hide show
  1. package/README.md +3 -3
  2. package/bin/fraim.js +8 -18
  3. package/dist/src/cli/commands/first-run.js +2 -37
  4. package/dist/src/cli/doctor/checks/mcp-connectivity-checks.js +1 -1
  5. package/dist/src/cli/fraim.js +0 -2
  6. package/dist/src/cli/setup/ide-invocation-surfaces.js +6 -6
  7. package/dist/src/cli/utils/managed-agent-paths.js +76 -6
  8. package/dist/src/cli/utils/version-utils.js +2 -2
  9. package/dist/src/config/persona-capability-bundles.js +3 -3
  10. package/dist/src/core/job-phases.js +168 -0
  11. package/dist/src/core/utils/ports.js +32 -0
  12. package/dist/src/core/utils/stub-generator.js +24 -2
  13. package/dist/src/first-run/session-service.js +106 -24
  14. package/dist/src/local-mcp-server/learning-context-builder.js +1 -1
  15. package/dist/src/local-mcp-server/stdio-server.js +41 -9
  16. package/dist/src/routes/analytics.js +2 -2
  17. package/dist/src/services/registry-service.js +2 -2
  18. package/index.js +1 -1
  19. package/package.json +61 -181
  20. package/dist/src/ai-hub/catalog.js +0 -406
  21. package/dist/src/ai-hub/cert-store.js +0 -70
  22. package/dist/src/ai-hub/conversation-store.js +0 -359
  23. package/dist/src/ai-hub/desktop-main.js +0 -449
  24. package/dist/src/ai-hub/hosts.js +0 -1664
  25. package/dist/src/ai-hub/hub-latest-version.js +0 -52
  26. package/dist/src/ai-hub/hub-launch-decision.js +0 -57
  27. package/dist/src/ai-hub/hub-runtime-file.js +0 -43
  28. package/dist/src/ai-hub/managed-browser.js +0 -269
  29. package/dist/src/ai-hub/manager-turns.js +0 -64
  30. package/dist/src/ai-hub/office-sideload.js +0 -156
  31. package/dist/src/ai-hub/openclaw-bridge.js +0 -250
  32. package/dist/src/ai-hub/preferences.js +0 -201
  33. package/dist/src/ai-hub/remote-hub-gateway.js +0 -88
  34. package/dist/src/ai-hub/server.js +0 -3827
  35. package/dist/src/ai-hub/types.js +0 -2
  36. package/dist/src/ai-hub/word-sideload.js +0 -95
  37. package/dist/src/cli/commands/hub.js +0 -238
  38. package/dist/src/cli/commands/test-mcp.js +0 -171
  39. package/dist/src/cli/setup/first-run.js +0 -242
  40. package/dist/src/core/config-writer.js +0 -75
  41. package/dist/src/core/utils/job-aliases.js +0 -47
  42. package/dist/src/core/utils/workflow-parser.js +0 -174
  43. package/dist/src/services/email-service-clean.js +0 -782
  44. package/extensions/office-word/favicon.ico +0 -0
  45. package/extensions/office-word/icon-64.png +0 -0
  46. package/extensions/office-word/manifest.xml +0 -33
  47. package/extensions/office-word/taskpane.html +0 -242
  48. package/public/ai-hub/index.html +0 -894
  49. package/public/ai-hub/powerpoint-taskpane/icon-64.png +0 -0
  50. package/public/ai-hub/powerpoint-taskpane/index.html +0 -236
  51. package/public/ai-hub/powerpoint-taskpane/manifest.xml +0 -30
  52. package/public/ai-hub/review.css +0 -381
  53. package/public/ai-hub/script.js +0 -11015
  54. package/public/ai-hub/styles.css +0 -4873
  55. package/public/portfolio/ashley.html +0 -523
  56. package/public/portfolio/auditya.html +0 -83
  57. package/public/portfolio/banke.html +0 -83
  58. package/public/portfolio/beza.html +0 -659
  59. package/public/portfolio/careena.html +0 -632
  60. package/public/portfolio/casey.html +0 -568
  61. package/public/portfolio/celia.html +0 -490
  62. package/public/portfolio/deidre.html +0 -642
  63. package/public/portfolio/gautam.html +0 -597
  64. package/public/portfolio/hari.html +0 -469
  65. package/public/portfolio/huxley.html +0 -1354
  66. package/public/portfolio/index.html +0 -741
  67. package/public/portfolio/maestro.html +0 -518
  68. package/public/portfolio/mandy.html +0 -590
  69. package/public/portfolio/mona.html +0 -597
  70. package/public/portfolio/pam.html +0 -887
  71. package/public/portfolio/procella.html +0 -107
  72. package/public/portfolio/qasm.html +0 -569
  73. package/public/portfolio/ricardo.html +0 -489
  74. package/public/portfolio/sade.html +0 -560
  75. package/public/portfolio/sam.html +0 -654
  76. package/public/portfolio/sechar.html +0 -580
  77. package/public/portfolio/sreya.html +0 -599
  78. package/public/portfolio/swen.html +0 -601
  79. /package/dist/src/{ai-hub → core}/brand-store.js +0 -0
@@ -1,236 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="UTF-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>FRAIM Hub</title>
7
- <script src="https://appsforoffice.microsoft.com/lib/1/hosted/office.js" type="text/javascript"></script>
8
- <script src="config.js" type="text/javascript"></script>
9
- <style>
10
- * { margin: 0; padding: 0; box-sizing: border-box; }
11
- html, body { height: 100%; overflow: hidden; }
12
- iframe { width: 100%; height: 100vh; border: none; display: block; }
13
- </style>
14
- </head>
15
- <body>
16
- <iframe id="hub" src="" allow="clipboard-read; clipboard-write"></iframe>
17
- <script>
18
- // PowerPoint task pane — mirrors extensions/office-word/taskpane.html so PPT
19
- // gets the identical full Hub experience. It mounts the Hub /ai-hub/ surface in
20
- // an iframe and bridges host context over postMessage using the same
21
- // word-context / word-request / word-response contract the Hub already speaks
22
- // (the names are the generic host-bridge protocol, not Word-specific).
23
- //
24
- // In Office Online the pane is served over HTTPS; loading an HTTP iframe from an
25
- // HTTPS page is mixed-content-blocked, so use the pane's own origin there. On
26
- // desktop (HTTP) use the direct Hub address.
27
- var HUB_ORIGIN = window.location.protocol === 'https:'
28
- ? window.location.origin
29
- : (window.FRAIM_HUB_ORIGIN || 'http://127.0.0.1:43091');
30
- var hubFrame = document.getElementById('hub');
31
- var pendingPush = null; // context queued before hub-ready fires
32
- var hubReady = false;
33
- var selectionHandlerAdded = false;
34
-
35
- // ── postMessage bridge (inbound from Hub) ─────────────────────────────────
36
- window.addEventListener('message', function(event) {
37
- if (event.origin !== HUB_ORIGIN) return;
38
- var msg = event.data || {};
39
- if (msg.type === 'hub-ready') {
40
- hubReady = true;
41
- if (pendingPush) { pushToHub(pendingPush); pendingPush = null; }
42
- } else if (msg.type === 'word-request') {
43
- handleHostRequest(msg.action, msg.requestId, msg.payload || {});
44
- }
45
- });
46
-
47
- function pushToHub(msg) {
48
- if (hubFrame && hubFrame.contentWindow) {
49
- hubFrame.contentWindow.postMessage(msg, HUB_ORIGIN);
50
- }
51
- }
52
-
53
- // ── Reading PowerPoint context ──────────────────────────────────────────────
54
- function docMeta() {
55
- var meta = { docUrl: '', docTitle: '' };
56
- try {
57
- var doc = window.Office && Office.context && Office.context.document;
58
- meta.docUrl = (doc && doc.url) ? doc.url : '';
59
- if (meta.docUrl) {
60
- var parts = meta.docUrl.replace(/\\/g, '/').split('/');
61
- meta.docTitle = (parts[parts.length - 1] || '').replace(/\.[^.]+$/, '');
62
- }
63
- } catch(e) {}
64
- return meta;
65
- }
66
-
67
- function readSelectionAsync(cb) {
68
- try {
69
- Office.context.document.getSelectedDataAsync(
70
- Office.CoercionType.Text,
71
- function(r) { cb(r.status === Office.AsyncResultStatus.Succeeded ? String(r.value || '').trim() : ''); }
72
- );
73
- } catch(e) { cb(''); }
74
- }
75
-
76
- // Read the current slide's text + speaker notes via the PowerPoint API.
77
- function readSlideAsync(cb) {
78
- try {
79
- if (typeof PowerPoint !== 'undefined' && PowerPoint.run) {
80
- PowerPoint.run(function(ctx) {
81
- var slides = ctx.presentation.getSelectedSlides();
82
- slides.load('items');
83
- return ctx.sync().then(function() {
84
- var slide = slides.items && slides.items[0];
85
- if (!slide) { cb({ slideText: '', notes: '' }); return; }
86
- var shapes = slide.shapes;
87
- shapes.load('items/textFrame/textRange/text');
88
- var ns = slide.notesSlide;
89
- try { ns.load('notesTextFrame/textRange/text'); } catch(e) {}
90
- return ctx.sync().then(function() {
91
- var notes = '';
92
- try { notes = ns.notesTextFrame.textRange.text || ''; } catch(e) {}
93
- var slideText = (shapes.items || [])
94
- .map(function(s){ try { return s.textFrame.textRange.text || ''; } catch(e){ return ''; } })
95
- .filter(Boolean).join('\n');
96
- cb({ slideText: slideText, notes: notes });
97
- });
98
- });
99
- }).catch(function() { cb({ slideText: '', notes: '' }); });
100
- } else { cb({ slideText: '', notes: '' }); }
101
- } catch(e) { cb({ slideText: '', notes: '' }); }
102
- }
103
-
104
- function slideCountAsync(cb) {
105
- try {
106
- if (typeof PowerPoint !== 'undefined' && PowerPoint.run) {
107
- PowerPoint.run(function(ctx) {
108
- var slides = ctx.presentation.slides;
109
- slides.load('items');
110
- return ctx.sync().then(function() { cb((slides.items || []).length); });
111
- }).catch(function() { cb(0); });
112
- } else { cb(0); }
113
- } catch(e) { cb(0); }
114
- }
115
-
116
- function readFullContext(cb) {
117
- var meta = docMeta();
118
- readSelectionAsync(function(selection) {
119
- readSlideAsync(function(slide) {
120
- slideCountAsync(function(count) {
121
- var body = [slide.slideText, slide.notes ? ('Notes: ' + slide.notes) : ''].filter(Boolean).join('\n');
122
- cb({
123
- docUrl: meta.docUrl,
124
- docTitle: meta.docTitle,
125
- selection: selection,
126
- hasSelection: selection.length > 0,
127
- bodyPreview: body.slice(0, 2000),
128
- wordCount: body ? body.split(/\s+/).filter(Boolean).length : 0,
129
- slideCount: count,
130
- comments: [],
131
- });
132
- });
133
- });
134
- });
135
- }
136
-
137
- // ── Selection-change handler ───────────────────────────────────────────────
138
- function addSelectionHandler() {
139
- if (selectionHandlerAdded) return;
140
- try {
141
- Office.context.document.addHandlerAsync(
142
- Office.EventType.DocumentSelectionChanged,
143
- function() {
144
- readSelectionAsync(function(sel) {
145
- var m = docMeta();
146
- pushToHub({ type: 'word-context-update', payload: {
147
- docUrl: m.docUrl, docTitle: m.docTitle,
148
- selection: sel, hasSelection: sel.length > 0,
149
- }});
150
- });
151
- },
152
- function(r) { if (r.status === Office.AsyncResultStatus.Succeeded) selectionHandlerAdded = true; }
153
- );
154
- } catch(e) {}
155
- }
156
-
157
- // ── Handling requests from Hub ─────────────────────────────────────────────
158
- function handleHostRequest(action, requestId, payload) {
159
- function respond(result) {
160
- pushToHub({ type: 'word-response', requestId: requestId, payload: result });
161
- }
162
- if (action === 'get-context') {
163
- readFullContext(function(ctx) { respond(ctx); });
164
- } else if (action === 'get-selection') {
165
- readSelectionAsync(function(sel) { respond({ selection: sel, hasSelection: sel.length > 0 }); });
166
- } else if (action === 'insert-text') {
167
- // Insert a new text box on the current slide with the given text.
168
- try {
169
- if (typeof PowerPoint !== 'undefined' && PowerPoint.run) {
170
- PowerPoint.run(function(ctx) {
171
- var slides = ctx.presentation.getSelectedSlides();
172
- slides.load('items');
173
- return ctx.sync().then(function() {
174
- var slide = slides.items && slides.items[0];
175
- if (!slide) throw new Error('No slide selected');
176
- var box = slide.shapes.addTextBox(payload.text || '');
177
- box.left = 50; box.top = 50; box.width = 600; box.height = 100;
178
- return ctx.sync();
179
- });
180
- }).then(function() { respond({ ok: true }); }).catch(function(e) { respond({ ok: false, error: e.message }); });
181
- } else { respond({ ok: false, error: 'PowerPoint API 1.1+ not available' }); }
182
- } catch(e) { respond({ ok: false, error: e.message }); }
183
- } else if (action === 'insert-after' || action === 'append-to-doc') {
184
- // Append text into the current slide's speaker notes.
185
- try {
186
- if (typeof PowerPoint !== 'undefined' && PowerPoint.run) {
187
- PowerPoint.run(function(ctx) {
188
- var slides = ctx.presentation.getSelectedSlides();
189
- slides.load('items');
190
- return ctx.sync().then(function() {
191
- var slide = slides.items && slides.items[0];
192
- if (!slide) throw new Error('No slide selected');
193
- var ns = slide.notesSlide;
194
- ns.load('notesTextFrame/textRange/text');
195
- return ctx.sync().then(function() {
196
- var existing = '';
197
- try { existing = ns.notesTextFrame.textRange.text || ''; } catch(e) {}
198
- ns.notesTextFrame.textRange.text = (existing ? existing + '\n' : '') + (payload.text || '');
199
- return ctx.sync();
200
- });
201
- });
202
- }).then(function() { respond({ ok: true }); }).catch(function(e) { respond({ ok: false, error: e.message }); });
203
- } else { respond({ ok: false, error: 'PowerPoint API not available' }); }
204
- } catch(e) { respond({ ok: false, error: e.message }); }
205
- } else {
206
- respond({ ok: false, error: 'Unknown action: ' + action });
207
- }
208
- }
209
-
210
- // ── Mount ─────────────────────────────────────────────────────────────────
211
- function mountHub() {
212
- var meta = docMeta();
213
- var params = new URLSearchParams({ surface: 'task-pane' });
214
- if (meta.docUrl) params.set('docUrl', meta.docUrl);
215
- if (meta.docTitle) params.set('docTitle', meta.docTitle);
216
- hubFrame.src = HUB_ORIGIN + '/ai-hub/?' + params.toString();
217
-
218
- hubFrame.addEventListener('load', function() {
219
- setTimeout(function() {
220
- readFullContext(function(ctx) {
221
- var msg = { type: 'word-context', payload: ctx };
222
- if (hubReady) { pushToHub(msg); } else { pendingPush = msg; }
223
- });
224
- }, 300);
225
- addSelectionHandler();
226
- });
227
- }
228
-
229
- if (typeof Office !== 'undefined') {
230
- Office.onReady(function() { mountHub(); });
231
- } else {
232
- window.addEventListener('DOMContentLoaded', mountHub);
233
- }
234
- </script>
235
- </body>
236
- </html>
@@ -1,30 +0,0 @@
1
- <?xml version="1.0" encoding="UTF-8"?>
2
- <OfficeApp xmlns="http://schemas.microsoft.com/office/appforoffice/1.1"
3
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4
- xmlns:bt="http://schemas.microsoft.com/office/officeappbasictypes/1.0"
5
- xsi:type="TaskPaneApp">
6
- <Id>e7a3c812-91fd-4b2e-8c15-d4f6a903b71e</Id>
7
- <Version>1.0.0.0</Version>
8
- <ProviderName>FRAIM</ProviderName>
9
- <DefaultLocale>en-US</DefaultLocale>
10
- <DisplayName DefaultValue="FRAIM"/>
11
- <Description DefaultValue="FRAIM AI Hub in PowerPoint - analyze, draft, and improve presentations."/>
12
- <IconUrl DefaultValue="http://127.0.0.1:43091/powerpoint-taskpane/icon-64.png"/>
13
- <HighResolutionIconUrl DefaultValue="http://127.0.0.1:43091/powerpoint-taskpane/icon-64.png"/>
14
- <SupportUrl DefaultValue="http://127.0.0.1:43091"/>
15
- <AppDomains>
16
- <AppDomain>https://appsforoffice.microsoft.com</AppDomain>
17
- </AppDomains>
18
- <Hosts>
19
- <Host Name="Presentation"/>
20
- </Hosts>
21
- <Requirements>
22
- <Sets>
23
- <Set Name="PowerPointApi" MinVersion="1.1"/>
24
- </Sets>
25
- </Requirements>
26
- <DefaultSettings>
27
- <SourceLocation DefaultValue="http://127.0.0.1:43091/powerpoint-taskpane/"/>
28
- </DefaultSettings>
29
- <Permissions>ReadWriteDocument</Permissions>
30
- </OfficeApp>
@@ -1,381 +0,0 @@
1
- /* ===========================================================================
2
- Issue #512 S6 — Review experience (R7), Learnings over sleep-on-learnings
3
- (R9), and Reverse mentoring (R10). Additive stylesheet linked after
4
- styles.css so it can build on the existing Hub token system without
5
- touching the shipped rules. Tokens: --accent, --accent-strong,
6
- --accent-soft, --muted, --line, --surface, --text, --done, --warn,
7
- --danger.
8
- =========================================================================== */
9
-
10
- /* ── R7: completion card + format-derived artifact strip (in the thread) ── */
11
- .review-completion {
12
- margin: 2px 18px 10px;
13
- display: flex;
14
- flex-direction: column;
15
- gap: 8px;
16
- }
17
- .review-card {
18
- /* #700: token-driven so the card follows light/dark. Was a hardcoded
19
- near-white that made var(--text) unreadable in dark mode. */
20
- background: var(--surface);
21
- border: 1px solid var(--line);
22
- border-radius: 10px;
23
- padding: 10px 14px;
24
- display: flex;
25
- flex-direction: column;
26
- gap: 6px;
27
- box-shadow: 0 4px 12px rgba(35, 30, 23, 0.05);
28
- }
29
- .review-card .rc-heading {
30
- font-size: 15px;
31
- font-weight: 700;
32
- color: var(--text);
33
- }
34
- .review-card .rc-row {
35
- display: grid;
36
- grid-template-columns: 110px minmax(0, 1fr);
37
- gap: 10px;
38
- align-items: baseline;
39
- }
40
- .review-card .rc-k {
41
- font-size: 11px;
42
- font-weight: 700;
43
- text-transform: uppercase;
44
- letter-spacing: 0.06em;
45
- color: var(--muted);
46
- }
47
- .review-card .rc-v {
48
- font-size: 13px;
49
- color: var(--text);
50
- line-height: 1.35;
51
- }
52
- .rc-artifact-strip {
53
- display: flex;
54
- flex-wrap: wrap;
55
- gap: 8px;
56
- }
57
- .rc-art-btn {
58
- background: var(--accent);
59
- color: #fff;
60
- border: none;
61
- border-radius: 8px;
62
- padding: 7px 12px;
63
- font-size: 13px;
64
- font-weight: 600;
65
- }
66
- .rc-art-btn:hover { background: var(--accent-strong); }
67
- .rc-art-btn.ghost {
68
- background: transparent;
69
- color: var(--text);
70
- border: 1px solid var(--line);
71
- font-weight: 500;
72
- }
73
- .rc-art-btn.ghost:hover { background: var(--soft); }
74
-
75
- /* ── Issue #566: success-confirmed growth offer + teach action ──────────────
76
- Reuses the .review-card surface with the personalization "teach" accent
77
- (defined in styles.css :root) so the offer reads as the employee proposing
78
- to grow, distinct from a plain review card. */
79
- .review-card--offer .rc-heading { color: var(--teach); }
80
- .review-card--growth .rc-heading { color: var(--teach); }
81
- .rc-art-btn--teach {
82
- background: var(--teach-soft);
83
- color: var(--teach);
84
- border: 1px solid var(--teach-line);
85
- }
86
- .rc-art-btn--teach:hover { background: #ece3ff; }
87
-
88
- /* ── R7.7: unified action bar — review actions sit beside the always-on
89
- coaching chips inside the coach panel. The chips/input never disappear. ── */
90
- .review-actions {
91
- display: flex;
92
- flex-wrap: wrap;
93
- align-items: center;
94
- gap: 8px;
95
- margin-bottom: 8px;
96
- padding-bottom: 8px;
97
- border-bottom: 1px dashed var(--line);
98
- }
99
- .review-actions .rb-approve {
100
- background: var(--done, #2e7d32);
101
- color: #fff;
102
- border: none;
103
- border-radius: 8px;
104
- padding: 7px 14px;
105
- font-size: 13px;
106
- font-weight: 700;
107
- }
108
- .review-actions .rb-approve:hover { filter: brightness(0.94); }
109
- .review-actions .rb-approve:disabled,
110
- .review-actions .rb-approve.managed-disabled {
111
- background: var(--soft, #e8e8e8);
112
- color: var(--muted, #888);
113
- filter: none;
114
- }
115
- .review-actions .rb-secondary {
116
- background: var(--warn, #f57c00);
117
- border: none;
118
- border-radius: 8px;
119
- padding: 7px 12px;
120
- font-size: 13px;
121
- font-weight: 600;
122
- color: #fff;
123
- }
124
- .review-actions .rb-secondary:hover { filter: brightness(0.94); }
125
- .review-actions .rb-secondary:disabled,
126
- .review-actions .rb-secondary.managed-disabled {
127
- background: var(--soft, #e8e8e8);
128
- color: var(--muted, #888);
129
- filter: none;
130
- }
131
- .review-actions .rb-handoff {
132
- font-size: 12px;
133
- color: var(--muted);
134
- flex: 1 1 220px;
135
- min-width: 0;
136
- }
137
-
138
- /* ── R9: Learnings surface (Share learnings + Bless/Skip proposals) ── */
139
- .learn-head-row {
140
- display: flex;
141
- align-items: flex-start;
142
- justify-content: space-between;
143
- gap: 12px;
144
- margin-bottom: 12px;
145
- }
146
- .learn-head-row .lh-title {
147
- font-size: 16px;
148
- font-weight: 700;
149
- color: var(--text);
150
- }
151
- .learn-head-row .lh-sub {
152
- font-size: 13px;
153
- color: var(--muted);
154
- margin-top: 2px;
155
- }
156
- .lh-run {
157
- flex-shrink: 0;
158
- background: var(--accent);
159
- color: #fff;
160
- border: none;
161
- border-radius: 12px;
162
- padding: 9px 16px;
163
- font-size: 13px;
164
- font-weight: 600;
165
- box-shadow: var(--shadow);
166
- }
167
- .lh-run:hover { background: var(--accent-strong); }
168
-
169
- .learn-list {
170
- display: flex;
171
- flex-direction: column;
172
- gap: 12px;
173
- }
174
- .learn-prop {
175
- background: var(--surface);
176
- border: 1px solid var(--line);
177
- border-radius: 16px;
178
- padding: 14px 16px;
179
- display: flex;
180
- flex-direction: column;
181
- gap: 8px;
182
- transition: opacity 160ms ease;
183
- }
184
- .learn-prop.resolved { opacity: 0.45; }
185
- .learn-prop .lp-head {
186
- display: flex;
187
- align-items: center;
188
- gap: 10px;
189
- }
190
- .learn-prop .lp-scope {
191
- font-size: 10px;
192
- font-weight: 700;
193
- padding: 2px 8px;
194
- border-radius: 6px;
195
- text-transform: uppercase;
196
- letter-spacing: 0.04em;
197
- flex-shrink: 0;
198
- }
199
- .learn-prop .lp-scope.scope-org { background: var(--accent-soft); color: var(--accent-strong); }
200
- .learn-prop .lp-scope.scope-mgr { background: #ede5ff; color: #6b4c92; }
201
- .learn-prop .lp-scope.scope-proj { background: #e3f0e8; color: #2e7d4f; }
202
- .learn-prop .lp-title {
203
- font-size: 14px;
204
- font-weight: 600;
205
- color: var(--text);
206
- }
207
- .learn-prop .lp-body {
208
- font-size: 13px;
209
- color: var(--text);
210
- line-height: 1.5;
211
- }
212
- .learn-prop .lp-example {
213
- font-size: 13px;
214
- color: var(--muted);
215
- font-style: italic;
216
- border-left: 3px solid var(--line);
217
- padding-left: 10px;
218
- }
219
- .learn-prop .lp-actions {
220
- display: flex;
221
- align-items: center;
222
- gap: 8px;
223
- flex-wrap: wrap;
224
- margin-top: 2px;
225
- }
226
-
227
- /* #533 #3: per-card edit/delete + per-section add + inline editor. */
228
- .learn-prop .lp-head .lp-title { flex: 1; min-width: 0; }
229
- .learn-prop .lp-act {
230
- width: 24px; height: 24px; border-radius: 6px; border: 1px solid var(--line);
231
- background: var(--surface); color: var(--muted); font-size: 12px; line-height: 1;
232
- display: none; align-items: center; justify-content: center; cursor: pointer; flex-shrink: 0;
233
- }
234
- .learn-prop:hover .lp-act, .learn-prop:focus-within .lp-act { display: inline-flex; }
235
- .learn-prop .lp-act:hover { color: var(--accent); border-color: var(--accent); }
236
- .learn-prop .lp-act-del:hover { color: var(--danger, #d2261f); border-color: var(--danger, #d2261f); }
237
- .learn-prop .lp-edit { display: flex; flex-direction: column; gap: 8px; }
238
- .learn-prop .lp-edit-row { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
239
- .learn-prop .lp-edit-cat, .learn-prop .lp-edit-prio {
240
- font: inherit; font-size: 12px; padding: 6px 8px; border: 1px solid var(--line);
241
- border-radius: 8px; background: var(--surface); color: var(--text);
242
- }
243
- .learn-prop .lp-edit-title {
244
- flex: 1; min-width: 140px; font: inherit; font-size: 13px; padding: 7px 10px;
245
- border: 1px solid var(--line); border-radius: 8px; background: var(--surface); color: var(--text);
246
- }
247
- .learn-prop .lp-edit-body {
248
- width: 100%; min-height: 90px; box-sizing: border-box; resize: vertical; font: inherit;
249
- font-size: 13px; line-height: 1.5; padding: 8px 10px; border: 1px solid var(--line);
250
- border-radius: 8px; background: var(--surface); color: var(--text);
251
- }
252
- .learn-prop .lp-edit-title:focus, .learn-prop .lp-edit-body:focus { outline: none; border-color: var(--accent); }
253
- .learn-prop .lp-edit-bar { display: flex; align-items: center; gap: 10px; }
254
- .learn-prop .lp-confirm { font-size: 12px; color: var(--text); margin-top: 8px; display: flex; flex-direction: column; gap: 8px; }
255
- .lp-add-btn {
256
- align-self: flex-start; margin-top: 10px; font-size: 12px; font-weight: 600; color: var(--accent);
257
- background: var(--surface); border: 1px solid var(--line); border-radius: 9px; padding: 7px 14px; cursor: pointer;
258
- }
259
- .lp-add-btn:hover { background: var(--accent-soft); border-color: var(--accent); }
260
- .learn-prop .lp-bless {
261
- background: var(--done, #2e7d32);
262
- color: #fff;
263
- border: none;
264
- border-radius: 9px;
265
- padding: 6px 14px;
266
- font-size: 12px;
267
- font-weight: 700;
268
- }
269
- .learn-prop .lp-skip {
270
- background: transparent;
271
- border: 1px solid var(--line);
272
- color: var(--text);
273
- border-radius: 9px;
274
- padding: 6px 14px;
275
- font-size: 12px;
276
- font-weight: 600;
277
- }
278
- .learn-prop .lp-bless:disabled,
279
- .learn-prop .lp-skip:disabled { cursor: default; opacity: 0.7; }
280
- .learn-prop .lp-scope-note {
281
- font-size: 11px;
282
- color: var(--muted);
283
- }
284
-
285
- /* #521 R7: promote-to-scope buttons on project candidates */
286
- .learn-prop .lp-promote-label { font-size: 11px; font-weight: 600; color: var(--muted); }
287
- .learn-prop .lp-promote {
288
- border: 1px solid var(--line);
289
- background: var(--surface);
290
- color: var(--text);
291
- border-radius: 9px;
292
- padding: 6px 12px;
293
- font-size: 12px;
294
- font-weight: 600;
295
- }
296
- .learn-prop .lp-promote.scope-proj { border-color: rgba(46,125,50,.5); }
297
- .learn-prop .lp-promote.scope-mgr { border-color: rgba(139,95,191,.5); }
298
- .learn-prop .lp-promote.scope-org { border-color: rgba(0,113,227,.5); }
299
- .learn-prop .lp-promote.done { background: var(--done, #2e7d32); color: #fff; border-color: transparent; }
300
- .learn-prop .lp-promote:disabled { cursor: default; opacity: 0.7; }
301
-
302
- /* ── R10: Reverse mentoring items (Got it / Dismiss) ── */
303
- .rev-item {
304
- background: var(--surface);
305
- border: 1px solid var(--line);
306
- border-radius: 16px;
307
- padding: 14px 16px;
308
- display: flex;
309
- flex-direction: column;
310
- gap: 8px;
311
- transition: opacity 160ms ease;
312
- }
313
- .rev-item .rev-from {
314
- display: flex;
315
- align-items: center;
316
- gap: 8px;
317
- font-size: 13px;
318
- font-weight: 600;
319
- color: var(--text);
320
- }
321
- .rev-item .rev-av {
322
- width: 24px;
323
- height: 24px;
324
- border-radius: 8px;
325
- background: var(--accent-soft);
326
- color: var(--accent-strong);
327
- display: inline-flex;
328
- align-items: center;
329
- justify-content: center;
330
- font-size: 10px;
331
- font-weight: 700;
332
- }
333
- .rev-item .rev-msg {
334
- font-size: 14px;
335
- color: var(--text);
336
- line-height: 1.5;
337
- }
338
- .rev-item .rev-actions {
339
- display: flex;
340
- gap: 8px;
341
- }
342
- .rev-item .rev-gotit {
343
- background: var(--accent);
344
- color: #fff;
345
- border: none;
346
- border-radius: 9px;
347
- padding: 6px 14px;
348
- font-size: 12px;
349
- font-weight: 600;
350
- }
351
- .rev-item .rev-gotit:hover { background: var(--accent-strong); }
352
- .rev-item .rev-dismiss {
353
- background: transparent;
354
- border: 1px solid var(--line);
355
- color: var(--text);
356
- border-radius: 9px;
357
- padding: 6px 14px;
358
- font-size: 12px;
359
- font-weight: 500;
360
- }
361
- .rev-item .rev-dismiss:hover { background: var(--soft); }
362
-
363
- /* ── R9: project-scoped learnings panel (over the workspace conversation) ── */
364
- .proj-learnings-panel {
365
- position: absolute;
366
- inset: 0;
367
- background: var(--bg);
368
- overflow-y: auto;
369
- z-index: 5;
370
- }
371
- .proj-learnings-panel .pl-back {
372
- background: transparent;
373
- border: 1px solid var(--line);
374
- border-radius: 10px;
375
- padding: 7px 14px;
376
- font-size: 13px;
377
- font-weight: 600;
378
- color: var(--text);
379
- }
380
- .proj-learnings-panel .pl-back:hover { background: var(--soft); }
381
- .workspace-conv { position: relative; }