diff --git a/backend/extension-src/background.js b/backend/extension-src/background.js index fc656f1..6c872fd 100644 --- a/backend/extension-src/background.js +++ b/backend/extension-src/background.js @@ -103,8 +103,11 @@ async function pushBooking(bookingId, deviceId) { body: JSON.stringify({ booking_id: bookingId, device_id: deviceId }), }); const body = await res.json().catch(() => ({})); - if (!res.ok) return { ok: false, error: body.error || `HTTP ${res.status}` }; - return { ok: true }; + // status included so callers can reliably tell "device busy" (409) apart + // from other failures — used by content-script.js's default-tablet + // fast-path to decide whether to fall back to the device picker. + if (!res.ok) return { ok: false, status: res.status, error: body.error || `HTTP ${res.status}` }; + return { ok: true, status: res.status }; } catch (err) { return { ok: false, error: err.message }; } diff --git a/backend/extension-src/content-script.js b/backend/extension-src/content-script.js index b9538a2..10fd160 100644 --- a/backend/extension-src/content-script.js +++ b/backend/extension-src/content-script.js @@ -89,14 +89,68 @@ function makeSendButton(bookingId, guestName, variant = 'block') { btn.addEventListener('click', e => { e.preventDefault(); e.stopPropagation(); - togglePanel(btn, bookingId, guestName, variant); + handleSendClick(btn, bookingId, guestName, variant); }); return btn; } +// Injected buttons skip asking which tablet entirely when a default is +// configured for this machine (see options.js) — push straight to it. Only +// falls back to the full device-picker panel if that specific push comes +// back busy (409), or if no default is configured at all — see +// project_checkin memory's extension notes, this was an explicit UX request +// ("injected buttons don't need to ask which tablet, just use/send to +// default unless busy"). The popup is deliberately different — it always +// shows the picker, just pre-selected to the default (see popup.js) — since +// opening the popup is already a more deliberate action than a one-click +// inline button. +async function handleSendClick(anchorBtn, bookingId, guestName, variant) { + const { defaultDeviceId } = await chrome.storage.local.get('defaultDeviceId'); + + if (defaultDeviceId) { + const status = showInlineStatus(anchorBtn, variant, 'Sending…'); + const result = await chrome.runtime.sendMessage({ + action: 'tcpPush', bookingId, deviceId: defaultDeviceId, + }); + if (result?.ok) { + status.textContent = 'Sent — the tablet should navigate now.'; + status.style.color = '#166534'; + return; + } + if (result?.status === 409) { + status.remove(); + // fall through to the picker below, with a note explaining why it's + // asking despite a default being configured + await togglePanel(anchorBtn, bookingId, guestName, variant, 'Default tablet is busy — choose another:'); + return; + } + status.textContent = `Failed: ${result?.error || 'unknown error'}`; + status.style.color = '#dc2626'; + return; + } + + await togglePanel(anchorBtn, bookingId, guestName, variant); +} + +// Small text-only status line for the default-tablet fast path — no +// dropdown, nothing to pick, just feedback on what happened. +function showInlineStatus(anchorBtn, variant, text) { + if (openPanel) { openPanel.remove(); openPanel = null; } + const el = document.createElement('div'); + const width = variant === 'block' ? 'width: 100%;' : ''; + el.style.cssText = ` + display: block; margin-top: 6px; ${width} font-size: 12px; color: #64748b; + font-family: -apple-system, BlinkMacSystemFont, sans-serif; + `; + el.textContent = text; + anchorBtn.insertAdjacentElement('afterend', el); + openPanel = el; + return el; +} + let openPanel = null; -async function togglePanel(anchorBtn, bookingId, guestName, variant = 'block') { +async function togglePanel(anchorBtn, bookingId, guestName, variant = 'block', note = '') { if (openPanel) { openPanel.remove(); openPanel = null; } const panel = document.createElement('div'); @@ -111,7 +165,7 @@ async function togglePanel(anchorBtn, bookingId, guestName, variant = 'block') { font-family: -apple-system, BlinkMacSystemFont, sans-serif; font-size: 12px; color: #1e293b; `; - panel.textContent = 'Loading tablets…'; + panel.textContent = note ? `${note} loading…` : 'Loading tablets…'; anchorBtn.insertAdjacentElement('afterend', panel); openPanel = panel; @@ -151,6 +205,12 @@ async function togglePanel(anchorBtn, bookingId, guestName, variant = 'block') { }); panel.innerHTML = ''; + if (note) { + const noteEl = document.createElement('div'); + noteEl.textContent = note; + noteEl.style.cssText = 'margin-bottom: 6px; color: #b45309; font-weight: 600;'; + panel.appendChild(noteEl); + } panel.appendChild(select); panel.appendChild(sendBtn); } diff --git a/backend/extension-src/options/options.html b/backend/extension-src/options/options.html index 6ff48e2..b9d9f17 100644 --- a/backend/extension-src/options/options.html +++ b/backend/extension-src/options/options.html @@ -28,6 +28,16 @@
+ +