Skip to content

Commit 6d658a3

Browse files
committed
Complete shared Mock DB session user persistence validation - PR_26157_011-shared-mock-db-completion-validation
1 parent e0c2a2b commit 6d658a3

39 files changed

Lines changed: 908 additions & 480 deletions

admin/db-viewer.html

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
<div class="kicker">Admin Only / Mock DB</div>
2020
<h1>Mock DB</h1>
2121
<p class="lede">Read-only mock DB dump for project tables, relationships, and data diagnostics.</p>
22+
<p class="status" role="status" data-session-user-header>Session user: User 1</p>
2223
</div>
2324
</section>
2425
<section class="section">
@@ -29,11 +30,24 @@ <h1>Mock DB</h1>
2930
<h2>Mock DB</h2>
3031
</div>
3132
<div class="accordion-stack">
33+
<details class="vertical-accordion" open>
34+
<summary>Session User</summary>
35+
<div class="accordion-body content-stack">
36+
<div class="action-group" role="group" aria-label="Mock DB session user controls" data-session-user-controls>
37+
<button class="btn btn--compact" type="button" data-session-user-button="user1">User 1</button>
38+
<button class="btn btn--compact" type="button" data-session-user-button="user2">User 2</button>
39+
<button class="btn btn--compact" type="button" data-session-user-button="user3">User 3</button>
40+
<button class="btn btn--compact" type="button" data-session-user-button="admin">Admin</button>
41+
</div>
42+
<p class="status" role="status" data-session-user-summary>Session user: User 1</p>
43+
</div>
44+
</details>
3245
<details class="vertical-accordion" open>
3346
<summary>Scope</summary>
3447
<div class="accordion-body content-stack">
3548
<p>Current tool mock DB records are displayed as a read-only human-readable dump.</p>
3649
<p>Use the diagnostics panel to confirm table attribution, relationships, and table bleed checks.</p>
50+
<button class="btn btn--compact" type="button" data-admin-db-clear>Clear Mock DB</button>
3751
</div>
3852
</details>
3953
</div>

admin/db-viewer.js

Lines changed: 118 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,15 @@ import { createProjectJourneyMockRepository } from "../toolbox/project-journey/p
22
import { createAssetToolMockRepository } from "../toolbox/assets/assets-mock-repository.js";
33
import { createProjectWorkspacePaletteRepository } from "../toolbox/colors/palette-workspace-repository.js";
44
import {
5-
getAllPersistedMockDbTables,
5+
getAllPersistedMockDbSnapshot,
6+
clearMockDbTables,
7+
getMockDbSessionUser,
8+
getMockDbSessionUsers,
69
getStandaloneMockDbTables,
7-
} from "../src/shared/mock-db/mock-db-store.js";
10+
setMockDbSessionUser,
11+
} from "../src/engine/persistence/mock-db-store.js";
812

