CMMS fork: AI bridge reads the project from IndexedDB, not the globals

Console showed buildProjectView reporting "4 photos" but NO
buildProjectImages line at all - it was hitting the silent
`!bomImages.length` early return. The studio.js bomData/bomImages
globals get transiently emptied while a project loads / auto-stitches,
and the get-buffer handler's `await` between buildProjectView and
buildProjectImages is enough of a yield to catch that window.

- buildProjectView + buildProjectImages + applyProjectPatch now call
  db.getComponents/getImages/getNets(currentBomId) directly instead of
  reading bomData/bomImages/bomList.
- buildProjectImages traces on entry ("start (board: ...)") and after
  the DB read ("N image record(s)") so an empty result is never silent.
This commit is contained in:
Stuart 2026-09-03 15:35:16 +10:00
commit 514551c9f2

View file

@ -131,6 +131,19 @@
return ready();
}
// Read the project straight from IndexedDB rather than the studio.js globals
// (bomData/bomImages) - those get transiently reset while a project loads or
// auto-stitches, which was leaving the AI bridge with an empty image list
// even though buildProjectView's own snapshot showed photos.
async function readBoard() {
const [components, imageRecs, allNets] = await Promise.all([
db.getComponents(currentBomId),
db.getImages(currentBomId),
db.getNets(),
]);
return { components, imageRecs, nets: allNets.filter(n => n.projectId === currentBomId) };
}
async function buildProjectView() {
// The widget waits 6s for a pcb reply (ai-assistant-widget.js); stay
// under that so a "still initialising" answer still gets through.
@ -142,15 +155,16 @@
}
const dev = deviceList.find(d => d.id === currentDeviceId);
const board = bomList.find(b => b.id === currentBomId);
const allNets = currentBomId ? await db.getNets() : [];
const nets = allNets.filter(n => n.projectId === currentBomId);
const { components, imageRecs, nets } = currentBomId
? await readBoard()
: { components: [], imageRecs: [], nets: [] };
const view = {
device: dev ? dev.name : null,
board: board ? board.name : null,
note: currentBomId ? undefined : 'No board is open yet - the user needs to create or select one before components/nets can be added.',
image_count: bomImages.length,
images: bomImages.map(i => ({ id: i.id, name: i.name || '' })),
components: bomData.map(c => ({
image_count: imageRecs.length,
images: imageRecs.map(i => ({ id: i.id, name: i.name || '' })),
components: components.map(c => ({
label: c.label, value: c.value || '', desc: c.desc || '',
x: Math.round(c.x), y: Math.round(c.y),
})),
@ -194,9 +208,12 @@
}
async function buildProjectImages() {
if (!currentBomId || !bomImages.length) return [];
trace('buildProjectImages: start (board:', currentBomId || '(none)', ')');
if (!currentBomId) return [];
const imgs = await db.getImages(currentBomId);
trace('buildProjectImages:', imgs.length, 'image record(s) in the DB');
const out = [];
for (const img of bomImages.slice(0, 6)) {
for (const img of imgs.slice(0, 6)) {
let blob = img.blob;
if (!(blob instanceof Blob)) {
try { blob = new Blob([blob], { type: (img.type || 'image/jpeg') }); }
@ -226,7 +243,7 @@
data_base64: b64,
});
}
trace('buildProjectImages:', out.length, 'of', bomImages.length, 'photos encoded');
trace('buildProjectImages:', out.length, 'of', imgs.length, 'photos encoded, total b64', out.reduce((n, i) => n + i.data_base64.length, 0));
return out;
}
@ -234,8 +251,10 @@
const patch = JSON.parse(patchJson);
if (!currentBomId) { status('Select or create a board first', true); return; }
const byLabel = l => bomData.find(c => (c.label || '').toUpperCase() === l.toUpperCase());
const fallbackImg = currentImgId || (bomImages[0] && bomImages[0].id) || null;
const comps = await db.getComponents(currentBomId);
const imgRecs = await db.getImages(currentBomId);
const byLabel = l => comps.find(c => (c.label || '').toUpperCase() === l.toUpperCase());
const fallbackImg = currentImgId || (imgRecs[0] && imgRecs[0].id) || null;
let spread = 40;
for (const c of (patch.components || [])) {