T1.1 (cont.) - F1: fill in the id-only stub, and resolve deep links in the bar

Completing T1.1. My first verification primed localStorage before loading each
page, which made both sources of truth agree and hid two remaining cases. Re-run
with genuinely cold storage, the app bar still showed "(unnamed)" on the field
view and "Select a project" on the console pages.

Two causes, both the same F1 shape - a page holding a copy the shared store
does not have:

1. field.js could only write {id} at boot (it needs the id synchronously, for
   the per-project storage namespace), then resolved the full record into a
   local PROJECT variable, rendered "Project: Job A" from it, and never
   published it. The store kept the stub, so the bar read "(unnamed)".

   setActive now fills a nameless record in from the cached project list, or
   from the API when the cache has not loaded yet, and re-checks the id before
   applying a slow response so it cannot overwrite a project the user has since
   switched to. That fixes every caller of this shape rather than the one that
   was caught - work-package-suite-app.js and wp-creation-app.js write the same
   stub. field.js also publishes the record it already fetched, so the common
   path costs no extra request.

2. admin.html and users.html have no project-resolution logic of their own, so
   nothing read ?project= and a deep link left the bar on whatever was last
   stored. The bar is the one component every chromed page has, so it resolves
   the parameter once in wp-chrome.js rather than being taught to five pages.

Verified with localStorage cleared before every navigation: a cold deep link
now shows the project on field, SOP wizard, launcher, admin and users, and the
stored record carries the name rather than a stub.

The creator remains the one page with no app bar - it loads no chrome because
it renders as the iframe child. T7.1.

browser_check 71/71. f_items F1 FIXED.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-14 18:25:06 -05:00
parent 5d5511a458
commit d22834f2f1
3 changed files with 46 additions and 2 deletions

View File

@@ -32,7 +32,15 @@ function boot() {
PID = params.get('project') || (ProjectData.getActiveId && ProjectData.getActiveId()) || '';
if (!PID) { showNoProject(); return; }
if (ProjectData.getActiveId && ProjectData.getActiveId() !== PID) { try { ProjectData.setActive({ id: PID }); } catch (e) {} }
if (ProjectData.get) { ProjectData.get(PID).then(function (p) { PROJECT = p; renderCtx(); }).catch(function () {}); }
// Publish the resolved record rather than keeping it to ourselves: line 34 could
// only write the id, and the app bar reads the shared record, not PROJECT.
if (ProjectData.get) {
ProjectData.get(PID).then(function (p) {
PROJECT = p;
if (p && p.name) { try { ProjectData.setActive(p); } catch (e) {} }
renderCtx();
}).catch(function () {});
}
loadWPs();
}
function renderCtx() {

View File

@@ -112,6 +112,25 @@
}
} catch (e) {}
notifyActive(p || null);
// A caller that knew only the id leaves the store holding a stub, and every
// reader then renders "(unnamed)" — field.js sets {id} on boot and resolves the
// record into a variable of its own. Fill the stub in from the cached list, or
// from the API when the cache has not loaded yet. The re-entry carries a name,
// so it cannot loop; the id re-check stops a slow response from overwriting a
// project the user has since switched to.
if (p && p.id && !p.name) {
var self = this;
var cached = readLocal().filter(function (x) { return x.id === p.id; })[0];
if (cached && cached.name) { self.setActive(cached); return; }
try {
if (self.get) {
self.get(p.id).then(function (full) {
if (full && full.name && self.getActiveId() === full.id) self.setActive(full);
}).catch(function () {});
}
} catch (e) {}
}
},
// Subscribe to active-project changes. Returns an unsubscribe function.

View File

@@ -163,7 +163,24 @@
.then(function (r) { return r.ok ? r.json() : []; });
}
Promise.resolve(p)
.then(function (list) { projects = Array.isArray(list) ? list : []; switcher.wpcRefresh(); })
.then(function (list) {
projects = Array.isArray(list) ? list : [];
// A deep link names the project, and the bar is the one component every
// page carrying chrome has. The launcher, SOP wizard, creator and field
// view each resolve ?project= themselves; admin.html and users.html have
// no project logic at all, so without this their bar shows whatever was
// last stored — or "Select a project" on a cold browser — while the URL
// says otherwise. Resolving it here covers every page once.
try {
var wanted = new URLSearchParams(location.search).get('project');
if (wanted && window.ProjectData && ProjectData.setActive) {
var hit = projects.filter(function (x) { return x.id === wanted; })[0];
var cur = activeProject();
if (hit && (!cur || cur.id !== hit.id || !cur.name)) ProjectData.setActive(hit);
}
} catch (e) {}
switcher.wpcRefresh();
})
.catch(function () {});
}