@homebridge-plugins/homebridge-eufy-security 5.0.0-beta.2 → 5.0.0-beta.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/config.schema.json +24 -0
  2. package/dist/account/temporary-authentication.js +3 -1
  3. package/dist/account/temporary-authentication.js.map +1 -1
  4. package/dist/configuration.js +36 -2
  5. package/dist/configuration.js.map +1 -1
  6. package/dist/device/member-evidence.js +10 -3
  7. package/dist/device/member-evidence.js.map +1 -1
  8. package/dist/diagnostics.js +582 -83
  9. package/dist/diagnostics.js.map +1 -1
  10. package/dist/homekit/adapters/camera-streaming.js +54 -26
  11. package/dist/homekit/adapters/camera-streaming.js.map +1 -1
  12. package/dist/homekit/adapters/lock.js +36 -13
  13. package/dist/homekit/adapters/lock.js.map +1 -1
  14. package/dist/homekit/adapters/motion.js +1 -1
  15. package/dist/homekit/adapters/motion.js.map +1 -1
  16. package/dist/homekit/adapters/package-sensor.js +63 -0
  17. package/dist/homekit/adapters/package-sensor.js.map +1 -0
  18. package/dist/homekit/adapters/registry.js +2 -0
  19. package/dist/homekit/adapters/registry.js.map +1 -1
  20. package/dist/homekit/adapters/security-system.js +95 -44
  21. package/dist/homekit/adapters/security-system.js.map +1 -1
  22. package/dist/homekit/coverage-matrix.js +1 -3
  23. package/dist/homekit/coverage-matrix.js.map +1 -1
  24. package/dist/homekit/reconciler.js +6 -2
  25. package/dist/homekit/reconciler.js.map +1 -1
  26. package/dist/media/live-stream.js +98 -54
  27. package/dist/media/live-stream.js.map +1 -1
  28. package/dist/media/recording.js +7 -5
  29. package/dist/media/recording.js.map +1 -1
  30. package/dist/media/snapshot.js +4 -4
  31. package/dist/media/station-live-sessions.js +47 -43
  32. package/dist/media/station-live-sessions.js.map +1 -1
  33. package/dist/platform.js +25 -5
  34. package/dist/platform.js.map +1 -1
  35. package/dist/runtime/channel.js +6 -2
  36. package/dist/runtime/channel.js.map +1 -1
  37. package/dist/runtime/owner.js +3 -0
  38. package/dist/runtime/owner.js.map +1 -1
  39. package/dist/ui/dashboard.js +32 -14
  40. package/dist/ui/dashboard.js.map +1 -1
  41. package/dist/ui/server.js +27 -4
  42. package/dist/ui/server.js.map +1 -1
  43. package/homebridge-ui/public/app.css +353 -88
  44. package/homebridge-ui/public/i18n/en.json +108 -78
  45. package/homebridge-ui/public/i18n/fr.json +108 -78
  46. package/homebridge-ui/public/index.html +95 -106
  47. package/homebridge-ui/public/js/app.js +328 -345
  48. package/homebridge-ui/public/js/dashboard.js +139 -10
  49. package/homebridge-ui/public/js/profile-wizard.js +17 -54
  50. package/i18n/runtime/en.json +5 -3
  51. package/package.json +6 -6
@@ -274,7 +274,9 @@ h2 {
274
274
  padding: 8px 12px;
275
275
  }
276
276
 
