Isolate SOP/WP data per project (namespaced localStorage)

SOP and Work Package localStorage keys are now namespaced by the active
project via ProjectData.key(base) -> base+'__'+<projectId> (SK() in the
suite, wpKey() in the creator), so switching projects shows that project's
own SOP and packages. project-data.js runs a one-time discard of the legacy
un-namespaced keys (guarded by wp_ns_migrated_v1), per the chosen approach.

Active project is resolved before the store loads in both the suite and the
creator so namespaced keys resolve correctly, including standalone deep-links.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-15 15:46:59 -07:00
parent 3c40b58ff8
commit e5c450597a
5 changed files with 75 additions and 33 deletions

View File

@@ -4,6 +4,10 @@ let currentStep = 1;
let sopComplete = false;
let allComments = [];
// Per-project storage key: 'wp_suite_sop' → 'wp_suite_sop__<projId>' when a
// project is active. Keeps each project's SOP separate in the browser.
function SK(base){ try { return (typeof ProjectData !== 'undefined' && ProjectData.key) ? ProjectData.key(base) : base; } catch(e){ return base; } }
let state = {
project: {name:'', number:'', client:'', division:'', site:''},
team: {pm:'', apm:'', cm:'', qm:''},
@@ -126,13 +130,15 @@ window.addEventListener('DOMContentLoaded',()=>{
renderStandardConstraints();
renderSequenceSteps();
renderSources();
// Resolve the active project FIRST so per-project storage keys are correct
// before we restore this project's SOP.
const params = new URLSearchParams(window.location.search);
applyProjectContext(params.get('project'));
restoreSavedSOP();
updateStepUI();
updateProjectDisplay();
// Deep-link: ?tab=sop | ?tab=wp | ?view=dashboard | ?project=<id> from the home page.
const params = new URLSearchParams(window.location.search);
applyProjectContext(params.get('project'));
// Deep-link: ?tab=sop | ?tab=wp | ?view=dashboard from the home page.
const tab = params.get('tab');
if(params.get('view') === 'dashboard') switchTool('wp');
else if(tab === 'wp' || tab === 'sop') switchTool(tab);
@@ -203,9 +209,9 @@ function loadSampleData(){
function restoreSavedSOP(){
let savedState = null, savedSop = null, complete = false;
try {
complete = localStorage.getItem('wp_suite_sop_complete') === '1';
savedState = JSON.parse(localStorage.getItem('wp_suite_state') || 'null');
savedSop = JSON.parse(localStorage.getItem('wp_suite_sop') || 'null');
complete = localStorage.getItem(SK('wp_suite_sop_complete')) === '1';
savedState = JSON.parse(localStorage.getItem(SK('wp_suite_state')) || 'null');
savedSop = JSON.parse(localStorage.getItem(SK('wp_suite_sop')) || 'null');
} catch(e){}
if(!complete || !savedState) return;
@@ -306,15 +312,18 @@ function onSOPReady(){
// record so the SOP is authored against the chosen project.
let activeProject = null;
function applyProjectContext(projectId){
try { if(typeof ProjectData !== 'undefined') activeProject = ProjectData.getActive(); } catch(e){}
if(projectId && (!activeProject || activeProject.id !== projectId)){
// Deep-linked to a project we don't have cached yet — resolve it, then prefill.
try {
if(typeof ProjectData !== 'undefined'){
try {
if(typeof ProjectData !== 'undefined'){
if(projectId && ProjectData.getActiveId() !== projectId){
// Deep-linked to a project that isn't the cached active one. Seed the id
// immediately so namespaced storage keys resolve, then fetch the full record.
const cached = ProjectData.getActive();
ProjectData.setActive(cached && cached.id === projectId ? cached : { id: projectId });
ProjectData.get(projectId).then(p => { if(p){ activeProject = p; ProjectData.setActive(p); prefillProjectFields(); updateProjectDisplay(); } });
}
} catch(e){}
}
activeProject = ProjectData.getActive();
}
} catch(e){}
prefillProjectFields();
}
function prefillProjectFields(){
@@ -713,11 +722,12 @@ function completeSOP(){
sopComplete = true;
updateProjectDisplay();
// Persist for the home page (green / "Review") and for the WP Creator tab.
// Persist for the home page (green / "Review") and for the WP Creator tab,
// namespaced to the active project so each project keeps its own SOP.
try {
localStorage.setItem('wp_suite_sop', JSON.stringify(sop));
localStorage.setItem('wp_suite_state', JSON.stringify(state));
localStorage.setItem('wp_suite_sop_complete', '1');
localStorage.setItem(SK('wp_suite_sop'), JSON.stringify(sop));
localStorage.setItem(SK('wp_suite_state'), JSON.stringify(state));
localStorage.setItem(SK('wp_suite_sop_complete'), '1');
} catch(e){}
track('sop_generated', {woTypes: sop.woTypes.length, constraints: sop.constraints.length});