9-
const AUDIT_FIELDS = ["createdAt", "updatedAt", "createdByType", "updatedByType"];
13+
const AUDIT_FIELDS = ["createdAt", "updatedAt", "createdBy", "updatedBy"];
1014
const TOOL_GROUP_ORDER = ["project-journey", "palette", "asset"];
1115
const TOOL_GROUP_LABELS = Object.freeze({
1216
asset: "Asset",
@@ -17,22 +21,29 @@ const STANDALONE_TABLE_LABELS = Object.freeze({
1721
users: "Users",
1822
actors: "Actors",
1923
});
20-
const AUDIT_REQUIRED_TABLES = new Set(["users", "actors"]);
2124

2225
class AdminDbViewer {
2326
constructor(documentRef = document) {
2427
this.document = documentRef;
25-
this.projectJourneyRepository = createProjectJourneyMockRepository();
26-
this.paletteRepository = createProjectWorkspacePaletteRepository();
27-
this.assetRepository = createAssetToolMockRepository();
28+
this.createRepositories();
2829
this.activeFilter = "all";
30+
this.clearButton = documentRef.querySelector("[data-admin-db-clear]");
2931
this.filterRoot = documentRef.querySelector("[data-admin-db-filters]");
3032
this.status = documentRef.querySelector("[data-admin-db-status]");
3133
this.diagnostics = documentRef.querySelector("[data-admin-db-diagnostics]");
3234
this.relationships = documentRef.querySelector("[data-admin-db-relationships]");
35+
this.sessionSummary = documentRef.querySelector("[data-session-user-summary]");
36+
this.sessionHeader = documentRef.querySelector("[data-session-user-header]");
37+
this.sessionUserControls = documentRef.querySelector("[data-session-user-controls]");
3338
this.tablesRoot = documentRef.querySelector("[data-admin-db-tables]");
3439
}
3540

41+
createRepositories() {
42+
this.projectJourneyRepository = createProjectJourneyMockRepository();
43+
this.paletteRepository = createProjectWorkspacePaletteRepository();
44+
this.assetRepository = createAssetToolMockRepository();
45+
}
46+
3647
start() {
3748
this.projectJourneyRepository.openProject("demo-project");
3849
this.render();
@@ -44,6 +55,26 @@ class AdminDbViewer {
4455
this.activeFilter = button.dataset.adminDbFilter || "all";
4556
this.render();
4657
});
58+
this.sessionUserControls?.addEventListener("click", (event) => {
59+
const button = event.target.closest("[data-session-user-button]");
60+
if (!button) {
61+
return;
62+
}
63+
setMockDbSessionUser(button.dataset.sessionUserButton || "user1");
64+
this.createRepositories();
65+
this.projectJourneyRepository.openProject("demo-project");
66+
this.render();
67+
});
68+
this.clearButton?.addEventListener("click", () => {
69+
if (!window.confirm("Clear all shared Mock DB records?")) {
70+
return;
71+
}
72+
clearMockDbTables();
73+
this.createRepositories();
74+
this.projectJourneyRepository.openProject("demo-project");
75+
this.activeFilter = "all";
76+
this.render();
77+
});
4778
}
4879

4980
createElement(tagName, options = {}) {
@@ -96,40 +127,40 @@ class AdminDbViewer {
96127
}
97128

98129
collectSnapshot() {
99-
const projectJourneyTables = this.projectJourneyRepository.getTables();
100-
const paletteTables = this.paletteRepository.getTables();
101-
const assetTables = this.assetRepository.getTables();
102-
const standaloneTables = getStandaloneMockDbTables();
103-
const persistedTables = getAllPersistedMockDbTables();
104-
const tables = {
105-
...persistedTables,
106-
...projectJourneyTables,
107-
...paletteTables,
108-
...assetTables,
109-
...standaloneTables,
110-
};
130+
this.projectJourneyRepository.getTables();
131+
this.paletteRepository.getTables();
132+
this.assetRepository.getTables();
133+
getStandaloneMockDbTables();
134+
const snapshot = getAllPersistedMockDbSnapshot();
135+
const tables = snapshot.tables;
136+
const owners = snapshot.owners || {};
137+
const tableNamesForOwner = (ownerId) => Object.keys(tables)
138+
.filter((tableName) => owners[tableName] === ownerId)
139+
.sort();
140+
const toolGroupIds = TOOL_GROUP_ORDER.filter((id) => tableNamesForOwner(id).length > 0);
141+
const toolOwnedTables = new Set(toolGroupIds.flatMap(tableNamesForOwner));
142+
const unownedTableNames = Object.keys(tables)
143+
.filter((tableName) => !toolOwnedTables.has(tableName));
144+
const standaloneTableNames = [
145+
...Object.keys(STANDALONE_TABLE_LABELS).filter((tableName) => unownedTableNames.includes(tableName)),
146+
...unownedTableNames.filter((tableName) => !Object.hasOwn(STANDALONE_TABLE_LABELS, tableName)).sort(),
147+
];
111148
const groups = [
112149
{
113150
id: "all",
114151
label: "All",
115152
tableNames: Object.keys(tables).sort(),
116153
type: "all",
117154
},
118-
...TOOL_GROUP_ORDER.map((id) => ({
155+
...toolGroupIds.map((id) => ({
119156
id,
120157
label: TOOL_GROUP_LABELS[id],
121-
tableNames: Object.keys(
122-
id === "project-journey"
123-
? projectJourneyTables
124-
: id === "palette"
125-
? paletteTables
126-
: assetTables,
127-
).sort(),
158+
tableNames: tableNamesForOwner(id),
128159
type: "tool",
129160
})),
130-
...Object.entries(STANDALONE_TABLE_LABELS).map(([tableName, label]) => ({
161+
...standaloneTableNames.map((tableName) => ({
131162
id: tableName,
132-
label,
163+
label: STANDALONE_TABLE_LABELS[tableName] || tableName,
133164
tableNames: [tableName],
134165
type: "table",
135166
})),
@@ -165,18 +196,35 @@ class AdminDbViewer {
165196
});
166197
table.setAttribute("aria-label", `${tableName} records`);
167198

168-
const fields = this.tableFields(records);
199+
const fields = this.tableFields(records).filter((field) => field !== "key");
169200
const head = this.createElement("thead");
170201
const headerRow = this.createElement("tr");
202+
headerRow.append(this.createElement("th", { text: "Key" }));
171203
fields.forEach((field) => {
172204
headerRow.append(this.createElement("th", { text: field }));
173205
});
174206
head.append(headerRow);
175207

176208
const tableBody = this.createElement("tbody");
209+
if (!records.length) {
210+
const row = this.createElement("tr");
211+
const cell = this.createElement("td", {
212+
text: "No records in this table. Add records from its tool or clear browser mock DB storage to reseed defaults.",
213+
});
214+
cell.colSpan = Math.max(1, fields.length + 1);
215+
row.append(cell);
216+
tableBody.append(row);
217+
}
177218
records.forEach((record) => {
178219
const row = this.createElement("tr");
179220
row.dataset.adminDbRecord = this.recordId(record);
221+
const keyCell = this.createElement("td", {
222+
text: this.isUlidKey(record.key) ? this.formatKeyValue(record.key) : this.formatValue(record.key),
223+
});
224+
if (this.isUlidKey(record.key)) {
225+
keyCell.title = String(record.key);
226+
}
227+
row.append(keyCell);
180228
fields.forEach((field) => {
181229
const value = record[field];
182230
const cell = this.createElement("td", {
@@ -223,12 +271,29 @@ class AdminDbViewer {
223271
});
224272
}
225273

274+
renderSessionUser() {
275+
const sessionUser = getMockDbSessionUser();
276+
if (this.sessionHeader) {
277+
this.sessionHeader.textContent = `Session user: ${sessionUser.label}`;
278+
}
279+
if (this.sessionSummary) {
280+
this.sessionSummary.textContent = `Selected session user: ${sessionUser.label}.`;
281+
}
282+
this.document.querySelectorAll("[data-session-user-button]").forEach((button) => {
283+
const selected = button.dataset.sessionUserButton === sessionUser.id;
284+
button.classList.toggle("primary", selected);
285+
button.setAttribute("aria-pressed", String(selected));
286+
if (selected) {
287+
button.setAttribute("aria-current", "true");
288+
} else {
289+
button.removeAttribute("aria-current");
290+
}
291+
});
292+
}
293+
226294
auditFindings(tables) {
227295
const findings = [];
228296
Object.entries(tables).forEach(([tableName, records]) => {
229-
if (!tableName.startsWith("project_journey_") && !AUDIT_REQUIRED_TABLES.has(tableName)) {
230-
return;
231-
}
232297
records.forEach((record) => {
233298
AUDIT_FIELDS.forEach((field) => {
234299
if (!Object.hasOwn(record, field)) {
@@ -244,6 +309,7 @@ class AdminDbViewer {
244309
const noteTypeKeys = new Set((tables.project_journey_note_types || []).map((type) => type.key));
245310
const noteKeys = new Set((tables.project_journey_notes || []).map((note) => note.key));
246311
const userKeys = new Set((tables.users || []).map((user) => user.key));
312+
const actorKeys = new Set((tables.actors || []).map((actor) => actor.key));
247313
const actorUserKeys = new Set((tables.actors || []).map((actor) => actor.userKey));
248314
const activeTemplateKeys = new Set(
249315
(tables.project_journey_templates || [])
@@ -254,7 +320,23 @@ class AdminDbViewer {
254320
const paletteColorKeys = new Set((tables.palette_colors || []).map((row) => `${row.projectId}:${row.symbol}`));
255321
const assetIds = new Set((tables.asset_library_items || []).map((asset) => asset.id));
256322
const assetStorageIds = new Set((tables.asset_storage_objects || []).map((object) => object.id));
323+
const allRecords = Object.entries(tables).flatMap(([tableName, records]) =>
324+
records.map((record) => ({
325+
...record,
326+
tableName,
327+
})),
328+
);
257329
return [
330+
{
331+
name: "*.createdBy -> actors.key",
332+
checked: allRecords.length,
333+
missing: allRecords.filter((record) => !actorKeys.has(record.createdBy)),
334+
},
335+
{
336+
name: "*.updatedBy -> actors.key",
337+
checked: allRecords.length,
338+
missing: allRecords.filter((record) => !actorKeys.has(record.updatedBy)),
339+
},
258340
{
259341
name: "project_journey_notes.typeKey -> project_journey_note_types.key",
260342
checked: (tables.project_journey_notes || []).length,
@@ -363,7 +445,7 @@ class AdminDbViewer {
363445
const staleFindings = this.staleDisplayFindings(tables, groups);
364446
const auditSummary = auditFindings.length
365447
? auditFindings
366-
: ["All current mock DB tables include createdAt, updatedAt, createdByType, and updatedByType where required by the owning mock model."];
448+
: ["All current mock DB tables include createdAt, updatedAt, createdBy, and updatedBy."];
367449
const bleedSummary = bleedFindings.length ? bleedFindings : ["No table bleed detected."];
368450
this.diagnostics.append(
369451
this.renderList(auditSummary, "adminDbAuditFindings"),
@@ -376,7 +458,7 @@ class AdminDbViewer {
376458
this.relationships.replaceChildren();
377459
const relationshipRows = this.relationshipsForTables(tables);
378460
const missingLinks = relationshipRows.flatMap((relationship) =>
379-
relationship.missing.map((record) => `${relationship.name} missing for ${this.recordId(record)}.`),
461+
relationship.missing.map((record) => `${relationship.name} missing for ${record.tableName ? `${record.tableName}.` : ""}${this.recordId(record)}.`),
380462
);
381463
const relationshipList = this.createElement("ul");
382464
relationshipList.dataset.adminDbRelationshipSummary = "";
@@ -403,6 +485,7 @@ class AdminDbViewer {
403485
.map((tableName) => [tableName, snapshot.tables[tableName]]),
404486
);
405487
this.renderFilters(snapshot.groups);
488+
this.renderSessionUser();
406489
this.renderDiagnostics(snapshot.tables, snapshot.groups);
407490
this.renderRelationships(snapshot.tables);
408491
this.renderTables(visibleTables);

assets/theme-v2/js/tool-display-mode.js

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -66,17 +66,6 @@
6666
displayMode.appendChild(body);
6767
slot.replaceWith(displayMode);
6868

69-
function roleAwareHref(targetHref) {
70-
const role = new URLSearchParams(window.location.search).get("role");
71-
if (!role || !targetHref) {
72-
return targetHref;
73-
}
74-
75-
const targetUrl = new URL(targetHref, window.location.origin + "/");
76-
targetUrl.searchParams.set("role", role);
77-
return targetUrl.pathname.replace(/^\/+/, "") + targetUrl.search + targetUrl.hash;
78-
}
79-
8069
function createNavigationControl(direction, target) {
8170
const controlLabel = direction === "previous" ? "Previous" : "Next";
8271
const dataAttribute = direction === "previous" ? "toolNavPrevious" : "toolNavNext";
@@ -90,7 +79,7 @@
9079
}
9180

9281
const link = document.createElement("a");
93-
link.href = roleAwareHref(target.href);
82+
link.href = target.href;
9483
link.dataset[dataAttribute] = target.kind;
9584
if (target.group) {
9685
link.dataset.toolNavGroup = target.group;

docs_build/dev/reports/coverage_changed_js_guardrail.txt

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,12 @@ Missing changed runtime JS files are WARN, not FAIL.
66
Source: Playwright/Chromium built-in V8 coverage from the active Playwright run.
77

88
Changed runtime JS files considered:
9-
(84%) toolbox/toolRegistry.js - executed lines 1788/1788; executed functions 31/37
10-
(93%) toolbox/project-journey/project-journey-mock-repository.js - executed lines 805/805; executed functions 68/73
11-
(97%) toolbox/project-journey/project-journey.js - executed lines 631/631; executed functions 64/66
9+
(70%) toolbox/colors/palette-workspace-repository.js - executed lines 1315/1315; executed functions 89/127
10+
(74%) toolbox/tools-page-accordions.js - executed lines 764/764; executed functions 53/72
11+
(81%) toolbox/assets/assets-mock-repository.js - executed lines 919/919; executed functions 61/75
12+
(96%) src/engine/persistence/mock-db-store.js - executed lines 368/368; executed functions 45/47
13+
(96%) toolbox/project-journey/project-journey.js - executed lines 948/948; executed functions 100/104
14+
(97%) toolbox/project-journey/project-journey-mock-repository.js - executed lines 1055/1055; executed functions 94/97
1215

1316
Guardrail warnings:
1417
(100%) none - no changed runtime JS coverage warnings

docs_build/dev/reports/dependency_gating_report.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Dependency Gating Report
22

3-
Generated: 2026-06-06T19:55:52.241Z
3+
Generated: 2026-06-07T00:02:22.171Z
44
Status: PASS
55

66
## Gate Order

docs_build/dev/reports/dependency_hydration_reuse_report.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Dependency Hydration Reuse Report
22

3-
Generated: 2026-06-06T19:55:52.242Z
3+
Generated: 2026-06-07T00:02:22.171Z
44
Status: PASS
55

66
## Summary

docs_build/dev/reports/execution_graph_reuse_report.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Execution Graph Reuse Report
22

3-
Generated: 2026-06-06T19:55:52.242Z
3+
Generated: 2026-06-07T00:02:22.172Z
44
Status: PASS
55

66
## Summary

docs_build/dev/reports/failure_fingerprint_report.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Failure Fingerprint Report
22

3-
Generated: 2026-06-06T19:55:52.242Z
3+
Generated: 2026-06-07T00:02:22.172Z
44
Status: PASS
55

66
## Summary

docs_build/dev/reports/incremental_validation_report.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Incremental Validation Report
22

3-
Generated: 2026-06-06T19:55:52.242Z
3+
Generated: 2026-06-07T00:02:22.172Z
44
Status: PASS
55

66
## Reuse Summary

docs_build/dev/reports/lane_compilation_report.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Lane Compilation Report
22

3-
Generated: 2026-06-06T19:55:52.241Z
3+
Generated: 2026-06-07T00:02:22.171Z
44
Status: PASS
55

66
## Lane Graph

0 commit comments

Comments
 (0)