From d22834f2f14b3c16a72287a56557217018002dfd Mon Sep 17 00:00:00 2001 From: "n.siegfried" Date: Fri, 14 Aug 2026 18:25:06 -0500 Subject: [PATCH] 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) --- html/field.js | 10 +++++++++- html/project-data.js | 19 +++++++++++++++++++ html/wp-chrome.js | 19 ++++++++++++++++++- 3 files changed, 46 insertions(+), 2 deletions(-) diff --git a/html/field.js b/html/field.js index f81a7af..4ec17e8 100644 --- a/html/field.js +++ b/html/field.js @@ -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() { diff --git a/html/project-data.js b/html/project-data.js index 22db885..dd56abe 100644 --- a/html/project-data.js +++ b/html/project-data.js @@ -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. diff --git a/html/wp-chrome.js b/html/wp-chrome.js index ae546b4..9783485 100644 --- a/html/wp-chrome.js +++ b/html/wp-chrome.js @@ -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 () {}); }