homebridge-roborock-matter 3.7.1 → 3.8.0
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.
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 3.8.0
|
|
4
|
+
|
|
5
|
+
**The settings page follows Homebridge's dark theme, and the icon is in the header.**
|
|
6
|
+
|
|
7
|
+
The page stayed white inside a dark Homebridge, in every version. It was not reading the operating system's setting — that would have been wrong too. Homebridge UI reaches into this plugin's iframe and puts classes on our own `<body>`: `dark-mode` when the user picks a dark theme, `config-ui-x-<theme>` when they pick a light one. The stylesheet had `color-scheme: light` hard-coded and one set of colours, so there was nothing to follow it with.
|
|
8
|
+
|
|
9
|
+
Every colour is now a token, the dark set is declared once, and `index.js` decides which applies and writes it to `<html data-theme>`. Homebridge's own choice wins over the OS, because someone who picked light in Homebridge on a dark Mac meant light; the OS is the fallback for a page opened outside Homebridge. A MutationObserver keeps up when the theme changes while the page is open — the switch is one screen away, and the parent applies it by mutating our body rather than reloading us.
|
|
10
|
+
|
|
11
|
+
Two details worth writing down. Homebridge also assigns `body.style.backgroundColor = "#242424 !important"`; the CSSOM rejects a value carrying `!important`, so that line has never done anything and the class is the only signal there is. And the QR tile stays white in both themes on purpose — a camera cannot read a code without a light quiet zone. It is the one colour on the page that is not a token, and the test names it so the exemption cannot grow.
|
|
12
|
+
|
|
13
|
+
**The plugin's icon now sits beside the title**, as on the other plugins' settings pages. It is `assets/icon.png` byte for byte rather than a redrawing or a resize, and a test holds the two files identical, so the tile here and the tile on the Homebridge plugin list cannot drift apart.
|
|
14
|
+
|
|
15
|
+
787 tests, up from 773. Verified red against 3.7.1: 14 of 14 fail.
|
|
16
|
+
|
|
3
17
|
## 3.7.1
|
|
4
18
|
|
|
5
19
|
**The Matter Pairing section told you to pair the wrong thing first.**
|
|
Binary file
|
|
@@ -1,11 +1,26 @@
|
|
|
1
1
|
<link rel="stylesheet" href="styles.css" />
|
|
2
2
|
|
|
3
3
|
<main class="container">
|
|
4
|
-
<header>
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
4
|
+
<header class="page-header">
|
|
5
|
+
<!-- The plugin's own icon, byte for byte the file in assets/ that npm and
|
|
6
|
+
the Homebridge plugin page show. Not a redrawing: a second copy of a
|
|
7
|
+
logo drifts from the first the moment either is touched, and then the
|
|
8
|
+
tile here and the tile on the plugin list stop matching. A test holds
|
|
9
|
+
the two files identical. -->
|
|
10
|
+
<img
|
|
11
|
+
class="brand-mark"
|
|
12
|
+
src="icon.png"
|
|
13
|
+
alt=""
|
|
14
|
+
width="54"
|
|
15
|
+
height="54"
|
|
16
|
+
decoding="async"
|
|
17
|
+
/>
|
|
18
|
+
<div class="brand-text">
|
|
19
|
+
<h1>Roborock Vacuum</h1>
|
|
20
|
+
<p class="subtitle">
|
|
21
|
+
Configure account access, discovery filters, and connection diagnostics.
|
|
22
|
+
</p>
|
|
23
|
+
</div>
|
|
9
24
|
</header>
|
|
10
25
|
|
|
11
26
|
<section class="panel">
|
|
@@ -1647,7 +1647,80 @@ async function updatePluginConfig(patch) {
|
|
|
1647
1647
|
await window.homebridge.savePluginConfig();
|
|
1648
1648
|
}
|
|
1649
1649
|
|
|
1650
|
+
/**
|
|
1651
|
+
* Follow Homebridge's theme, not the operating system's.
|
|
1652
|
+
*
|
|
1653
|
+
* Homebridge UI reaches into this iframe's document and puts classes on our
|
|
1654
|
+
* body: `dark-mode` (plus `config-ui-x-dark-mode-<theme>`) when the user has
|
|
1655
|
+
* picked a dark theme, and `config-ui-x-<theme>` when they have picked a light
|
|
1656
|
+
* one. It also tries to force the background with
|
|
1657
|
+
* `body.style.backgroundColor = "#242424 !important"`, which the CSSOM rejects
|
|
1658
|
+
* because a property value may not carry `!important` — so that line does
|
|
1659
|
+
* nothing and the class is the only signal there is.
|
|
1660
|
+
*
|
|
1661
|
+
* The page used to read neither, so it stayed white inside a dark Homebridge.
|
|
1662
|
+
*
|
|
1663
|
+
* Precedence, and the order matters: Homebridge's own choice wins whenever it
|
|
1664
|
+
* has expressed one, because a user who picked light in Homebridge on a dark
|
|
1665
|
+
* Mac meant light. Only when no Homebridge class is present at all — the page
|
|
1666
|
+
* opened on its own, or before the parent has painted — do we fall back to the
|
|
1667
|
+
* OS preference, so the first frame is never the wrong colour.
|
|
1668
|
+
*/
|
|
1669
|
+
const DARK_CLASS = "dark-mode";
|
|
1670
|
+
const HOMEBRIDGE_THEME_CLASS = /^config-ui-x-/;
|
|
1671
|
+
|
|
1672
|
+
function homebridgeHasChosenATheme() {
|
|
1673
|
+
return Array.from(document.body.classList).some((name) =>
|
|
1674
|
+
HOMEBRIDGE_THEME_CLASS.test(name)
|
|
1675
|
+
);
|
|
1676
|
+
}
|
|
1677
|
+
|
|
1678
|
+
function prefersDark() {
|
|
1679
|
+
return (
|
|
1680
|
+
typeof window.matchMedia === "function" &&
|
|
1681
|
+
window.matchMedia("(prefers-color-scheme: dark)").matches
|
|
1682
|
+
);
|
|
1683
|
+
}
|
|
1684
|
+
|
|
1685
|
+
function applyTheme() {
|
|
1686
|
+
const dark = document.body.classList.contains(DARK_CLASS)
|
|
1687
|
+
? true
|
|
1688
|
+
: homebridgeHasChosenATheme()
|
|
1689
|
+
? false
|
|
1690
|
+
: prefersDark();
|
|
1691
|
+
document.documentElement.dataset.theme = dark ? "dark" : "light";
|
|
1692
|
+
}
|
|
1693
|
+
|
|
1694
|
+
/**
|
|
1695
|
+
* Keep following it after the first paint.
|
|
1696
|
+
*
|
|
1697
|
+
* The theme can change while this page is open — the switch is in the same UI,
|
|
1698
|
+
* one screen away — and the parent applies it by mutating our body's class
|
|
1699
|
+
* list rather than reloading us. Without the observer the settings page is the
|
|
1700
|
+
* one thing in Homebridge that stays the old colour until it is reopened.
|
|
1701
|
+
*/
|
|
1702
|
+
function watchTheme() {
|
|
1703
|
+
applyTheme();
|
|
1704
|
+
|
|
1705
|
+
new MutationObserver(applyTheme).observe(document.body, {
|
|
1706
|
+
attributes: true,
|
|
1707
|
+
attributeFilter: ["class"],
|
|
1708
|
+
});
|
|
1709
|
+
|
|
1710
|
+
if (typeof window.matchMedia === "function") {
|
|
1711
|
+
const query = window.matchMedia("(prefers-color-scheme: dark)");
|
|
1712
|
+
// Only relevant while Homebridge has not chosen for us; applyTheme decides.
|
|
1713
|
+
if (typeof query.addEventListener === "function") {
|
|
1714
|
+
query.addEventListener("change", applyTheme);
|
|
1715
|
+
}
|
|
1716
|
+
}
|
|
1717
|
+
}
|
|
1718
|
+
|
|
1650
1719
|
function init() {
|
|
1720
|
+
// Before anything else: a page that is briefly the wrong colour is the first
|
|
1721
|
+
// thing the eye catches.
|
|
1722
|
+
watchTheme();
|
|
1723
|
+
|
|
1651
1724
|
// The markup ships in the off state, but say it once here too: loadConfig()
|
|
1652
1725
|
// can fail or find no config, and the alternative is a loud orange pairing
|
|
1653
1726
|
// callout for a feature that is switched off.
|
|
@@ -4,6 +4,17 @@
|
|
|
4
4
|
also sent a request to Google from a local admin interface. The body rule
|
|
5
5
|
below already falls back to the system stack. */
|
|
6
6
|
|
|
7
|
+
/* Every colour on this page comes from a token below, and the dark set is
|
|
8
|
+
declared exactly once. That is a rule, not a preference: the page renders
|
|
9
|
+
inside Homebridge's own iframe, and a single hard-coded hex is all it takes
|
|
10
|
+
to put light-on-light or dark-on-dark somewhere nobody looks. The rule is
|
|
11
|
+
enforced by __tests__/the-settings-page-follows-homebridge-s-theme.test.js.
|
|
12
|
+
|
|
13
|
+
Which set applies is decided by index.js and written to
|
|
14
|
+
`<html data-theme>`, because Homebridge's theme is not the operating
|
|
15
|
+
system's: the UI reaches into this iframe and puts `dark-mode` on our body
|
|
16
|
+
when the user picks a dark theme, whatever the OS is set to. See
|
|
17
|
+
applyTheme() there for the precedence. */
|
|
7
18
|
:root {
|
|
8
19
|
color-scheme: light;
|
|
9
20
|
--bg: #f4f6f9;
|
|
@@ -13,15 +24,72 @@
|
|
|
13
24
|
--text: #17242f;
|
|
14
25
|
--heading: #0c1a24;
|
|
15
26
|
--muted: #5a6b7b;
|
|
27
|
+
--placeholder: #8a97a5;
|
|
16
28
|
--primary: #0e7a6a;
|
|
17
29
|
--primary-strong: #0b6357;
|
|
18
30
|
--primary-text: #ffffff;
|
|
31
|
+
--primary-wash: rgba(14, 122, 106, 0.06);
|
|
32
|
+
--primary-ring: rgba(14, 122, 106, 0.16);
|
|
33
|
+
--primary-glow: rgba(14, 122, 106, 0.14);
|
|
34
|
+
--row-hover: #f1f7f5;
|
|
19
35
|
--secondary: #eef2f5;
|
|
20
36
|
--border: #d9e0e7;
|
|
21
37
|
--shadow: 0 6px 18px rgba(15, 32, 44, 0.07);
|
|
22
38
|
--warning: #9a5b00;
|
|
23
39
|
--danger: #b42323;
|
|
24
40
|
--link: #0e7a6a;
|
|
41
|
+
|
|
42
|
+
/* Status colours, as a face and a soft backing each. */
|
|
43
|
+
--good-line: #16a34a;
|
|
44
|
+
--good-bg: #e6f6ec;
|
|
45
|
+
--good-border: #b5e3c5;
|
|
46
|
+
--good-text: #14683a;
|
|
47
|
+
--warn-line: #d97706;
|
|
48
|
+
--warn-bg: #fdf3df;
|
|
49
|
+
--warn-border: #f0d9a8;
|
|
50
|
+
--warn-text: #8a5a00;
|
|
51
|
+
--danger-line: #dc2626;
|
|
52
|
+
--callout-border: #d9822b;
|
|
53
|
+
--callout-bg: rgba(217, 130, 43, 0.08);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/* The dark set. One block, same names, no extra rules — anything that needs
|
|
57
|
+
to change in dark mode changes here or it is not a token yet. */
|
|
58
|
+
:root[data-theme="dark"] {
|
|
59
|
+
color-scheme: dark;
|
|
60
|
+
--bg: #1c1c1c;
|
|
61
|
+
--panel: #242424;
|
|
62
|
+
--panel-soft: #2a2a2a;
|
|
63
|
+
--input: #1f1f1f;
|
|
64
|
+
--text: #e4e6e8;
|
|
65
|
+
--heading: #f5f7f8;
|
|
66
|
+
--muted: #a2adb6;
|
|
67
|
+
--placeholder: #7c8790;
|
|
68
|
+
--primary: #3fb39c;
|
|
69
|
+
--primary-strong: #56c7b0;
|
|
70
|
+
--primary-text: #10201d;
|
|
71
|
+
--primary-wash: rgba(63, 179, 156, 0.08);
|
|
72
|
+
--primary-ring: rgba(63, 179, 156, 0.28);
|
|
73
|
+
--primary-glow: rgba(0, 0, 0, 0.45);
|
|
74
|
+
--row-hover: #303634;
|
|
75
|
+
--secondary: #333333;
|
|
76
|
+
--border: #3a3a3a;
|
|
77
|
+
--shadow: 0 6px 18px rgba(0, 0, 0, 0.45);
|
|
78
|
+
--warning: #e0a458;
|
|
79
|
+
--danger: #f0736e;
|
|
80
|
+
--link: #56c7b0;
|
|
81
|
+
|
|
82
|
+
--good-line: #3fbf76;
|
|
83
|
+
--good-bg: rgba(63, 191, 118, 0.14);
|
|
84
|
+
--good-border: rgba(63, 191, 118, 0.42);
|
|
85
|
+
--good-text: #7fdca6;
|
|
86
|
+
--warn-line: #e0a458;
|
|
87
|
+
--warn-bg: rgba(224, 164, 88, 0.14);
|
|
88
|
+
--warn-border: rgba(224, 164, 88, 0.42);
|
|
89
|
+
--warn-text: #edc088;
|
|
90
|
+
--danger-line: #f0736e;
|
|
91
|
+
--callout-border: #d9822b;
|
|
92
|
+
--callout-bg: rgba(217, 130, 43, 0.14);
|
|
25
93
|
}
|
|
26
94
|
|
|
27
95
|
* {
|
|
@@ -31,11 +99,7 @@
|
|
|
31
99
|
body {
|
|
32
100
|
margin: 0;
|
|
33
101
|
font-family: "Space Grotesk", "Segoe UI", system-ui, sans-serif;
|
|
34
|
-
background: linear-gradient(
|
|
35
|
-
180deg,
|
|
36
|
-
rgba(14, 122, 106, 0.06),
|
|
37
|
-
transparent 260px
|
|
38
|
-
),
|
|
102
|
+
background: linear-gradient(180deg, var(--primary-wash), transparent 260px),
|
|
39
103
|
var(--bg);
|
|
40
104
|
color: var(--text);
|
|
41
105
|
}
|
|
@@ -48,6 +112,28 @@ body {
|
|
|
48
112
|
gap: 18px;
|
|
49
113
|
}
|
|
50
114
|
|
|
115
|
+
/* The icon sits beside the title, the way it does on the other plugins'
|
|
116
|
+
settings pages. */
|
|
117
|
+
.page-header {
|
|
118
|
+
display: flex;
|
|
119
|
+
align-items: center;
|
|
120
|
+
gap: 16px;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
.brand-mark {
|
|
124
|
+
flex: 0 0 auto;
|
|
125
|
+
width: 54px;
|
|
126
|
+
height: 54px;
|
|
127
|
+
/* The artwork carries its own rounded tile; this only matches the corner so
|
|
128
|
+
the edges do not show against the page. */
|
|
129
|
+
border-radius: 12px;
|
|
130
|
+
display: block;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
.brand-text {
|
|
134
|
+
min-width: 0;
|
|
135
|
+
}
|
|
136
|
+
|
|
51
137
|
/* Explicit heading colors: Homebridge UI injects its own theme stylesheet
|
|
52
138
|
into custom-UI iframes, so headings must not rely on inheritance or they
|
|
53
139
|
can end up dark-on-dark / light-on-light. */
|
|
@@ -146,11 +232,11 @@ select:focus,
|
|
|
146
232
|
input:focus {
|
|
147
233
|
outline: none;
|
|
148
234
|
border-color: var(--primary);
|
|
149
|
-
box-shadow: 0 0 0 3px
|
|
235
|
+
box-shadow: 0 0 0 3px var(--primary-ring);
|
|
150
236
|
}
|
|
151
237
|
|
|
152
238
|
input::placeholder {
|
|
153
|
-
color:
|
|
239
|
+
color: var(--placeholder);
|
|
154
240
|
}
|
|
155
241
|
|
|
156
242
|
.checkbox {
|
|
@@ -235,7 +321,7 @@ button {
|
|
|
235
321
|
|
|
236
322
|
button:hover {
|
|
237
323
|
transform: translateY(-1px);
|
|
238
|
-
box-shadow: 0 10px 20px
|
|
324
|
+
box-shadow: 0 10px 20px var(--primary-glow);
|
|
239
325
|
}
|
|
240
326
|
|
|
241
327
|
button:active {
|
|
@@ -292,15 +378,15 @@ select:focus-visible {
|
|
|
292
378
|
}
|
|
293
379
|
|
|
294
380
|
.toast.success {
|
|
295
|
-
border-left-color:
|
|
381
|
+
border-left-color: var(--good-line);
|
|
296
382
|
}
|
|
297
383
|
|
|
298
384
|
.toast.error {
|
|
299
|
-
border-left-color:
|
|
385
|
+
border-left-color: var(--danger-line);
|
|
300
386
|
}
|
|
301
387
|
|
|
302
388
|
.toast.warning {
|
|
303
|
-
border-left-color:
|
|
389
|
+
border-left-color: var(--warn-line);
|
|
304
390
|
}
|
|
305
391
|
|
|
306
392
|
.diagnostics-summary {
|
|
@@ -363,7 +449,7 @@ select:focus-visible {
|
|
|
363
449
|
|
|
364
450
|
.device-row:hover {
|
|
365
451
|
border-color: var(--primary);
|
|
366
|
-
background:
|
|
452
|
+
background: var(--row-hover);
|
|
367
453
|
}
|
|
368
454
|
|
|
369
455
|
.device-row input {
|
|
@@ -403,11 +489,11 @@ select:focus-visible {
|
|
|
403
489
|
}
|
|
404
490
|
|
|
405
491
|
.local-test-card.good {
|
|
406
|
-
border-color:
|
|
492
|
+
border-color: var(--good-border);
|
|
407
493
|
}
|
|
408
494
|
|
|
409
495
|
.local-test-card.warn {
|
|
410
|
-
border-color:
|
|
496
|
+
border-color: var(--warn-border);
|
|
411
497
|
}
|
|
412
498
|
|
|
413
499
|
.device-header {
|
|
@@ -450,10 +536,14 @@ select:focus-visible {
|
|
|
450
536
|
height: 180px;
|
|
451
537
|
border-radius: 8px;
|
|
452
538
|
border: 1px solid var(--border);
|
|
539
|
+
/* Deliberately white in both themes, and the one colour on this page that
|
|
540
|
+
is not a token. A QR code needs a light quiet zone to scan; a camera
|
|
541
|
+
pointed at a dark tile gets nothing. */
|
|
453
542
|
background: #ffffff;
|
|
454
543
|
}
|
|
455
544
|
|
|
456
545
|
.qr-placeholder {
|
|
546
|
+
background: var(--panel-soft);
|
|
457
547
|
color: var(--muted);
|
|
458
548
|
display: grid;
|
|
459
549
|
place-items: center;
|
|
@@ -514,15 +604,15 @@ select:focus-visible {
|
|
|
514
604
|
}
|
|
515
605
|
|
|
516
606
|
.pill.good {
|
|
517
|
-
background:
|
|
518
|
-
border-color:
|
|
519
|
-
color:
|
|
607
|
+
background: var(--good-bg);
|
|
608
|
+
border-color: var(--good-border);
|
|
609
|
+
color: var(--good-text);
|
|
520
610
|
}
|
|
521
611
|
|
|
522
612
|
.pill.warn {
|
|
523
|
-
background:
|
|
524
|
-
border-color:
|
|
525
|
-
color:
|
|
613
|
+
background: var(--warn-bg);
|
|
614
|
+
border-color: var(--warn-border);
|
|
615
|
+
color: var(--warn-text);
|
|
526
616
|
}
|
|
527
617
|
|
|
528
618
|
.status-pill {
|
|
@@ -537,15 +627,15 @@ select:focus-visible {
|
|
|
537
627
|
}
|
|
538
628
|
|
|
539
629
|
.status-pill.good {
|
|
540
|
-
background:
|
|
541
|
-
border-color:
|
|
542
|
-
color:
|
|
630
|
+
background: var(--good-bg);
|
|
631
|
+
border-color: var(--good-border);
|
|
632
|
+
color: var(--good-text);
|
|
543
633
|
}
|
|
544
634
|
|
|
545
635
|
.status-pill.warn {
|
|
546
|
-
background:
|
|
547
|
-
border-color:
|
|
548
|
-
color:
|
|
636
|
+
background: var(--warn-bg);
|
|
637
|
+
border-color: var(--warn-border);
|
|
638
|
+
color: var(--warn-text);
|
|
549
639
|
}
|
|
550
640
|
|
|
551
641
|
.diagnostic-device dl,
|
|
@@ -586,6 +676,11 @@ select:focus-visible {
|
|
|
586
676
|
font-size: 1.85rem;
|
|
587
677
|
}
|
|
588
678
|
|
|
679
|
+
.brand-mark {
|
|
680
|
+
width: 44px;
|
|
681
|
+
height: 44px;
|
|
682
|
+
}
|
|
683
|
+
|
|
589
684
|
.container {
|
|
590
685
|
padding: 22px 16px;
|
|
591
686
|
}
|
|
@@ -636,10 +731,10 @@ select:focus-visible {
|
|
|
636
731
|
.pairing-callout {
|
|
637
732
|
margin: 4px 0 16px 26px;
|
|
638
733
|
padding: 12px 14px;
|
|
639
|
-
border: 1px solid
|
|
734
|
+
border: 1px solid var(--callout-border);
|
|
640
735
|
border-left-width: 4px;
|
|
641
736
|
border-radius: 6px;
|
|
642
|
-
background:
|
|
737
|
+
background: var(--callout-bg);
|
|
643
738
|
}
|
|
644
739
|
|
|
645
740
|
.pairing-callout h3 {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "homebridge-roborock-matter",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.8.0",
|
|
4
4
|
"description": "The most complete Roborock plugin for Apple Home. Supports the entire Roborock lineup — from the classic S-series to the new 2025 Q7 series that no other plugin can control. Sign in with your Roborock account and get native start/stop, room cleaning, suction levels, battery, and live 'cleaning in the kitchen' room tracking. Verified by Homebridge.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": {
|