fraim-framework 2.0.168 → 2.0.170

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.
@@ -1,236 +1,236 @@
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
+ <!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 +1,30 @@
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>
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
30
  </OfficeApp>
@@ -3976,11 +3976,14 @@ async function startRun(job, instructions, employeeId, preassignedConvId) {
3976
3976
  // Issue #489: capture selection state at job-start so write-back knows insert-after vs append.
3977
3977
  wordStartedWithSelection: document.body.dataset.surface === 'task-pane' && !!(state.wordContext && state.wordContext.hasSelection),
3978
3978
  };
3979
- upsertConversation(conv);
3980
- state.activeId = conv.id;
3981
- persistConversations();
3982
- renderRail();
3983
- renderActive();
3979
+ upsertConversation(conv);
3980
+ state.activeId = conv.id;
3981
+ // Issue #539: make Cmd/Ctrl+Shift+R available as soon as the run is queued,
3982
+ // not only after the server responds with the run id.
3983
+ state.lastRun = { job, instructions, employeeId };
3984
+ persistConversations();
3985
+ renderRail();
3986
+ renderActive();
3984
3987
 
3985
3988
  try {
3986
3989
  const run = await requestJson('/api/ai-hub/runs', {
@@ -4008,9 +4011,8 @@ async function startRun(job, instructions, employeeId, preassignedConvId) {
4008
4011
  renderRail();
4009
4012
  renderActive();
4010
4013
  startPolling();
4011
- // Issue #539: record last run for Cmd+Shift+R re-run and update in-memory
4012
- // preferences so the next palette open shows the correct recent section.
4013
- state.lastRun = { job, instructions, employeeId };
4014
+ // Issue #539: update in-memory preferences so the next palette open shows
4015
+ // the correct recent section.
4014
4016
  if (state.bootstrap && state.bootstrap.preferences) {
4015
4017
  const prefs = state.bootstrap.preferences;
4016
4018
  const nextIds = [job.id, ...(prefs.recentJobIds || []).filter((id) => id !== job.id)].slice(0, 8);
@@ -518,6 +518,6 @@ function selectSlot(el, num) {
518
518
  document.getElementById('inviteSent').textContent = '✓ Invite sent';
519
519
  }
520
520
  </script>
521
-
521
+
522
522
  </body>
523
523
  </html>
@@ -563,6 +563,6 @@ function toggleCard(num) {
563
563
  if (!isOpen) { card.classList.add('open'); card.scrollIntoView({ behavior: 'smooth', block: 'nearest' }); }
564
564
  }
565
565
  </script>
566
-
566
+
567
567
  </body>
568
568
  </html>
@@ -485,6 +485,6 @@ function toggleCard(num) {
485
485
  if (!isOpen) { card.classList.add('open'); card.scrollIntoView({ behavior: 'smooth', block: 'nearest' }); }
486
486
  }
487
487
  </script>
488
-
488
+
489
489
  </body>
490
490
  </html>
@@ -592,6 +592,6 @@ if (document.readyState !== 'loading') {
592
592
  renderSlide(0);
593
593
  }
594
594
  </script>
595
-
595
+
596
596
  </body>
597
597
  </html>
@@ -464,6 +464,6 @@ function toggleCard(num) {
464
464
  if (!isOpen) { card.classList.add('open'); card.scrollIntoView({ behavior: 'smooth', block: 'nearest' }); }
465
465
  }
466
466
  </script>
467
-
467
+
468
468
  </body>
469
469
  </html>
@@ -513,6 +513,6 @@ function toggleCard(num) {
513
513
  if (!isOpen) { card.classList.add('open'); card.scrollIntoView({ behavior: 'smooth', block: 'nearest' }); }
514
514
  }
515
515
  </script>
516
-
516
+
517
517
  </body>
518
518
  </html>
@@ -585,6 +585,6 @@ function toggleCard(num) {
585
585
  if (!isOpen) { card.classList.add('open'); card.scrollIntoView({ behavior: 'smooth', block: 'nearest' }); }
586
586
  }
587
587
  </script>
588
-
588
+
589
589
  </body>
590
590
  </html>