From e2c19f5a98fbdbf99e751539946f9953be57ed65 Mon Sep 17 00:00:00 2001 From: James Reategui Date: Thu, 9 Jul 2026 10:56:50 -0400 Subject: [PATCH] fix(web): quick-view header layout + VIN row with copy, es wallet label MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The real-time pill shared the title row with the vehicle name, and long Year Make Model titles ran underneath it (flex containers don't ellipsize). The pill moves down beside the Token # line — its own row, so it can never cover the title — and the title span now truncates properly with a hover tooltip for the full name. Below Token #, the VIN renders in the same style with a click-to-copy button (icon flashes a check; resets on vehicle switch). The old VIN pill next to the license plate is dropped as redundant. Also: the Spanish "Add by wallet address" label now reads "Agregar por dirección de Wallet 0x" (requested wording). Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01UxoaxTXSLqUwkYfewdwGBM --- web/src/elements/vehicle-quick-view.ts | 133 +++++++++++++++++-------- web/src/generated/locales/es.ts | 2 +- web/xliff/es.xlf | 2 +- 3 files changed, 92 insertions(+), 45 deletions(-) diff --git a/web/src/elements/vehicle-quick-view.ts b/web/src/elements/vehicle-quick-view.ts index 3fd910a..de8f539 100644 --- a/web/src/elements/vehicle-quick-view.ts +++ b/web/src/elements/vehicle-quick-view.ts @@ -33,6 +33,8 @@ export class VehicleQuickView extends LitElement { /** Real-time: when on, re-poll this vehicle's signals + location on an * interval. Off by default, reset whenever the selected vehicle changes. */ @state() private realtimeOn = false; + @state() private vinCopied = false; + private vinCopiedTimer: number | null = null; private rtTimer: number | null = null; private static readonly REALTIME_INTERVAL_MS = 20 * 1000; @@ -60,9 +62,10 @@ export class VehicleQuickView extends LitElement { willUpdate(changed: Map) { if (changed.has('vehicle')) { - // Switching (or closing) the vehicle resets real-time and any - // selected trip route — the toggle is opt-in per vehicle. + // Switching (or closing) the vehicle resets real-time, the VIN-copied + // flash, and any selected trip route — the toggle is opt-in per vehicle. this.stopRealtime(); + this.vinCopied = false; if (this.vehicle) { this.clearTripSelection(); void this.loadSignals(); @@ -99,6 +102,18 @@ export class VehicleQuickView extends LitElement { } } + /** Copy the VIN to the clipboard, flashing the icon as feedback. */ + private async copyVin(vin: string) { + try { + await navigator.clipboard.writeText(vin); + } catch { + return; // clipboard unavailable (permissions/insecure context) — no feedback + } + this.vinCopied = true; + if (this.vinCopiedTimer) window.clearTimeout(this.vinCopiedTimer); + this.vinCopiedTimer = window.setTimeout(() => { this.vinCopied = false; }, 1500); + } + /** Toggle real-time updates for this vehicle (off by default). */ private toggleRealtime() { if (this.realtimeOn) { @@ -271,7 +286,7 @@ export class VehicleQuickView extends LitElement { gap: 12px; padding: 16px 16px 12px; } - header .identity { min-width: 0; } + header .identity { min-width: 0; flex: 1; } header h3 { display: flex; align-items: center; @@ -283,7 +298,15 @@ export class VehicleQuickView extends LitElement { text-overflow: ellipsis; white-space: nowrap; } - header h3 .favorite-star { color: #f5c84b; font-size: 18px; } + /* The span (not the flex h3) truncates, so a long title ellipsizes + instead of running under its neighbors. */ + header h3 .title-text { + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + header h3 .favorite-star { color: #f5c84b; font-size: 18px; flex-shrink: 0; } header h3 .status-dot { width: 10px; height: 10px; @@ -294,10 +317,42 @@ export class VehicleQuickView extends LitElement { .status-amber { background: #f5c84b; } .status-red { background: var(--error, #e57373); } header .sub { - margin-top: 4px; font: var(--type-body-sm); color: var(--on-surface-variant); } + /* Token # on the left, the real-time toggle on the right — its own + row so the pill can never sit on top of the title. */ + header .sub-row { + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; + margin-top: 4px; + } + header .vin-row { + display: flex; + align-items: center; + gap: 4px; + margin-top: 2px; + } + header .vin-row .vin-text { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + .copy-btn { + background: none; + border: none; + color: var(--on-surface-variant); + padding: 2px; + border-radius: var(--radius-sm); + cursor: pointer; + display: inline-flex; + flex-shrink: 0; + transition: color 0.15s ease; + } + .copy-btn:hover { color: var(--primary); } + .copy-btn .material-symbols-outlined { font-size: 14px; } .identifiers { display: flex; flex-wrap: wrap; @@ -319,13 +374,8 @@ export class VehicleQuickView extends LitElement { white-space: nowrap; } .plate-pill .material-symbols-outlined { font-size: 14px; } - .vin-pill { - font-family: var(--font-mono); - font-size: 11px; - color: var(--on-surface-variant); - white-space: nowrap; - } .close-btn { + margin-top: -2px; background: none; border: none; color: var(--on-surface-variant); @@ -338,12 +388,6 @@ export class VehicleQuickView extends LitElement { } .close-btn:hover { background: var(--surface-container-high); color: var(--primary); } - .header-actions { - display: flex; - align-items: center; - gap: 6px; - flex-shrink: 0; - } .rt-btn { display: inline-flex; align-items: center; @@ -603,37 +647,40 @@ export class VehicleQuickView extends LitElement {

${v.isFavorite ? html`star` : ''} - ${v.title} + ${v.title}

-
${v.seenAt}
- -
- - +
+
${v.seenAt}
+ +
+ ${v.vin ? html` +
+ ${v.vin} + +
+ ` : nothing}
+ - ${v.licensePlate || v.vin ? html` + ${v.licensePlate ? html`
- ${v.licensePlate ? html` - - directions_car${v.licensePlate} - - ` : nothing} - ${v.vin ? html` - ${v.vin} - ` : nothing} + + directions_car${v.licensePlate} +
` : nothing} diff --git a/web/src/generated/locales/es.ts b/web/src/generated/locales/es.ts index 701de06..4dbfb07 100644 --- a/web/src/generated/locales/es.ts +++ b/web/src/generated/locales/es.ts @@ -252,7 +252,7 @@ 'sb3be08d826f659c3': `Aún no hay grupos. Cree uno para organizar su flota.`, 'sb52f6b2e5a4bc127': `Faltantes`, 'sb64127e4e4f0b575': `Cerrar sesión`, -'sb730f38dec076dc2': `Agregar por dirección de cartera`, +'sb730f38dec076dc2': `Agregar por dirección de Wallet 0x`, 'sb82063ff4133ae44': `Cargando vehículos asignados…`, 'sb85774dc5d18ff0f': `Confirmar`, 'sb971debc37bf0b54': `Propietario`, diff --git a/web/xliff/es.xlf b/web/xliff/es.xlf index 367ccd6..27aa527 100644 --- a/web/xliff/es.xlf +++ b/web/xliff/es.xlf @@ -838,7 +838,7 @@ Add by wallet address - Agregar por dirección de cartera + Agregar por dirección de Wallet 0x Resend