277
- .diagnostics-actions {
277
+ .diagnostics-actions:not([hidden]) {
278
+ display: flex;
279
+ gap: 8px;
278
280
  margin-top: 4px;
279
281
  }
280
282
 
@@ -290,42 +292,25 @@ h2 {
290
292
  padding: clamp(16px, 4vw, 24px);
291
293
  }
292
294
 
293
- .diagnostics-question,
294
- .diagnostics-direct,
295
- .diagnostics-frequency,
296
- .diagnostics-match {
295
+ .diagnostics-question {
297
296
  display: grid;
298
297
  gap: 16px;
299
298
  }
300
299
 
301
- .diagnostics-question > strong,
302
- .diagnostics-frequency > strong,
303
- .diagnostics-match > strong {
304
- font-size: clamp(17px, 4vw, 21px);
305
- line-height: 1.35;
306
- letter-spacing: -0.01em;
307
- text-wrap: balance;
308
- }
309
-
310
- .diagnostics-match p {
311
- margin-bottom: 0;
312
- }
313
-
314
- .diagnostics-choice-actions {
315
- display: grid;
316
- grid-template-columns: repeat(2, minmax(0, 1fr));
317
- gap: 8px;
300
+ .diagnostics-question > h3,
301
+ .diagnostics-guidance > h3 {
302
+ margin: 0;
318
303
  }
319
304
 
320
- .diagnostics-choice-actions button {
321
- width: 100%;
322
- padding-inline: 16px;
305
+ .diagnostics-guidance > h3 {
306
+ font-size: inherit;
323
307
  }
324
308
 
325
- button.diagnostics-secondary-action {
326
- border: 1px solid var(--line-strong);
327
- background: transparent;
328
- color: var(--ink);
309
+ .diagnostics-question > h3 {
310
+ font-size: clamp(17px, 4vw, 21px);
311
+ line-height: 1.35;
312
+ letter-spacing: -0.01em;
313
+ text-wrap: balance;
329
314
  }
330
315
 
331
316
  button.diagnostics-quiet-action {
@@ -366,55 +351,159 @@ button.diagnostics-quiet-action {
366
351
  text-transform: uppercase;
367
352
  }
368
353
 
369
- .diagnostics-actions button {
370
- width: 100%;
354
+ .diagnostics-actions [data-diagnostics-reproduction] {
355
+ flex: 1 1 auto;
371
356
  }
372
357
 
358
+ .diagnostics-result h2,
373
359
  .diagnostics-result p {
374
360
  margin-bottom: 0;
375
361
  }
376
362
 
377
- .diagnostics-privacy {
363
+ .diagnostics-note {
364
+ margin-bottom: 0;
378
365
  color: var(--muted);
379
366
  font-size: 12px;
380
367
  }
381
368
 
382
- .diagnostics-result {
369
+ .diagnostics-result[open] {
383
370
  display: grid;
371
+ max-width: 560px;
384
372
  gap: 8px;
385
373
  }
386
374
 
387
- .diagnostics-result a {
388
- display: inline-block;
389
- margin-top: 8px;
390
- color: var(--accent-ink);
375
+ /*
376
+ * The download is a button because it acts here; the two ways to report are anchors because they navigate to
377
+ * GitHub, side by side because either one finishes the handoff.
378
+ */
379
+ .diagnostics-result [data-diagnostics-export],
380
+ .diagnostics-handoff a {
381
+ display: flex;
382
+ min-height: 44px;
383
+ align-items: center;
384
+ justify-content: center;
385
+ border-radius: var(--radius-control);
386
+ padding: 8px 12px;
391
387
  font-weight: 600;
388
+ text-align: center;
389
+ text-decoration: none;
392
390
  }
393
391
 
394
- .diagnostics-result button {
395
- width: 100%;
392
+ /*
393
+ * The dialog sits in the top layer, which adds nothing to the page height Homebridge sizes its iframe by, so the
394
+ * stage keeps room for it rather than leaving it to scroll inside a sliver.
395
+ */
396
+ .diagnostics-stage:has(> .diagnostics-result[open]) {
397
+ min-height: 320px;
396
398
  }
397
399
 
398
- .diagnostics-manifest {
399
- margin-top: 12px;
400
- border-block: 1px solid var(--line);
401
- padding-block: 8px;
402
- font-size: 12px;
403
- font-variant-numeric: tabular-nums;
400
+ .diagnostics-handoff:not([hidden]) {
401
+ display: grid;
402
+ grid-template-columns: 1fr 1fr;
403
+ gap: 8px;
404
+ margin-top: 8px;
404
405
  }
405
406
 
406
- .diagnostics-manifest ul {
407
- margin: 8px 0;
408
- padding-left: 20px;
407
+ .diagnostics-handoff [data-diagnostics-issue] {
408
+ background: var(--accent);
409
+ color: var(--on-accent);
409
410
  }
410
411
 
411
- .diagnostics-review-confirm {
412
- display: flex;
413
- align-items: flex-start;
412
+ .diagnostics-handoff [data-diagnostics-existing-issue] {
413
+ border: 1px solid var(--line-strong);
414
+ color: var(--ink);
415
+ }
416
+
417
+ .diagnostics-result [data-diagnostics-result-status]:empty {
418
+ display: none;
419
+ }
420
+
421
+ .diagnostics-result [data-diagnostics-download-again],
422
+ .diagnostics-result [data-diagnostics-start-another] {
423
+ justify-self: center;
424
+ }
425
+
426
+ /*
427
+ * The opening screen: every area at once, three to a row, each label over its one-line summary.
428
+ *
429
+ * Each tile carries a Material Symbols outline glyph (Apache-2.0) as a mask rather than an element. A mask lets
430
+ * the glyph take the tile's own colour in every state instead of being baked into an image. The data URI is inline, so
431
+ * the panel renders the same on a host with no route to the internet.
432
+ */
433
+ .diagnostics-tiles {
434
+ display: grid;
435
+ grid-template-columns: repeat(3, minmax(0, 1fr));
414
436
  gap: 8px;
415
437
  margin-top: 12px;
438
+ }
439
+
440
+ .diagnostics-tiles button {
441
+ display: flex;
442
+ min-height: 88px;
443
+ flex-direction: column;
444
+ align-items: center;
445
+ justify-content: center;
446
+ gap: 8px;
447
+ padding: 10px 6px;
448
+ border: 1px solid var(--line-strong);
449
+ background: transparent;
450
+ color: var(--ink);
416
451
  font-size: 12px;
417
- cursor: pointer;
452
+ font-weight: 600;
453
+ line-height: 1.25;
454
+ overflow-wrap: break-word;
455
+ text-align: center;
456
+ }
457
+
458
+ .diagnostics-tiles button::before {
459
+ width: 26px;
460
+ height: 26px;
461
+ flex: none;
462
+ background: currentColor;
463
+ content: '';
464
+ mask-position: center;
465
+ mask-repeat: no-repeat;
466
+ mask-size: contain;
467
+ }
468
+
469
+ .diagnostics-tiles small {
470
+ color: var(--muted);
471
+ font-size: 11px;
472
+ font-weight: 400;
473
+ }
474
+
475
+ .diagnostics-tiles button:hover:not(:disabled) {
476
+ border-color: var(--accent);
477
+ background: var(--accent-soft);
478
+ color: var(--accent-ink);
479
+ }
480
+
481
+ .diagnostics-tiles [data-diagnostics-tile='startup-authentication']::before {
482
+ mask-image: url('data:image/svg+xml,%3Csvg%20xmlns=%22http://www.w3.org/2000/svg%22%20viewBox=%220%20-960%20960%20960%22%3E%3Cpath%20d=%22M480-120v-80h280v-560H480v-80h280q33%200%2056.5%2023.5T840-760v560q0%2033-23.5%2056.5T760-120H480Zm-80-160-55-58%20102-102H120v-80h327L345-622l55-58%20200%20200-200%20200Z%22/%3E%3C/svg%3E');
483
+ }
484
+
485
+ .diagnostics-tiles [data-diagnostics-tile='device-representation']::before {
486
+ mask-image: url('data:image/svg+xml,%3Csvg%20xmlns=%22http://www.w3.org/2000/svg%22%20viewBox=%220%20-960%20960%20960%22%3E%3Cpath%20d=%22M400-560ZM160-160q-33%200-56.5-23.5T80-240v-480q0-33%2023.5-56.5T160-800h640q33%200%2056.5%2023.5T880-720H160v480h80v80h-80Zm640-80v-320H640v320h160Zm-180%2080q-25%200-42.5-17.5T560-220v-360q0-25%2017.5-42.5T620-640h200q25%200%2042.5%2017.5T880-580v360q0%2025-17.5%2042.5T820-160H620Zm100-300q13%200%2021.5-9t8.5-21q0-13-8.5-21.5T720-520q-12%200-21%208.5t-9%2021.5q0%2012%209%2021t21%209ZM340-160l-20-70q-19-17-29.5-40T280-320q0-27%2010.5-50t29.5-40l20-70h120l20%2070q19%2017%2029.5%2040t10.5%2050q0%2027-10.5%2050T480-230l-20%2070H340Zm60-100q26%200%2043-17.5t17-42.5q0-25-18-42.5T400-380q-24%200-42%2017t-18%2043q0%2026%2017%2043t43%2017Zm320-140Z%22/%3E%3C/svg%3E');
487
+ }
488
+
489
+ .diagnostics-tiles [data-diagnostics-tile='control-state']::before {
490
+ mask-image: url('data:image/svg+xml,%3Csvg%20xmlns=%22http://www.w3.org/2000/svg%22%20viewBox=%220%20-960%20960%20960%22%3E%3Cpath%20d=%22M440-120v-240h80v80h320v80H520v80h-80Zm-320-80v-80h240v80H120Zm160-160v-80H120v-80h160v-80h80v240h-80Zm160-80v-80h400v80H440Zm160-160v-240h80v80h160v80H680v80h-80Zm-480-80v-80h400v80H120Z%22/%3E%3C/svg%3E');
491
+ }
492
+
493
+ .diagnostics-tiles [data-diagnostics-tile='live-media']::before {
494
+ mask-image: url('data:image/svg+xml,%3Csvg%20xmlns=%22http://www.w3.org/2000/svg%22%20viewBox=%220%20-960%20960%20960%22%3E%3Cpath%20d=%22M160-160q-33%200-56.5-23.5T80-240v-480q0-33%2023.5-56.5T160-800h480q33%200%2056.5%2023.5T720-720v180l160-160v440L720-420v180q0%2033-23.5%2056.5T640-160H160Zm0-80h480v-480H160v480Zm0%200v-480%20480Z%22/%3E%3C/svg%3E');
495
+ }
496
+
497
+ .diagnostics-tiles [data-diagnostics-tile='hksv-recording']::before {
498
+ mask-image: url('data:image/svg+xml,%3Csvg%20xmlns=%22http://www.w3.org/2000/svg%22%20viewBox=%220%20-960%20960%20960%22%3E%3Cpath%20d=%22m160-800%2080%20160h120l-80-160h80l80%20160h120l-80-160h80l80%20160h120l-80-160h120q33%200%2056.5%2023.5T880-720v480q0%2033-23.5%2056.5T800-160H160q-33%200-56.5-23.5T80-240v-480q0-33%2023.5-56.5T160-800Zm0%20240v320h640v-320H160Zm0%200v320-320Z%22/%3E%3C/svg%3E');
499
+ }
500
+
501
+ .diagnostics-tiles [data-diagnostics-tile='dashboard-ui']::before {
502
+ mask-image: url('data:image/svg+xml,%3Csvg%20xmlns=%22http://www.w3.org/2000/svg%22%20viewBox=%220%20-960%20960%20960%22%3E%3Cpath%20d=%22M520-600v-240h320v240H520ZM120-440v-400h320v400H120Zm400%20320v-400h320v400H520Zm-400%200v-240h320v240H120Zm80-400h160v-240H200v240Zm400%20320h160v-240H600v240Zm0-480h160v-80H600v80ZM200-200h160v-80H200v80Zm160-320Zm240-160Zm0%20240ZM360-280Z%22/%3E%3C/svg%3E');
503
+ }
504
+
505
+ .diagnostics-tiles [data-diagnostics-tile='other']::before {
506
+ mask-image: url('data:image/svg+xml,%3Csvg%20xmlns=%22http://www.w3.org/2000/svg%22%20viewBox=%220%20-960%20960%20960%22%3E%3Cpath%20d=%22M513.5-254.5Q528-269%20528-290t-14.5-35.5Q499-340%20478-340t-35.5%2014.5Q428-311%20428-290t14.5%2035.5Q457-240%20478-240t35.5-14.5ZM442-394h74q0-33%207.5-52t42.5-52q26-26%2041-49.5t15-56.5q0-56-41-86t-97-30q-57%200-92.5%2030T342-618l66%2026q5-18%2022.5-39t53.5-21q32%200%2048%2017.5t16%2038.5q0%2020-12%2037.5T506-526q-44%2039-54%2059t-10%2073Zm38%20314q-83%200-156-31.5T197-197q-54-54-85.5-127T80-480q0-83%2031.5-156T197-763q54-54%20127-85.5T480-880q83%200%20156%2031.5T763-763q54%2054%2085.5%20127T880-480q0%2083-31.5%20156T763-197q-54%2054-127%2085.5T480-80Zm0-80q134%200%20227-93t93-227q0-134-93-227t-227-93q-134%200-227%2093t-93%20227q0%20134%2093%20227t227%2093Zm0-320Z%22/%3E%3C/svg%3E');
418
507
  }
419
508
 
420
509
  .advanced-fields {
@@ -536,6 +625,7 @@ button.diagnostics-quiet-action {
536
625
  border: 1px solid var(--line-strong);
537
626
  border-radius: var(--radius-control);
538
627
  background: var(--panel);
628
+ color: var(--ink);
539
629
  padding: 0;
540
630
  }
541
631
 
@@ -570,38 +660,84 @@ button.diagnostics-quiet-action {
570
660
  transform 150ms var(--ease-out);
571
661
  }
572
662
 
663
+ .dashboard-action[data-collecting],
664
+ .dashboard-action:has(.diagnostics-name) {
665
+ width: auto;
666
+ grid-auto-flow: column;
667
+ gap: 6px;
668
+ padding: 0 10px;
669
+ }
670
+
573
671
  .dashboard-action[data-collecting] {
574
672
  border-color: var(--accent);
575
673
  background: color-mix(in srgb, var(--accent) 16%, var(--panel));
576
674
  }
577
675
 
676
+ /*
677
+ * The dashboard's diagnostics action is named in words at rest, and a capture under way replaces that name
678
+ * beside the icon, so neither rests on the icon or on colour alone.
679
+ */
680
+ .diagnostics-name,
681
+ .diagnostics-capturing {
682
+ font-size: 12px;
683
+ font-weight: 600;
684
+ }
685
+
686
+ .diagnostics-capturing,
687
+ .dashboard-action[data-collecting] .diagnostics-name {
688
+ display: none;
689
+ }
690
+
691
+ .dashboard-action[data-collecting] .diagnostics-capturing {
692
+ display: inline;
693
+ }
694
+
695
+ /* A problem the dashboard shows: an alert ring, and a dot so the state does not rest on colour alone. */
696
+ .dashboard-action[data-attention] {
697
+ border-color: var(--alert);
698
+ box-shadow: 0 0 0 2px color-mix(in srgb, var(--alert) 24%, transparent);
699
+ }
700
+
701
+ .dashboard-action[data-attention]::before {
702
+ content: '';
703
+ position: absolute;
704
+ top: -5px;
705
+ right: -5px;
706
+ width: 8px;
707
+ height: 8px;
708
+ border: 2px solid var(--panel);
709
+ border-radius: 50%;
710
+ background: var(--alert);
711
+ }
712
+
578
713
  .dashboard-action:hover::after,
579
714
  .dashboard-action:focus-visible::after {
580
715
  opacity: 1;
581
716
  transform: translateY(0);
582
717
  }
583
718
 
584
- /*
585
- * The way into a diagnostic run, standing where a status pill used to. A pill saying "Connected" was decoration on
586
- * the healthy path and a dead end on every other; this appears only where a run would explain something.
587
- */
588
- .dashboard-diagnose {
589
- flex: 0 0 auto;
590
- /* Buttons are full width by default here, and this one sits inline beside the title. */
591
- width: auto;
592
- border: 1px solid var(--alert);
593
- border-radius: var(--radius-pill);
594
- background: color-mix(in srgb, var(--alert) 16%, var(--panel));
719
+ .blocking-dialog {
720
+ max-width: 420px;
721
+ border: 0;
722
+ border-radius: var(--radius-panel);
723
+ background: var(--panel);
724
+ box-shadow: var(--shadow-overlay);
595
725
  color: var(--ink);
596
- padding: 6px 12px;
597
- font: inherit;
598
- font-size: 12px;
599
- font-weight: 600;
600
- cursor: pointer;
726
+ padding: 24px;
727
+ }
728
+
729
+ .blocking-dialog::backdrop {
730
+ background: rgb(0 0 0 / 30%);
731
+ backdrop-filter: blur(5px);
601
732
  }
602
733
 
603
- .dashboard-diagnose:hover {
604
- background: color-mix(in srgb, var(--alert) 26%, var(--panel));
734
+ .blocking-dialog h2 {
735
+ margin: 0 0 8px;
736
+ }
737
+
738
+ .blocking-dialog p {
739
+ margin: 0 0 20px;
740
+ color: var(--ink-secondary);
605
741
  }
606
742
 
607
743
  .device-group + .device-group {
@@ -1149,7 +1285,128 @@ button.diagnostics-quiet-action {
1149
1285
  font-weight: 700;
1150
1286
  }
1151
1287
 
1152
- .preference-grid .preference-changed .snapshot-description {
1288
+ .preference-grid .preference-changed /*
1289
+ * The guard-mode map: four HomeKit states against nine station modes, one pairing per row.
1290
+ *
1291
+ * A table because the content is one, which also gives each select its own row label without a second element
1292
+ * beside it. Row rules and no box: the panel's own surface encloses it, and the one control in the row is the
1293
+ * select, which carries the only border here.
1294
+ */
1295
+ .arming-setting {
1296
+ color: var(--muted);
1297
+ font-size: 11px;
1298
+ }
1299
+
1300
+ .arming-setting > summary {
1301
+ display: grid;
1302
+ grid-template-columns: minmax(0, 1fr) 12px;
1303
+ align-items: center;
1304
+ gap: 2px 8px;
1305
+ cursor: pointer;
1306
+ }
1307
+
1308
+ /* `display: grid` on a summary drops the native marker, so the affordance is drawn back in as a caret. */
1309
+ .arming-setting > summary::marker,
1310
+ .arming-setting > summary::-webkit-details-marker {
1311
+ content: '';
1312
+ display: none;
1313
+ }
1314
+
1315
+ .arming-setting > summary::after {
1316
+ content: '';
1317
+ grid-row: 1 / span 2;
1318
+ grid-column: 2;
1319
+ width: 6px;
1320
+ height: 6px;
1321
+ border-right: 1.5px solid var(--muted);
1322
+ border-bottom: 1.5px solid var(--muted);
1323
+ transform: rotate(45deg);
1324
+ transition: transform 140ms var(--ease-out);
1325
+ }
1326
+
1327
+ .arming-setting[open] > summary::after {
1328
+ transform: rotate(-135deg);
1329
+ }
1330
+
1331
+ /* The map as one line, which is the answer most households only ever read. */
1332
+ .arming-summary {
1333
+ overflow: hidden;
1334
+ color: var(--muted);
1335
+ font-size: 10px;
1336
+ text-overflow: ellipsis;
1337
+ white-space: nowrap;
1338
+ }
1339
+
1340
+ .arming-setting[open] > summary {
1341
+ margin-bottom: 8px;
1342
+ }
1343
+
1344
+ .arming-table {
1345
+ width: 100%;
1346
+ border-collapse: collapse;
1347
+ }
1348
+
1349
+ .arming-table thead th {
1350
+ padding: 0 0 6px;
1351
+ border-bottom: 1px solid var(--line-strong);
1352
+ color: var(--muted);
1353
+ font-size: 10px;
1354
+ font-weight: 600;
1355
+ letter-spacing: 0.04em;
1356
+ text-align: left;
1357
+ text-transform: uppercase;
1358
+ }
1359
+
1360
+ .arming-table tbody tr + tr th,
1361
+ .arming-table tbody tr + tr td {
1362
+ border-top: 1px solid var(--line);
1363
+ }
1364
+
1365
+ .arming-table th[scope='row'] {
1366
+ width: 38%;
1367
+ padding: 6px 10px 6px 0;
1368
+ color: var(--ink);
1369
+ font-size: 11px;
1370
+ font-weight: 600;
1371
+ text-align: left;
1372
+ }
1373
+
1374
+ .arming-table th[scope='row'] label {
1375
+ cursor: pointer;
1376
+ }
1377
+
1378
+ .arming-table td {
1379
+ padding: 6px 0;
1380
+ }
1381
+
1382
+ .arming-table select {
1383
+ width: 100%;
1384
+ min-height: 28px;
1385
+ border: 1px solid var(--line-strong);
1386
+ border-radius: var(--radius-control);
1387
+ background: var(--panel);
1388
+ color: var(--ink);
1389
+ padding: 2px 6px;
1390
+ font-size: 11px;
1391
+ }
1392
+
1393
+ /* The changed row carries the weight, so a pending map reads as which pairing moved rather than as a heavier table. */
1394
+ .arming-setting .snapshot-help {
1395
+ margin-top: 6px;
1396
+ }
1397
+
1398
+ .arming-table select.preference-control-changed {
1399
+ border-color: var(--accent);
1400
+ font-weight: 700;
1401
+ }
1402
+
1403
+ @media (pointer: coarse) {
1404
+ .arming-table select {
1405
+ min-height: 44px;
1406
+ }
1407
+ }
1408
+
1409
+ .snapshot-description {
1153
1410
  font-weight: 400;
1154
1411
  }
1155
1412
 
@@ -1322,8 +1579,7 @@ button.diagnostics-quiet-action {
1322
1579
  cursor: pointer;
1323
1580
  }
1324
1581
 
1325
- .first-setup-confirmation input,
1326
- .diagnostics-review-confirm input {
1582
+ .first-setup-confirmation input {
1327
1583
  appearance: none;
1328
1584
  display: grid;
1329
1585
  place-content: center;
@@ -1338,8 +1594,7 @@ button.diagnostics-quiet-action {
1338
1594
  padding: 0;
1339
1595
  }
1340
1596
 
1341
- .first-setup-confirmation input::before,
1342
- .diagnostics-review-confirm input::before {
1597
+ .first-setup-confirmation input::before {
1343
1598
  content: '';
1344
1599
  width: 8px;
1345
1600
  height: 4px;
@@ -1348,14 +1603,12 @@ button.diagnostics-quiet-action {
1348
1603
  transform: rotate(-45deg) scale(0);
1349
1604
  }
1350
1605
 
1351
- .first-setup-confirmation input:checked,
1352
- .diagnostics-review-confirm input:checked {
1606
+ .first-setup-confirmation input:checked {
1353
1607
  border-color: var(--accent);
1354
1608
  background: var(--accent);
1355
1609
  }
1356
1610
 
1357
- .first-setup-confirmation input:checked::before,
1358
- .diagnostics-review-confirm input:checked::before {
1611
+ .first-setup-confirmation input:checked::before {
1359
1612
  transform: rotate(-45deg) scale(1);
1360
1613
  }
1361
1614
 
@@ -1655,7 +1908,6 @@ button:disabled {
1655
1908
  cursor: not-allowed;
1656
1909
  }
1657
1910
 
1658
- button.diagnostics-secondary-action:hover:not(:disabled),
1659
1911
  .dashboard-page-back:hover:not(:disabled),
1660
1912
  .warm-up-controls button:hover:not(:disabled),
1661
1913
  .warm-up-column button:hover:not(:disabled),
@@ -1687,8 +1939,7 @@ button.diagnostics-quiet-action:hover:not(:disabled) {
1687
1939
  }
1688
1940
 
1689
1941
  .warm-up-controls button:disabled,
1690
- .warm-up-column button:disabled,
1691
- button.diagnostics-secondary-action:disabled {
1942
+ .warm-up-column button:disabled {
1692
1943
  border-color: var(--line);
1693
1944
  background: var(--panel);
1694
1945
  color: var(--ink-disabled);
@@ -1734,6 +1985,20 @@ input:focus-visible {
1734
1985
  }
1735
1986
 
1736
1987
  @media (max-width: 420px) {
1988
+ /*
1989
+ * Two across rather than three. Seven areas still fit on one screen at this width, and a third column
1990
+ * leaves each label about ninety pixels, where the longest of them runs to four lines.
1991
+ */
1992
+ .diagnostics-tiles {
1993
+ grid-template-columns: repeat(2, minmax(0, 1fr));
1994
+ }
1995
+
1996
+ /* The row keeps its three actions on one line by leaving the diagnostics action to its icon. */
1997
+ .diagnostics-name {
1998
+ display: none;
1999
+ }
2000
+
2001
+
1737
2002
  .status-row {
1738
2003
  display: grid;
1739
2004
  }
@@ -1806,14 +2071,14 @@ input:focus-visible {
1806
2071
  width: 100%;
1807
2072
  }
1808
2073
 
1809
- .diagnostics-choice-actions {
1810
- display: grid;
1811
- grid-template-columns: repeat(2, minmax(0, 1fr));
2074
+ /* Two actions side by side leave each a sliver of a label at this width, so they stack. */
2075
+ .diagnostics-handoff:not([hidden]) {
2076
+ grid-template-columns: 1fr;
1812
2077
  }
1813
2078
 
1814
- .diagnostics-choice-actions button {
1815
- width: 100%;
1816
- min-width: 0;
2079
+ .diagnostics-choice-actions {
2080
+ display: grid;
2081
+ gap: 8px;
1817
2082
  }
1818
2083
 
1819
2084
  .device-grid {