fraim-hub 2.0.260 → 2.0.262
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.
|
@@ -7,6 +7,7 @@ exports.packsDir = packsDir;
|
|
|
7
7
|
exports.defaultCloneDir = defaultCloneDir;
|
|
8
8
|
exports.resolvePackHome = resolvePackHome;
|
|
9
9
|
exports.packReadRoots = packReadRoots;
|
|
10
|
+
exports.migrateStrandedContent = migrateStrandedContent;
|
|
10
11
|
exports.findStrandedLegacyContent = findStrandedLegacyContent;
|
|
11
12
|
exports.gitUrlHasUserinfo = gitUrlHasUserinfo;
|
|
12
13
|
exports.ensurePackClone = ensurePackClone;
|
|
@@ -172,6 +173,78 @@ function resolvePackHome(layer) {
|
|
|
172
173
|
function packReadRoots(layer) {
|
|
173
174
|
return [resolvePackHome(layer).contentRoot];
|
|
174
175
|
}
|
|
176
|
+
/**
|
|
177
|
+
* Auto-migrate content stranded at the legacy standard path to the configured
|
|
178
|
+
* contentRoot. Runs at most once per process per (layer + contentRoot) pair.
|
|
179
|
+
* Never throws: migration failures are silently skipped.
|
|
180
|
+
*
|
|
181
|
+
* Applies when a synced backend (local-folder, git, fraim-cloud) is configured
|
|
182
|
+
* and the legacy standard path differs from contentRoot. Each file is moved
|
|
183
|
+
* with renameSync; on a cross-device rename (e.g. ~/.fraim → OneDrive) it
|
|
184
|
+
* falls back to copyFileSync + unlinkSync. Files already present in contentRoot
|
|
185
|
+
* are never overwritten — contentRoot is always authoritative.
|
|
186
|
+
*/
|
|
187
|
+
const _migratedStrandedLayers = new Set();
|
|
188
|
+
function migrateStrandedContent(layer) {
|
|
189
|
+
const home = resolvePackHome(layer);
|
|
190
|
+
const key = `${layer}:${home.contentRoot}`;
|
|
191
|
+
if (_migratedStrandedLayers.has(key))
|
|
192
|
+
return;
|
|
193
|
+
_migratedStrandedLayers.add(key);
|
|
194
|
+
const legacyContentRoot = home.legacyRoot ? path_1.default.join(home.legacyRoot, 'personalized-employee') : null;
|
|
195
|
+
if (!legacyContentRoot || legacyContentRoot === home.contentRoot)
|
|
196
|
+
return;
|
|
197
|
+
if (!fs_1.default.existsSync(legacyContentRoot))
|
|
198
|
+
return;
|
|
199
|
+
try {
|
|
200
|
+
const walk = (absDir, relDir) => {
|
|
201
|
+
let entries;
|
|
202
|
+
try {
|
|
203
|
+
entries = fs_1.default.readdirSync(absDir, { withFileTypes: true });
|
|
204
|
+
}
|
|
205
|
+
catch {
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
for (const entry of entries) {
|
|
209
|
+
const rel = relDir ? `${relDir}/${entry.name}` : entry.name;
|
|
210
|
+
const src = path_1.default.join(absDir, entry.name);
|
|
211
|
+
const dst = path_1.default.join(home.contentRoot, rel);
|
|
212
|
+
if (entry.isDirectory()) {
|
|
213
|
+
walk(src, rel);
|
|
214
|
+
continue;
|
|
215
|
+
}
|
|
216
|
+
if (!entry.isFile())
|
|
217
|
+
continue;
|
|
218
|
+
if (fs_1.default.existsSync(dst))
|
|
219
|
+
continue;
|
|
220
|
+
try {
|
|
221
|
+
fs_1.default.mkdirSync(path_1.default.dirname(dst), { recursive: true });
|
|
222
|
+
fs_1.default.renameSync(src, dst);
|
|
223
|
+
}
|
|
224
|
+
catch {
|
|
225
|
+
try {
|
|
226
|
+
fs_1.default.copyFileSync(src, dst);
|
|
227
|
+
fs_1.default.unlinkSync(src);
|
|
228
|
+
}
|
|
229
|
+
catch { /* leave source intact */ }
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
};
|
|
233
|
+
walk(legacyContentRoot, '');
|
|
234
|
+
// Clean up empty dirs left behind (best-effort).
|
|
235
|
+
for (const dir of ORG_CONTENT_DIRS) {
|
|
236
|
+
try {
|
|
237
|
+
fs_1.default.rmdirSync(path_1.default.join(legacyContentRoot, dir));
|
|
238
|
+
}
|
|
239
|
+
catch { /* not empty — fine */ }
|
|
240
|
+
}
|
|
241
|
+
try {
|
|
242
|
+
fs_1.default.rmdirSync(legacyContentRoot);
|
|
243
|
+
}
|
|
244
|
+
catch { /* not empty — fine */ }
|
|
245
|
+
}
|
|
246
|
+
catch { /* migration is best-effort; never fail a sync over it */ }
|
|
247
|
+
}
|
|
175
248
|
/**
|
|
176
249
|
* Content sitting at the standard local path that the configured home does not
|
|
177
250
|
* hold. Since the home is the single read location, this content is stranded:
|
|
@@ -72,11 +72,18 @@ function managerSecondaryLearningsBase() {
|
|
|
72
72
|
function getLearningRoots(workspaceRoot) {
|
|
73
73
|
const managerHome = (0, pack_home_1.resolvePackHome)('manager');
|
|
74
74
|
const managerHomeBase = (0, path_1.join)(managerHome.contentRoot, 'learnings');
|
|
75
|
+
const managerCacheBase = managerSecondaryLearningsBase();
|
|
76
|
+
// Derive the display base from the actual cache path relative to ~/.fraim so
|
|
77
|
+
// the display string always matches the filesystem path (fixed by #1043r2).
|
|
78
|
+
const fraimDir = (0, project_fraim_paths_1.getUserFraimDirPath)();
|
|
79
|
+
const managerCacheDisplayBase = managerCacheBase.startsWith(fraimDir)
|
|
80
|
+
? (0, project_fraim_paths_1.getUserFraimDisplayPath)(managerCacheBase.slice(fraimDir.length).replace(/\\/g, '/').replace(/^\//, ''))
|
|
81
|
+
: managerCacheBase.replace(/\\/g, '/');
|
|
75
82
|
return {
|
|
76
83
|
globalPersonalBase: (0, project_fraim_paths_1.getConfiguredPortableLearningsDir)(workspaceRoot),
|
|
77
84
|
globalPersonalDisplayBase: (0, project_fraim_paths_1.getConfiguredPortableLearningsDisplayPath)(workspaceRoot),
|
|
78
|
-
managerCacheBase
|
|
79
|
-
managerCacheDisplayBase
|
|
85
|
+
managerCacheBase,
|
|
86
|
+
managerCacheDisplayBase,
|
|
80
87
|
managerHomeBase,
|
|
81
88
|
managerHomeDisplayBase: managerHomeBase.replace(/\\/g, '/'),
|
|
82
89
|
repoLearningsBase: (0, project_fraim_paths_1.getWorkspaceLearningsDir)(workspaceRoot)
|
|
@@ -637,11 +644,20 @@ function resolveOrgContextFile(workspaceRoot, relativePath, orgCacheEligible = t
|
|
|
637
644
|
}
|
|
638
645
|
}
|
|
639
646
|
if (!orgCacheEligible) {
|
|
640
|
-
const
|
|
647
|
+
const managerHome = (0, pack_home_1.resolvePackHome)('manager');
|
|
648
|
+
const managerCachePath = (0, path_1.join)(managerHome.contentRoot, relativePath);
|
|
641
649
|
if ((0, fs_1.existsSync)(managerCachePath)) {
|
|
650
|
+
// Derive the display path relative to ~/.fraim. For the standard
|
|
651
|
+
// single-machine backend contentRoot is ~/.fraim/personalized-employee
|
|
652
|
+
// so the display path is ~/.fraim/personalized-employee/... For a
|
|
653
|
+
// synced backend it may be elsewhere (e.g. ~/.fraim/packs/manager-repo).
|
|
654
|
+
const fraimDir = (0, project_fraim_paths_1.getUserFraimDirPath)();
|
|
655
|
+
const relToFraim = managerCachePath.startsWith(fraimDir)
|
|
656
|
+
? managerCachePath.slice(fraimDir.length).replace(/\\/g, '/').replace(/^\//, '')
|
|
657
|
+
: `personalized-employee/${relativePath}`;
|
|
642
658
|
return {
|
|
643
659
|
present: true,
|
|
644
|
-
displayPath: (0, project_fraim_paths_1.getUserFraimDisplayPath)(
|
|
660
|
+
displayPath: (0, project_fraim_paths_1.getUserFraimDisplayPath)(relToFraim)
|
|
645
661
|
};
|
|
646
662
|
}
|
|
647
663
|
}
|
|
@@ -812,13 +828,18 @@ function resolveTeamContextFile(workspaceRoot, key) {
|
|
|
812
828
|
}
|
|
813
829
|
}
|
|
814
830
|
if (key === 'manager' || key === 'managerRules') {
|
|
815
|
-
const
|
|
831
|
+
const managerHome = (0, pack_home_1.resolvePackHome)('manager');
|
|
832
|
+
const managerCachePath = (0, path_1.join)(managerHome.contentRoot, relativePath);
|
|
816
833
|
if ((0, fs_1.existsSync)(managerCachePath)) {
|
|
834
|
+
const fraimDir = (0, project_fraim_paths_1.getUserFraimDirPath)();
|
|
835
|
+
const relToFraim = managerCachePath.startsWith(fraimDir)
|
|
836
|
+
? managerCachePath.slice(fraimDir.length).replace(/\\/g, '/').replace(/^\//, '')
|
|
837
|
+
: `personalized-employee/${relativePath}`;
|
|
817
838
|
return {
|
|
818
839
|
present: true,
|
|
819
840
|
readPath: managerCachePath,
|
|
820
841
|
writePath: '',
|
|
821
|
-
displayPath: (0, project_fraim_paths_1.getUserFraimDisplayPath)(
|
|
842
|
+
displayPath: (0, project_fraim_paths_1.getUserFraimDisplayPath)(relToFraim),
|
|
822
843
|
scope,
|
|
823
844
|
managedByManagerSync: true
|
|
824
845
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fraim-hub",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.262",
|
|
4
4
|
"description": "FRAIM Hub local companion package.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"fraim-hub": "bin/fraim-hub.js",
|
|
@@ -163,7 +163,7 @@
|
|
|
163
163
|
"electron": "^41.2.2",
|
|
164
164
|
"electron-updater": "^6.8.9",
|
|
165
165
|
"express": "^5.2.1",
|
|
166
|
-
"fraim": "2.0.
|
|
166
|
+
"fraim": "2.0.262",
|
|
167
167
|
"mongodb": "^7.0.0",
|
|
168
168
|
"node-cron": "4.2.1",
|
|
169
169
|
"node-edge-tts": "^1.2.10",
|
package/public/ai-hub/review.css
CHANGED
|
@@ -638,3 +638,41 @@
|
|
|
638
638
|
display: flex;
|
|
639
639
|
gap: 8px;
|
|
640
640
|
}
|
|
641
|
+
/* Destination row: Company / This project toggle buttons. */
|
|
642
|
+
.lp-pp-dest-row {
|
|
643
|
+
display: flex;
|
|
644
|
+
gap: 8px;
|
|
645
|
+
}
|
|
646
|
+
.lp-pp-dest-btn {
|
|
647
|
+
flex: 1;
|
|
648
|
+
display: flex;
|
|
649
|
+
flex-direction: column;
|
|
650
|
+
align-items: flex-start;
|
|
651
|
+
gap: 2px;
|
|
652
|
+
padding: 8px 12px;
|
|
653
|
+
border-radius: 8px;
|
|
654
|
+
border: 1.5px solid var(--line);
|
|
655
|
+
background: var(--bg);
|
|
656
|
+
cursor: pointer;
|
|
657
|
+
text-align: left;
|
|
658
|
+
transition: border-color 0.12s, background 0.12s;
|
|
659
|
+
}
|
|
660
|
+
.lp-pp-dest-btn:hover { border-color: var(--accent, #0055cc); }
|
|
661
|
+
.lp-pp-dest-btn.lp-pp-dest-selected {
|
|
662
|
+
border-color: var(--accent, #0055cc);
|
|
663
|
+
background: color-mix(in srgb, var(--accent, #0055cc) 8%, var(--bg));
|
|
664
|
+
}
|
|
665
|
+
.lp-pp-dest-name {
|
|
666
|
+
font-size: 13px;
|
|
667
|
+
font-weight: 600;
|
|
668
|
+
color: var(--text);
|
|
669
|
+
}
|
|
670
|
+
.lp-pp-dest-sub {
|
|
671
|
+
font-size: 11px;
|
|
672
|
+
color: var(--muted);
|
|
673
|
+
}
|
|
674
|
+
.lp-pp-project-section {
|
|
675
|
+
display: flex;
|
|
676
|
+
flex-direction: column;
|
|
677
|
+
gap: 6px;
|
|
678
|
+
}
|
package/public/ai-hub/script.js
CHANGED
|
@@ -658,6 +658,7 @@ function taskPaneProjectEntries() {
|
|
|
658
658
|
|
|
659
659
|
function taskPaneJobEntries() {
|
|
660
660
|
const jobs = [
|
|
661
|
+
{ id: '__freeform__', title: 'Ad-hoc instructions', intent: 'Start from the instructions below.' },
|
|
661
662
|
...((state.bootstrap && state.bootstrap.jobs) || []),
|
|
662
663
|
...((state.bootstrap && state.bootstrap.managerTemplates) || []),
|
|
663
664
|
];
|
|
@@ -727,7 +728,7 @@ function ensureTaskPaneLauncher() {
|
|
|
727
728
|
input.id = id;
|
|
728
729
|
if (tag === 'textarea') {
|
|
729
730
|
input.rows = 4;
|
|
730
|
-
input.placeholder = '
|
|
731
|
+
input.placeholder = 'Tell the employee what to do with this Office context.';
|
|
731
732
|
input.addEventListener('input', () => { input.dataset.userTouched = '1'; });
|
|
732
733
|
}
|
|
733
734
|
label.appendChild(span);
|
|
@@ -783,10 +784,11 @@ function renderTaskPaneLauncher() {
|
|
|
783
784
|
const current = document.getElementById('task-pane-current-job');
|
|
784
785
|
const context = document.getElementById('task-pane-context-summary');
|
|
785
786
|
const conv = typeof activeConversation === 'function' ? activeConversation() : null;
|
|
787
|
+
const priorJobId = jobSelect && jobSelect.value;
|
|
786
788
|
|
|
787
789
|
taskPaneSetOptions(projectSelect, projects, (project) => project.folderPath, (project) => project.name || friendlyProjectShortName(project.folderPath), state.projectPath);
|
|
788
790
|
taskPaneSetOptions(employeeSelect, agents, (agent) => agent.id, (agent) => agent.label || agent.id, state.selectedEmployeeId);
|
|
789
|
-
taskPaneSetOptions(jobSelect, jobs, (job) => job.id, (job) => job.title || job.id,
|
|
791
|
+
taskPaneSetOptions(jobSelect, jobs, (job) => job.id, (job) => job.title || job.id, priorJobId || '__freeform__');
|
|
790
792
|
|
|
791
793
|
if (instructions && instructions.dataset.userTouched !== '1') {
|
|
792
794
|
instructions.value = taskPaneInstructionSeed();
|
|
@@ -835,13 +837,16 @@ async function taskPaneStartSelectedJob() {
|
|
|
835
837
|
const statusEl = document.getElementById('task-pane-start-status');
|
|
836
838
|
const selectedProject = projectSelect && projectSelect.value;
|
|
837
839
|
const requestedEmployeeId = employeeSelect && employeeSelect.value;
|
|
840
|
+
const requestedJobId = jobSelect && jobSelect.value;
|
|
838
841
|
if (selectedProject && tfCanonicalProjectPath(selectedProject) !== tfCanonicalProjectPath(state.projectPath)) {
|
|
839
842
|
await taskPaneSwitchProject(selectedProject);
|
|
840
843
|
const updatedEmployeeSelect = document.getElementById('task-pane-employee-select');
|
|
841
844
|
if (updatedEmployeeSelect && requestedEmployeeId) updatedEmployeeSelect.value = requestedEmployeeId;
|
|
845
|
+
const updatedJobSelect = document.getElementById('task-pane-job-select');
|
|
846
|
+
if (updatedJobSelect && requestedJobId) updatedJobSelect.value = requestedJobId;
|
|
842
847
|
}
|
|
843
848
|
const jobs = taskPaneJobEntries();
|
|
844
|
-
const job = jobs.find((entry) => entry.id ===
|
|
849
|
+
const job = jobs.find((entry) => entry.id === requestedJobId);
|
|
845
850
|
const employeeId = requestedEmployeeId || (employeeSelect && employeeSelect.value);
|
|
846
851
|
if (!job || !employeeId) return;
|
|
847
852
|
if (start) start.disabled = true;
|
|
@@ -10889,7 +10894,13 @@ function tfRenderProjectContextTop() {
|
|
|
10889
10894
|
{ ico: '🧠', label: 'Sleep on learnings', onClick: () => tfRunShareLearnings('project') },
|
|
10890
10895
|
{ ico: '↑', label: 'Share with others', onClick: () => {
|
|
10891
10896
|
const h = document.getElementById('project-learnings');
|
|
10892
|
-
if (h)
|
|
10897
|
+
if (!h) return;
|
|
10898
|
+
const entering = h.dataset.lpShareMode !== 'true';
|
|
10899
|
+
h.dataset.lpShareMode = entering ? 'true' : 'false';
|
|
10900
|
+
const acc = document.getElementById('proj-learnings-acc');
|
|
10901
|
+
const actionRow = acc && acc.querySelector('.ctx-acc-action-row');
|
|
10902
|
+
if (actionRow) actionRow.hidden = entering;
|
|
10903
|
+
tfReRenderScope('manager', 'project');
|
|
10893
10904
|
}},
|
|
10894
10905
|
]);
|
|
10895
10906
|
}
|
|
@@ -12457,7 +12468,15 @@ function tfRenderManager() {
|
|
|
12457
12468
|
// promotion); only the share action.
|
|
12458
12469
|
tfEnsureAccAction('manager-learn-acc', '↑', 'Share with others', () => {
|
|
12459
12470
|
const h = document.getElementById('manager-learnings');
|
|
12460
|
-
if (h)
|
|
12471
|
+
if (!h) return;
|
|
12472
|
+
const entering = h.dataset.lpShareMode !== 'true';
|
|
12473
|
+
h.dataset.lpShareMode = entering ? 'true' : 'false';
|
|
12474
|
+
// Hide the accordion action row while in share mode so the top button
|
|
12475
|
+
// doesn't duplicate the bottom share bar.
|
|
12476
|
+
const acc = document.getElementById('manager-learn-acc');
|
|
12477
|
+
const actionRow = acc && acc.querySelector('.ctx-acc-action-row');
|
|
12478
|
+
if (actionRow) actionRow.hidden = entering;
|
|
12479
|
+
tfReRenderScope('manager', 'machine');
|
|
12461
12480
|
});
|
|
12462
12481
|
// Issue #540 R4-R7: render manager team pool on every manager-tab activation.
|
|
12463
12482
|
renderManagerTeamPool();
|
|
@@ -13114,6 +13133,15 @@ function tfUpdateShareBar(host, scope, level) {
|
|
|
13114
13133
|
cancelBtn.addEventListener('click', () => {
|
|
13115
13134
|
sel.clear();
|
|
13116
13135
|
host.dataset.lpShareMode = 'false';
|
|
13136
|
+
// Restore the accordion action row that was hidden when share mode started.
|
|
13137
|
+
const accId = scope === 'manager' && level === 'machine' ? 'manager-learn-acc'
|
|
13138
|
+
: scope === 'manager' && level === 'project' ? 'proj-learnings-acc'
|
|
13139
|
+
: null;
|
|
13140
|
+
if (accId) {
|
|
13141
|
+
const acc = document.getElementById(accId);
|
|
13142
|
+
const actionRow = acc && acc.querySelector('.ctx-acc-action-row');
|
|
13143
|
+
if (actionRow) actionRow.hidden = false;
|
|
13144
|
+
}
|
|
13117
13145
|
tfReRenderScope(scope, level);
|
|
13118
13146
|
});
|
|
13119
13147
|
bar.appendChild(cancelBtn);
|
|
@@ -13133,64 +13161,120 @@ async function tfRunShareWithOthers(scope, level, selectedTitles) {
|
|
|
13133
13161
|
if (typeof showStatus === 'function') showStatus('Pick a project folder first, then share learnings.', true);
|
|
13134
13162
|
return;
|
|
13135
13163
|
}
|
|
13136
|
-
//
|
|
13137
|
-
//
|
|
13138
|
-
//
|
|
13139
|
-
|
|
13140
|
-
|
|
13141
|
-
if (projects.length > 1 && tf.area === 'manager') {
|
|
13142
|
-
tfShareWithOthersPicker(scope, level, selectedTitles, job);
|
|
13143
|
-
return;
|
|
13144
|
-
}
|
|
13145
|
-
await tfLaunchShareWithOthers(scope, level, selectedTitles, job, tf.activeProjectId);
|
|
13164
|
+
// Always show the destination picker when sharing from the Manager tab —
|
|
13165
|
+
// the manager must choose Company vs. This project regardless of how many
|
|
13166
|
+
// projects are loaded.
|
|
13167
|
+
const hostId = scope === 'manager' && level === 'machine' ? 'manager-learnings' : 'project-learnings';
|
|
13168
|
+
tfShareDestinationPicker(hostId, scope, level, selectedTitles, job);
|
|
13146
13169
|
}
|
|
13147
13170
|
|
|
13148
|
-
//
|
|
13149
|
-
//
|
|
13150
|
-
function
|
|
13151
|
-
const host = document.getElementById(
|
|
13171
|
+
// Destination picker: first ask Company vs. This project, then (for project)
|
|
13172
|
+
// optionally ask which project when more than one is loaded.
|
|
13173
|
+
function tfShareDestinationPicker(hostId, scope, level, selectedTitles, job) {
|
|
13174
|
+
const host = document.getElementById(hostId);
|
|
13152
13175
|
if (!host) return;
|
|
13153
|
-
//
|
|
13154
|
-
|
|
13155
|
-
|
|
13156
|
-
picker = document.createElement('div');
|
|
13176
|
+
// Remove any existing picker before showing a fresh one.
|
|
13177
|
+
host.querySelectorAll('.lp-project-picker').forEach((el) => el.remove());
|
|
13178
|
+
|
|
13179
|
+
const picker = document.createElement('div');
|
|
13157
13180
|
picker.className = 'lp-project-picker';
|
|
13158
|
-
|
|
13159
|
-
|
|
13160
|
-
|
|
13161
|
-
|
|
13162
|
-
|
|
13163
|
-
picker.appendChild(
|
|
13164
|
-
|
|
13165
|
-
|
|
13166
|
-
|
|
13167
|
-
|
|
13168
|
-
|
|
13169
|
-
|
|
13170
|
-
|
|
13171
|
-
|
|
13172
|
-
|
|
13173
|
-
|
|
13181
|
+
|
|
13182
|
+
// ── Step 1: destination scope ──────────────────────────────────────────
|
|
13183
|
+
const destLabel = document.createElement('div');
|
|
13184
|
+
destLabel.className = 'lp-pp-label';
|
|
13185
|
+
destLabel.textContent = 'Share to:';
|
|
13186
|
+
picker.appendChild(destLabel);
|
|
13187
|
+
|
|
13188
|
+
const destRow = document.createElement('div');
|
|
13189
|
+
destRow.className = 'lp-pp-dest-row';
|
|
13190
|
+
|
|
13191
|
+
const makeDestBtn = (value, text, desc) => {
|
|
13192
|
+
const btn = document.createElement('button');
|
|
13193
|
+
btn.type = 'button';
|
|
13194
|
+
btn.className = 'lp-pp-dest-btn';
|
|
13195
|
+
btn.dataset.dest = value;
|
|
13196
|
+
const name = document.createElement('span'); name.className = 'lp-pp-dest-name'; name.textContent = text;
|
|
13197
|
+
const sub = document.createElement('span'); sub.className = 'lp-pp-dest-sub'; sub.textContent = desc;
|
|
13198
|
+
btn.appendChild(name); btn.appendChild(sub);
|
|
13199
|
+
return btn;
|
|
13200
|
+
};
|
|
13201
|
+
|
|
13202
|
+
const companyBtn = makeDestBtn('org', 'Company', 'Everyone in the organization');
|
|
13203
|
+
const projectBtn = makeDestBtn('project', 'This project', 'Visible on a specific project');
|
|
13204
|
+
destRow.appendChild(companyBtn);
|
|
13205
|
+
destRow.appendChild(projectBtn);
|
|
13206
|
+
picker.appendChild(destRow);
|
|
13207
|
+
|
|
13208
|
+
// ── Step 2: project picker (hidden until "This project" chosen) ────────
|
|
13209
|
+
const projectSection = document.createElement('div');
|
|
13210
|
+
projectSection.className = 'lp-pp-project-section';
|
|
13211
|
+
projectSection.hidden = true;
|
|
13212
|
+
|
|
13213
|
+
const projects = (typeof tf !== 'undefined' && tf.projects) || [];
|
|
13214
|
+
if (projects.length > 1) {
|
|
13215
|
+
const projLabel = document.createElement('label');
|
|
13216
|
+
projLabel.className = 'lp-pp-label';
|
|
13217
|
+
const projLabelId = 'lp-pp-proj-' + Math.random().toString(36).slice(2, 8);
|
|
13218
|
+
projLabel.id = projLabelId;
|
|
13219
|
+
projLabel.textContent = 'Which project?';
|
|
13220
|
+
const projSel = document.createElement('select');
|
|
13221
|
+
projSel.className = 'lp-pp-sel';
|
|
13222
|
+
projSel.setAttribute('aria-labelledby', projLabelId);
|
|
13223
|
+
for (const p of projects) {
|
|
13224
|
+
const o = document.createElement('option'); o.value = p.id;
|
|
13225
|
+
o.textContent = p.name || p.id;
|
|
13226
|
+
if (p.id === tf.activeProjectId) o.selected = true;
|
|
13227
|
+
projSel.appendChild(o);
|
|
13228
|
+
}
|
|
13229
|
+
projectSection.appendChild(projLabel);
|
|
13230
|
+
projectSection.appendChild(projSel);
|
|
13231
|
+
projectSection._projSel = projSel;
|
|
13232
|
+
}
|
|
13233
|
+
picker.appendChild(projectSection);
|
|
13234
|
+
|
|
13235
|
+
// ── Action row ─────────────────────────────────────────────────────────
|
|
13174
13236
|
const bar = document.createElement('div'); bar.className = 'lp-pp-bar';
|
|
13175
|
-
const go = document.createElement('button');
|
|
13176
|
-
|
|
13237
|
+
const go = document.createElement('button');
|
|
13238
|
+
go.type = 'button'; go.className = 'rb-approve'; go.textContent = 'Start'; go.disabled = true;
|
|
13239
|
+
const cancel = document.createElement('button');
|
|
13240
|
+
cancel.type = 'button'; cancel.className = 'rb-secondary'; cancel.textContent = 'Cancel';
|
|
13177
13241
|
bar.appendChild(go); bar.appendChild(cancel);
|
|
13178
13242
|
picker.appendChild(bar);
|
|
13179
13243
|
host.appendChild(picker);
|
|
13244
|
+
|
|
13245
|
+
let chosenDest = null;
|
|
13246
|
+
|
|
13247
|
+
const selectDest = (btn) => {
|
|
13248
|
+
destRow.querySelectorAll('.lp-pp-dest-btn').forEach((b) => b.classList.remove('lp-pp-dest-selected'));
|
|
13249
|
+
btn.classList.add('lp-pp-dest-selected');
|
|
13250
|
+
chosenDest = btn.dataset.dest;
|
|
13251
|
+
projectSection.hidden = chosenDest !== 'project';
|
|
13252
|
+
go.disabled = false;
|
|
13253
|
+
};
|
|
13254
|
+
|
|
13255
|
+
companyBtn.addEventListener('click', () => selectDest(companyBtn));
|
|
13256
|
+
projectBtn.addEventListener('click', () => selectDest(projectBtn));
|
|
13257
|
+
|
|
13180
13258
|
cancel.addEventListener('click', () => picker.remove());
|
|
13259
|
+
|
|
13181
13260
|
go.addEventListener('click', async () => {
|
|
13182
13261
|
go.disabled = true;
|
|
13183
13262
|
picker.remove();
|
|
13184
|
-
|
|
13263
|
+
const projectId = chosenDest === 'project'
|
|
13264
|
+
? (projectSection._projSel ? projectSection._projSel.value : (tf.activeProjectId || state.projectPath))
|
|
13265
|
+
: (tf.activeProjectId || state.projectPath);
|
|
13266
|
+
await tfLaunchShareWithOthers(scope, level, selectedTitles, job, projectId, chosenDest);
|
|
13185
13267
|
});
|
|
13186
13268
|
}
|
|
13187
13269
|
|
|
13188
|
-
async function tfLaunchShareWithOthers(scope, level, selectedTitles, job, projectId) {
|
|
13270
|
+
async function tfLaunchShareWithOthers(scope, level, selectedTitles, job, projectId, destination) {
|
|
13271
|
+
const dest = destination || 'project';
|
|
13189
13272
|
let msg;
|
|
13190
13273
|
if (selectedTitles && selectedTitles.length > 0) {
|
|
13191
13274
|
const levelWord = level === 'project' ? 'project' : level === 'org' ? 'company' : 'manager';
|
|
13275
|
+
const destWord = dest === 'org' ? 'company (org) level' : 'project level';
|
|
13192
13276
|
const list = selectedTitles.map((t) => `- "${t}"`).join('\n');
|
|
13193
|
-
msg = `Run share-with-others. I want to share these ${levelWord}-level learnings:\n${list}\nShow me the
|
|
13277
|
+
msg = `Run share-with-others. I want to share these ${levelWord}-level learnings to the ${destWord}:\n${list}\nShow me the de-personalisation plan for each, then apply it.`;
|
|
13194
13278
|
} else {
|
|
13195
13279
|
msg = 'Run share-with-others. Show me everything I own at each level, grouped by type, so I can decide what to move.';
|
|
13196
13280
|
}
|
package/public/ai-hub/styles.css
CHANGED
|
@@ -2866,24 +2866,27 @@ button.small { padding: 4px 10px; font-size: 12px; }
|
|
|
2866
2866
|
body:is([data-surface="task-pane"],[data-surface="extension"]). No media queries — this is a surface
|
|
2867
2867
|
flag, not a viewport breakpoint. */
|
|
2868
2868
|
|
|
2869
|
-
body:is([data-surface="task-pane"],[data-surface="extension"]) {
|
|
2870
|
-
font-size: 13px;
|
|
2871
|
-
overflow: auto;
|
|
2872
|
-
|
|
2869
|
+
body:is([data-surface="task-pane"],[data-surface="extension"]) {
|
|
2870
|
+
font-size: 13px;
|
|
2871
|
+
overflow: auto;
|
|
2872
|
+
background: var(--bg);
|
|
2873
|
+
color: var(--text);
|
|
2874
|
+
}
|
|
2873
2875
|
body:is([data-surface="task-pane"],[data-surface="extension"]) .hub-tabs,
|
|
2874
2876
|
body:is([data-surface="task-pane"],[data-surface="extension"]):not(.task-pane-run-active) .hub-area,
|
|
2875
2877
|
body:is([data-surface="task-pane"],[data-surface="extension"]) .proj-tabs {
|
|
2876
2878
|
display: none !important;
|
|
2877
2879
|
}
|
|
2878
2880
|
.task-pane-launcher { display: none; }
|
|
2879
|
-
body:is([data-surface="task-pane"],[data-surface="extension"]):not(.task-pane-run-active) .task-pane-launcher {
|
|
2880
|
-
display: flex;
|
|
2881
|
-
flex-direction: column;
|
|
2882
|
-
gap: 10px;
|
|
2883
|
-
padding: 12px;
|
|
2884
|
-
border-bottom: 1px solid var(--
|
|
2885
|
-
background: var(--surface, #fff);
|
|
2886
|
-
|
|
2881
|
+
body:is([data-surface="task-pane"],[data-surface="extension"]):not(.task-pane-run-active) .task-pane-launcher {
|
|
2882
|
+
display: flex;
|
|
2883
|
+
flex-direction: column;
|
|
2884
|
+
gap: 10px;
|
|
2885
|
+
padding: 12px;
|
|
2886
|
+
border-bottom: 1px solid var(--line, rgba(0,0,0,0.07));
|
|
2887
|
+
background: var(--surface, #fff);
|
|
2888
|
+
color: var(--text, #171717);
|
|
2889
|
+
}
|
|
2887
2890
|
body:is([data-surface="task-pane"],[data-surface="extension"]).task-pane-run-active #area-projects {
|
|
2888
2891
|
display: flex !important;
|
|
2889
2892
|
min-height: 100vh;
|
|
@@ -2940,17 +2943,27 @@ body:is([data-surface="task-pane"],[data-surface="extension"]).task-pane-run-act
|
|
|
2940
2943
|
font-weight: 600;
|
|
2941
2944
|
color: var(--muted);
|
|
2942
2945
|
}
|
|
2943
|
-
.tp-launcher-field select,
|
|
2944
|
-
.tp-launcher-field textarea {
|
|
2945
|
-
width: 100%;
|
|
2946
|
-
min-width: 0;
|
|
2947
|
-
border: 1px solid var(--
|
|
2948
|
-
border-radius: 8px;
|
|
2949
|
-
background: var(--
|
|
2950
|
-
color: var(--text, #171717);
|
|
2951
|
-
font: inherit;
|
|
2952
|
-
font-size: 12px;
|
|
2953
|
-
}
|
|
2946
|
+
.tp-launcher-field select,
|
|
2947
|
+
.tp-launcher-field textarea {
|
|
2948
|
+
width: 100%;
|
|
2949
|
+
min-width: 0;
|
|
2950
|
+
border: 1px solid var(--line, rgba(0,0,0,0.12));
|
|
2951
|
+
border-radius: 8px;
|
|
2952
|
+
background: var(--surface, #fff);
|
|
2953
|
+
color: var(--text, #171717);
|
|
2954
|
+
font: inherit;
|
|
2955
|
+
font-size: 12px;
|
|
2956
|
+
}
|
|
2957
|
+
.tp-launcher-field select:focus,
|
|
2958
|
+
.tp-launcher-field textarea:focus {
|
|
2959
|
+
outline: 2px solid var(--accent, #0071e3);
|
|
2960
|
+
outline-offset: 1px;
|
|
2961
|
+
border-color: var(--accent, #0071e3);
|
|
2962
|
+
}
|
|
2963
|
+
.tp-launcher-field textarea::placeholder {
|
|
2964
|
+
color: var(--muted);
|
|
2965
|
+
opacity: 1;
|
|
2966
|
+
}
|
|
2954
2967
|
.tp-launcher-field select {
|
|
2955
2968
|
height: 34px;
|
|
2956
2969
|
padding: 0 8px;
|
|
@@ -2987,8 +3000,9 @@ body:is([data-surface="task-pane"],[data-surface="extension"]).task-pane-run-act
|
|
|
2987
3000
|
gap: 10px;
|
|
2988
3001
|
min-width: 0;
|
|
2989
3002
|
padding: 10px 12px;
|
|
2990
|
-
border-bottom: 1px solid var(--
|
|
3003
|
+
border-bottom: 1px solid var(--line, rgba(0,0,0,0.07));
|
|
2991
3004
|
background: var(--surface, #fff);
|
|
3005
|
+
color: var(--text, #171717);
|
|
2992
3006
|
}
|
|
2993
3007
|
.tp-run-copy {
|
|
2994
3008
|
min-width: 0;
|
|
@@ -3022,6 +3036,31 @@ body:is([data-surface="task-pane"],[data-surface="extension"]) #task-pane-new-jo
|
|
|
3022
3036
|
font-size: 12px;
|
|
3023
3037
|
white-space: nowrap;
|
|
3024
3038
|
}
|
|
3039
|
+
:root[data-theme="dark"] body:is([data-surface="task-pane"],[data-surface="extension"]) {
|
|
3040
|
+
background: var(--bg);
|
|
3041
|
+
}
|
|
3042
|
+
:root[data-theme="dark"] body:is([data-surface="task-pane"],[data-surface="extension"]):not(.task-pane-run-active) .task-pane-launcher,
|
|
3043
|
+
:root[data-theme="dark"] body:is([data-surface="task-pane"],[data-surface="extension"]).task-pane-run-active .task-pane-run-nav {
|
|
3044
|
+
background: #242424;
|
|
3045
|
+
border-color: rgba(255, 255, 255, 0.16);
|
|
3046
|
+
}
|
|
3047
|
+
:root[data-theme="dark"] body:is([data-surface="task-pane"],[data-surface="extension"]) .tp-launcher-field select,
|
|
3048
|
+
:root[data-theme="dark"] body:is([data-surface="task-pane"],[data-surface="extension"]) .tp-launcher-field textarea {
|
|
3049
|
+
background: #171717;
|
|
3050
|
+
color: #f2f2f2;
|
|
3051
|
+
border-color: rgba(255, 255, 255, 0.22);
|
|
3052
|
+
}
|
|
3053
|
+
:root[data-theme="dark"] body:is([data-surface="task-pane"],[data-surface="extension"]) .tp-launcher-field select option {
|
|
3054
|
+
background: #171717;
|
|
3055
|
+
color: #f2f2f2;
|
|
3056
|
+
}
|
|
3057
|
+
:root[data-theme="dark"] body:is([data-surface="task-pane"],[data-surface="extension"]) .tp-launcher-context,
|
|
3058
|
+
:root[data-theme="dark"] body:is([data-surface="task-pane"],[data-surface="extension"]) .tp-launcher-status,
|
|
3059
|
+
:root[data-theme="dark"] body:is([data-surface="task-pane"],[data-surface="extension"]) .tp-launcher-field,
|
|
3060
|
+
:root[data-theme="dark"] body:is([data-surface="task-pane"],[data-surface="extension"]) .tp-run-eyebrow,
|
|
3061
|
+
:root[data-theme="dark"] body:is([data-surface="task-pane"],[data-surface="extension"]) .tp-run-meta {
|
|
3062
|
+
color: #c7c7c7;
|
|
3063
|
+
}
|
|
3025
3064
|
body:is([data-surface="task-pane"],[data-surface="extension"]) .page {
|
|
3026
3065
|
padding: 10px 12px 8px;
|
|
3027
3066
|
gap: 10px;
|
|
@@ -4166,14 +4205,18 @@ body.hub-shell { display: flex; flex-direction: column; height: 100vh; overflow:
|
|
|
4166
4205
|
.modal-hdr { padding: 18px 22px; border-bottom: 1px solid var(--line); }
|
|
4167
4206
|
.modal-hdr h2 { font-size: 18px; font-weight: 700; margin: 0 0 4px; }
|
|
4168
4207
|
.modal-hdr p { font-size: 13px; color: var(--muted); margin: 0; line-height: 1.5; }
|
|
4169
|
-
.modal-body { padding: 18px 22px; display: flex; flex-direction: column; gap: 14px; }
|
|
4170
|
-
.modal-close { float: right; background: var(--bg); border: none; width: 28px; height: 28px; border-radius: 50%; font-size: 16px; cursor: pointer; color: var(--muted); line-height: 1; }
|
|
4171
|
-
.modal-close:hover { color: var(--text); }
|
|
4172
|
-
.
|
|
4173
|
-
.aom-cancel-btn
|
|
4174
|
-
.
|
|
4175
|
-
.
|
|
4176
|
-
.modal-
|
|
4208
|
+
.modal-body { padding: 18px 22px; display: flex; flex-direction: column; gap: 14px; }
|
|
4209
|
+
.modal-close { float: right; background: var(--bg); border: none; width: 28px; height: 28px; border-radius: 50%; font-size: 16px; cursor: pointer; color: var(--muted); line-height: 1; }
|
|
4210
|
+
.modal-close:hover { color: var(--text); }
|
|
4211
|
+
.modal-close:focus, .modal-close:focus-visible { outline: 2px solid var(--accent-strong); outline-offset: 2px; }
|
|
4212
|
+
.aom-cancel-btn { background: var(--surface); border: 1px solid color-mix(in srgb, var(--accent-strong) 52%, var(--line)); border-radius: 20px; padding: 9px 20px; font-size: 14px; cursor: pointer; color: var(--accent-strong); }
|
|
4213
|
+
.aom-cancel-btn:hover { border-color: var(--accent-strong); color: var(--accent-strong); }
|
|
4214
|
+
.aom-cancel-btn:focus, .aom-cancel-btn:focus-visible { outline: 2px solid var(--accent-strong); outline-offset: 2px; }
|
|
4215
|
+
.modal-footer { padding: 12px 22px 20px; display: flex; align-items: center; justify-content: space-between; }
|
|
4216
|
+
.modal-next { padding: 9px 22px; background: var(--accent-strong); color: var(--bg); border: none; border-radius: 20px; font-size: 14px; font-weight: 600; cursor: pointer; }
|
|
4217
|
+
.modal-next:hover { opacity: .9; }
|
|
4218
|
+
.modal-next:focus, .modal-next:focus-visible { outline: 2px solid var(--accent-strong); outline-offset: 2px; }
|
|
4219
|
+
.modal-back { font-size: 13px; color: var(--muted); background: none; border: none; cursor: pointer; }
|
|
4177
4220
|
.modal-step-dots { display: flex; gap: 6px; justify-content: center; margin-bottom: 18px; }
|
|
4178
4221
|
.modal-dot { width: 6px; height: 6px; border-radius: 50%; background: var(--line); }
|
|
4179
4222
|
.modal-dot.on { background: var(--text); }
|
|
@@ -4630,7 +4673,8 @@ body.hub-shell { display: flex; flex-direction: column; height: 100vh; overflow:
|
|
|
4630
4673
|
align-items: center;
|
|
4631
4674
|
justify-content: center;
|
|
4632
4675
|
}
|
|
4633
|
-
.np-close:hover { color: var(--text); }
|
|
4676
|
+
.np-close:hover { color: var(--text); }
|
|
4677
|
+
.np-close:focus, .np-close:focus-visible { outline: 2px solid var(--accent-strong); outline-offset: 2px; }
|
|
4634
4678
|
|
|
4635
4679
|
.np-step-dots {
|
|
4636
4680
|
display: flex;
|
|
@@ -4692,26 +4736,29 @@ body.hub-shell { display: flex; flex-direction: column; height: 100vh; overflow:
|
|
|
4692
4736
|
justify-content: space-between;
|
|
4693
4737
|
}
|
|
4694
4738
|
|
|
4695
|
-
.np-next {
|
|
4696
|
-
padding: 9px 22px;
|
|
4697
|
-
background: var(--
|
|
4698
|
-
color:
|
|
4699
|
-
border: none;
|
|
4700
|
-
border-radius: 20px;
|
|
4701
|
-
font-size: 14px;
|
|
4702
|
-
font-weight: 600;
|
|
4703
|
-
cursor: pointer;
|
|
4704
|
-
}
|
|
4705
|
-
.np-next:hover { opacity: .88; }
|
|
4706
|
-
|
|
4707
|
-
.np-
|
|
4708
|
-
|
|
4709
|
-
|
|
4710
|
-
|
|
4711
|
-
|
|
4712
|
-
|
|
4713
|
-
|
|
4714
|
-
|
|
4739
|
+
.np-next {
|
|
4740
|
+
padding: 9px 22px;
|
|
4741
|
+
background: var(--accent-strong);
|
|
4742
|
+
color: var(--bg);
|
|
4743
|
+
border: none;
|
|
4744
|
+
border-radius: 20px;
|
|
4745
|
+
font-size: 14px;
|
|
4746
|
+
font-weight: 600;
|
|
4747
|
+
cursor: pointer;
|
|
4748
|
+
}
|
|
4749
|
+
.np-next:hover { opacity: .88; }
|
|
4750
|
+
.np-next:focus, .np-next:focus-visible { outline: 2px solid var(--accent-strong); outline-offset: 2px; }
|
|
4751
|
+
.np-next:disabled { opacity: .55; cursor: not-allowed; }
|
|
4752
|
+
|
|
4753
|
+
.np-back {
|
|
4754
|
+
font-size: 13px;
|
|
4755
|
+
color: var(--accent-strong);
|
|
4756
|
+
background: none;
|
|
4757
|
+
border: none;
|
|
4758
|
+
cursor: pointer;
|
|
4759
|
+
}
|
|
4760
|
+
.np-back:hover { color: var(--accent-strong); text-decoration: underline; }
|
|
4761
|
+
.np-back:focus, .np-back:focus-visible { outline: 2px solid var(--accent-strong); outline-offset: 2px; }
|
|
4715
4762
|
|
|
4716
4763
|
/* ── Discard-guard confirm (shown when a dirty wizard is dismissed) ── */
|
|
4717
4764
|
.np-discard {
|
|
@@ -4736,10 +4783,11 @@ body.hub-shell { display: flex; flex-direction: column; height: 100vh; overflow:
|
|
|
4736
4783
|
.np-discard-msg { font-size: 13px; color: var(--muted); margin: 0 0 18px; line-height: 1.5; }
|
|
4737
4784
|
.np-discard-row { display: flex; gap: 8px; justify-content: flex-end; }
|
|
4738
4785
|
.np-discard-row button { font: inherit; font-size: 13px; font-weight: 600; border-radius: 9px; padding: 8px 16px; cursor: pointer; }
|
|
4739
|
-
.np-discard-keep { background: var(--surface); border: 1px solid var(--line); color: var(--text); }
|
|
4740
|
-
.np-discard-keep:hover { border-color: var(--muted); }
|
|
4741
|
-
.np-discard-yes { background: var(--danger, #d2261f); color: #fff; border: none; }
|
|
4742
|
-
.np-discard-yes:hover { opacity: .9; }
|
|
4786
|
+
.np-discard-keep { background: var(--surface); border: 1px solid var(--line); color: var(--text); }
|
|
4787
|
+
.np-discard-keep:hover { border-color: var(--muted); }
|
|
4788
|
+
.np-discard-yes { background: var(--danger, #d2261f); color: #fff; border: none; }
|
|
4789
|
+
.np-discard-yes:hover { opacity: .9; }
|
|
4790
|
+
.np-discard-row button:focus, .np-discard-row button:focus-visible { outline: 2px solid var(--accent-strong); outline-offset: 2px; }
|
|
4743
4791
|
|
|
4744
4792
|
/* ── D11: Source attachment row (step 2) ── */
|
|
4745
4793
|
.np-source {
|
|
@@ -5226,8 +5274,9 @@ body.hub-shell { display: flex; flex-direction: column; height: 100vh; overflow:
|
|
|
5226
5274
|
/* ── New-project: folder picker row ── */
|
|
5227
5275
|
.np-folder-row { display:flex; gap:8px; }
|
|
5228
5276
|
.np-folder-row input { flex:1; }
|
|
5229
|
-
.np-browse-btn { padding:9px 16px; background:var(--
|
|
5230
|
-
.np-browse-btn:hover { opacity:.85; }
|
|
5277
|
+
.np-browse-btn { padding:9px 16px; background:var(--accent-strong); color:var(--bg); border:none; border-radius:9px; font-size:13px; font-weight:600; cursor:pointer; white-space:nowrap; flex-shrink:0; }
|
|
5278
|
+
.np-browse-btn:hover { opacity:.85; }
|
|
5279
|
+
.np-browse-btn:focus, .np-browse-btn:focus-visible { outline:2px solid var(--accent-strong); outline-offset:2px; }
|
|
5231
5280
|
.np-field-hint { font-size:11px; color:var(--muted); margin-top:5px; line-height:1.5; }
|
|
5232
5281
|
|
|
5233
5282
|
@media (max-width: 520px) {
|