Connect the two tools: cross-nav links and Send to Scope Lock handoff
- Add a header link on each tool pointing to the other. - Add a Send to Scope Lock button on Workshop clusters that carry a candidate solution tag. Writes the item to a shared localStorage queue (sde_scope_handoff_v1). - Scope Lock drains that queue on load and adds each item to the Scope Boundary Board as unsorted, deduping by a deterministic id so a repeat send never creates a duplicate. - No backend needed: both tools already share the same browser origin. - Verified with a jsdom test covering send, drain, reload, and re-send-after-reset. - Update README and CHANGELOG.
This commit is contained in:
@@ -24,7 +24,7 @@
|
||||
header .brand span{color:#c6c6c6; font-weight:400;}
|
||||
header .spacer{flex:1;}
|
||||
.persist-note{font-size:12px; color:#c6c6c6;}
|
||||
.hdr-btn{background:transparent; color:#fff; border:1px solid #6f6f6f; padding:7px 13px; font-size:13.5px;}
|
||||
.hdr-btn{background:transparent; color:#fff; border:1px solid #6f6f6f; padding:7px 13px; font-size:13.5px; text-decoration:none; display:inline-block;}
|
||||
.hdr-btn:hover{background:#353535;}
|
||||
.hdr-btn.primary{background:var(--interactive); border-color:var(--interactive);}
|
||||
.hdr-btn.primary:hover{background:var(--interactive-hover);}
|
||||
@@ -181,6 +181,7 @@
|
||||
<div class="brand"><strong>Project SDE</strong> <span>| Field Problem Workshop v5</span></div>
|
||||
<div class="spacer"></div>
|
||||
<span class="persist-note">State lives in this session. Save JSON before closing.</span>
|
||||
<a class="hdr-btn" href="/tools/scope-lock-meeting-suite.html">Scope Lock Meeting Suite →</a>
|
||||
<button class="hdr-btn" id="btnLoad">Load JSON</button>
|
||||
<button class="hdr-btn" id="btnSave">Save JSON</button>
|
||||
<button class="hdr-btn" id="btnReset">Reset</button>
|
||||
@@ -388,6 +389,26 @@ function clusterById(id){ return S.clusters.find(c=>c.id===id); }
|
||||
function membersOf(cid){ return S.problems.filter(p=>p.cluster===cid); }
|
||||
function activeMembers(cid){ return S.problems.filter(p=>p.cluster===cid && p.type==='Problem'); }
|
||||
|
||||
/* ================= HANDOFF TO SCOPE LOCK ================= */
|
||||
// Shared localStorage queue that the Scope Lock Meeting Suite reads on load.
|
||||
// Both tools are served from the same origin, so this works with no backend.
|
||||
const SCOPE_HANDOFF_KEY = 'sde_scope_handoff_v1';
|
||||
function sendToScopeLock(c){
|
||||
let queue = [];
|
||||
try{
|
||||
const raw = localStorage.getItem(SCOPE_HANDOFF_KEY);
|
||||
if(raw) queue = JSON.parse(raw);
|
||||
if(!Array.isArray(queue)) queue = [];
|
||||
}catch(e){ queue = []; }
|
||||
queue.push({
|
||||
id: 'ws_' + c.id,
|
||||
t: c.ws,
|
||||
note: c.root || c.name || '',
|
||||
sentAt: Date.now()
|
||||
});
|
||||
try{ localStorage.setItem(SCOPE_HANDOFF_KEY, JSON.stringify(queue)); }catch(e){}
|
||||
}
|
||||
|
||||
document.querySelectorAll('nav button').forEach(b=>{
|
||||
b.addEventListener('click', ()=>{
|
||||
document.querySelectorAll('nav button').forEach(x=>x.classList.remove('active'));
|
||||
@@ -474,6 +495,7 @@ function renderMap(){
|
||||
<span class="cl-count">${mem.length} issue${mem.length===1?'':'s'}${c.ws?' · '+esc(c.ws):''}</span>
|
||||
<span class="spacer"></span>
|
||||
${c.promoted?'<span class="promoted-flag">✓ In Breadcrumbs</span>':`<button class="btn small ghost" data-cprom="${c.id}">Promote →</button>`}
|
||||
${c.ws ? (c.sentToScope ? '<span class="promoted-flag">✓ Sent to Scope Lock</span>' : `<button class="btn small ghost" data-cscope="${c.id}">Send to Scope Lock →</button>`) : ''}
|
||||
<div class="dot-ctl">
|
||||
<button class="dot-btn" data-cdot="${c.id}" data-d="-1">−</button>
|
||||
<span class="dot-n">${c.dots}</span>
|
||||
@@ -508,6 +530,15 @@ document.getElementById('panel-map').addEventListener('click', e=>{
|
||||
}
|
||||
return;
|
||||
}
|
||||
if(t.dataset.cscope){
|
||||
const c = clusterById(t.dataset.cscope);
|
||||
if(c && c.ws && !c.sentToScope){
|
||||
sendToScopeLock(c);
|
||||
c.sentToScope = true;
|
||||
save(); renderMap(); toast('Sent "' + c.ws + '" to Scope Lock');
|
||||
}
|
||||
return;
|
||||
}
|
||||
if(t.dataset.cdel){
|
||||
const c = clusterById(t.dataset.cdel);
|
||||
if(c){
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
header .meta{font-size:13px; color:#c6c6c6;}
|
||||
.hdr-btn{
|
||||
background:transparent; color:#fff; border:1px solid #6f6f6f;
|
||||
padding:7px 14px; font-size:14px;
|
||||
padding:7px 14px; font-size:14px; text-decoration:none; display:inline-block;
|
||||
}
|
||||
.hdr-btn:hover{background:#353535;}
|
||||
.hdr-btn.primary{background:var(--interactive); border-color:var(--interactive);}
|
||||
@@ -231,6 +231,7 @@
|
||||
<div class="brand"><strong>Project SDE</strong> <span>| Scope Lock Meeting Suite</span></div>
|
||||
<div class="spacer"></div>
|
||||
<div class="meta">Micron Pilot | Innovation Team</div>
|
||||
<a class="hdr-btn" href="/tools/field-problem-workshop.html">Field Problem Workshop →</a>
|
||||
<button class="hdr-btn" id="btnReset">Reset all</button>
|
||||
<button class="hdr-btn primary" id="btnExport">Export meeting summary</button>
|
||||
</header>
|
||||
@@ -783,6 +784,33 @@ function confirmModal(opts, onConfirm){
|
||||
}
|
||||
|
||||
function renderAll(){ renderScope(); renderRank(); renderRegistry(); renderReady(); }
|
||||
|
||||
/* ================= HANDOFF FROM FIELD PROBLEM WORKSHOP ================= */
|
||||
// Shared localStorage queue the Workshop's "Send to Scope Lock" button writes to.
|
||||
// Both tools are served from the same origin, so this works with no backend.
|
||||
const SCOPE_HANDOFF_KEY = 'sde_scope_handoff_v1';
|
||||
function drainScopeHandoff(){
|
||||
let queue = [];
|
||||
try{
|
||||
const raw = localStorage.getItem(SCOPE_HANDOFF_KEY);
|
||||
if(raw) queue = JSON.parse(raw);
|
||||
if(!Array.isArray(queue)) queue = [];
|
||||
}catch(e){ queue = []; }
|
||||
if(!queue.length) return;
|
||||
let added = 0;
|
||||
queue.forEach(item=>{
|
||||
if(!item || !item.id || !item.t) return;
|
||||
if(S.scope.some(s=>s.id===item.id)) return; // already added on a previous load
|
||||
S.scope.push({id:item.id, t:item.t, b:'', lock:false});
|
||||
added++;
|
||||
});
|
||||
try{ localStorage.removeItem(SCOPE_HANDOFF_KEY); }catch(e){}
|
||||
if(added){
|
||||
save();
|
||||
toast(added===1 ? 'Added 1 item from Field Problem Workshop' : 'Added ' + added + ' items from Field Problem Workshop');
|
||||
}
|
||||
}
|
||||
drainScopeHandoff();
|
||||
renderAll();
|
||||
</script>
|
||||
</body>
|
||||
|
||||
Reference in New Issue
Block a user