diff --git a/CHANGELOG.md b/CHANGELOG.md index af343f8..4845352 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ All notable changes to this project are logged here, newest first. This file starts from the point the toolkit was handed off and set up in this repo. +## 2026-09-04 (2) + +- Added: the two tools now work together. Each tool's header has a link to the other tool. On the Field Problem Workshop, a cluster tagged with a candidate solution (for example "Tracking MVP (pilot)") gets a "Send to Scope Lock" button. Clicking it adds that cluster as a real, unsorted item on the Scope Lock Meeting Suite's Scope Boundary Board. Both tools already share the same browser origin, so this uses a small shared `localStorage` queue; no backend or database was needed. The item lands on Scope Lock's board the next time that tool is opened or refreshed, and re-sending the same cluster will not create a duplicate. Verified with a jsdom test that simulates both tools sharing one browser's storage. + ## 2026-09-04 - Fixed: Portainer stack deploy failed with `env file /data/compose/44/.env not found`. Cause: `docker-compose.yml` used `env_file: - .env`, which requires a real file on disk; Portainer's UI-entered environment variables satisfy `${VAR}` substitution in the compose file but do not create that file. Changed `docker-compose.yml` to reference each setting as `${VAR:-default}` under `environment:` instead, which works both for a real local `.env` (auto-loaded by Compose for substitution) and for values entered directly in Portainer. Corrected the wrong explanation of this in `PORTAINER_DEPLOY.md`. diff --git a/README.md b/README.md index d5998b3..168a189 100644 --- a/README.md +++ b/README.md @@ -99,6 +99,18 @@ The two tools store data in different ways. 1. Click Export in the tool to create a durable record. 2. Save the exported file to your computer or to a shared drive. +## How the tools work together + +Each tool's header has a link to the other tool. + +The Field Problem Workshop can send a cluster to the Scope Lock Meeting Suite. + +1. Tag a cluster with a candidate solution, for example "Tracking MVP (pilot)". +2. Click "Send to Scope Lock" on that cluster. +3. Open the Scope Lock Meeting Suite. The cluster appears as a new, unsorted item on the Scope Boundary Board. + +The send action stores the item in the browser's local storage. Both tools must run from the same server address for this to work. Sending the same cluster twice does not create a duplicate item. + ## Reset a tool Each tool has a Reset control. diff --git a/tools/field-problem-workshop.html b/tools/field-problem-workshop.html index 1e89cc4..4319b4f 100644 --- a/tools/field-problem-workshop.html +++ b/tools/field-problem-workshop.html @@ -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 @@
Project SDE | Field Problem Workshop v5
State lives in this session. Save JSON before closing. + Scope Lock Meeting Suite → @@ -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(){ ${mem.length} issue${mem.length===1?'':'s'}${c.ws?' · '+esc(c.ws):''} ${c.promoted?'':``} + ${c.ws ? (c.sentToScope ? '' : ``) : ''}
${c.dots} @@ -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){ diff --git a/tools/scope-lock-meeting-suite.html b/tools/scope-lock-meeting-suite.html index 495aba7..d35e287 100644 --- a/tools/scope-lock-meeting-suite.html +++ b/tools/scope-lock-meeting-suite.html @@ -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 @@
Project SDE | Scope Lock Meeting Suite
Micron Pilot | Innovation Team
+ Field Problem Workshop → @@ -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();