hyper-windowtint 0.3.2 → 0.3.5
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 +46 -0
- package/index.js +49 -136
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,52 @@ All notable changes to `hyper-windowtint` will be documented here.
|
|
|
5
5
|
The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.3.5] - 2026-05-16
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
- Color collisions across projects. Previously every project root got an
|
|
12
|
+
independent random seed, and two random seeds could hash to the same
|
|
13
|
+
palette index — so two unrelated projects regularly came out the same
|
|
14
|
+
color. `seedForProjectRoot` now tracks which palette indices are in use
|
|
15
|
+
by other active projects and rolls candidate seeds until it finds one
|
|
16
|
+
that hashes to an unused slot. The first 12 distinct project roots in
|
|
17
|
+
a Hyper session are now guaranteed to get 12 distinct colors; only
|
|
18
|
+
projects 13+ have to collide.
|
|
19
|
+
|
|
20
|
+
## [0.3.4] - 2026-05-16
|
|
21
|
+
|
|
22
|
+
### Changed
|
|
23
|
+
- README now references real screenshots in `docs/` instead of a placeholder.
|
|
24
|
+
|
|
25
|
+
### Removed
|
|
26
|
+
- Dead code left over from 0.3.3's `decorateTab` removal: the `uidToColor`
|
|
27
|
+
map, the `tintVersion` counter and its `WINDOWTINT_COLOR_CHANGE` dispatch,
|
|
28
|
+
and the `reduceUI` export. None affected runtime; they only existed to
|
|
29
|
+
feed the now-removed per-tab decorations. `index.js` drops from 647 to 622
|
|
30
|
+
lines.
|
|
31
|
+
|
|
32
|
+
## [0.3.3] - 2026-05-16
|
|
33
|
+
|
|
34
|
+
### Changed
|
|
35
|
+
- Corner color-name badge now defaults to **off**. Opt back in with
|
|
36
|
+
`config.windowTint.showBadge: true` in `~/.hyper.js`.
|
|
37
|
+
|
|
38
|
+
### Removed
|
|
39
|
+
- The bottom underline accent and the left/right edge stripes that 0.3.2
|
|
40
|
+
added via `decorateTab`'s `customChildrenBefore`. Hyper 3.x's Tab
|
|
41
|
+
component does not render plugin-injected children, so these decorations
|
|
42
|
+
were never actually painting on most users' screens. The `decorateTab`,
|
|
43
|
+
`getTabProps`, and `mapHeaderState` exports have been removed entirely
|
|
44
|
+
since they only served those non-rendering decorations.
|
|
45
|
+
- The dead-code path that depended on those exports.
|
|
46
|
+
|
|
47
|
+
### Note
|
|
48
|
+
- The window-level signals — colored window border, top line in the tab
|
|
49
|
+
bar, and the active-tab background gradient — all still work and remain
|
|
50
|
+
the primary at-a-glance project indicator. Per-tab outlines for inactive
|
|
51
|
+
tabs are deferred to a future release pending a different mechanism
|
|
52
|
+
(likely a renderer-side DOM observer) that bypasses Hyper's prop-dropping.
|
|
53
|
+
|
|
8
54
|
## [0.3.2] - 2026-05-16
|
|
9
55
|
|
|
10
56
|
### Added
|
package/index.js
CHANGED
|
@@ -99,7 +99,7 @@ function readUserConfig(config) {
|
|
|
99
99
|
return {
|
|
100
100
|
palette: palette.length ? palette : DEFAULT_PALETTE,
|
|
101
101
|
borderWidth: typeof u.borderWidth === 'string' ? u.borderWidth : '3px',
|
|
102
|
-
showBadge: u.showBadge
|
|
102
|
+
showBadge: u.showBadge === true,
|
|
103
103
|
glow: u.glow !== false,
|
|
104
104
|
};
|
|
105
105
|
}
|
|
@@ -200,16 +200,49 @@ async function resolveProjectRootAsync(cwd) {
|
|
|
200
200
|
}
|
|
201
201
|
}
|
|
202
202
|
|
|
203
|
-
function createRandomSeed() {
|
|
204
|
-
return `windowtint:${Date.now().toString(36)}:${Math.random().toString(36).slice(2)}`;
|
|
203
|
+
function createRandomSeed(salt) {
|
|
204
|
+
return `windowtint:${Date.now().toString(36)}:${Math.random().toString(36).slice(2)}${salt != null ? ':' + salt : ''}`;
|
|
205
205
|
}
|
|
206
206
|
|
|
207
|
+
// Main-process palette-size hint for collision avoidance. The user can
|
|
208
|
+
// override the palette in their renderer-side config, but main can't see
|
|
209
|
+
// that, so we assume the default 12-slot palette here. If a user shrinks
|
|
210
|
+
// their palette, the worst case is that we'll still allocate indices 0–11
|
|
211
|
+
// uniformly — they just won't all be reachable in the renderer.
|
|
212
|
+
const COLLISION_AVOIDANCE_PALETTE_SIZE = DEFAULT_PALETTE.length;
|
|
213
|
+
|
|
207
214
|
function seedForProjectRoot(root) {
|
|
208
215
|
if (!root) return root;
|
|
209
|
-
if (
|
|
210
|
-
|
|
216
|
+
if (projectSeedCache.has(root)) return projectSeedCache.get(root);
|
|
217
|
+
|
|
218
|
+
// Figure out which palette indices are currently taken by other projects
|
|
219
|
+
// so we can prefer a slot that isn't already in use.
|
|
220
|
+
const paletteSize = COLLISION_AVOIDANCE_PALETTE_SIZE;
|
|
221
|
+
const usedIndices = new Set();
|
|
222
|
+
projectSeedCache.forEach((existingSeed) => {
|
|
223
|
+
usedIndices.add(hashToIndex(existingSeed, paletteSize));
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
// Brute-force candidate seeds until one hashes to an unused index, or we
|
|
227
|
+
// run out of attempts. Probability of a single attempt being clean is
|
|
228
|
+
// (paletteSize - usedIndices.size) / paletteSize, so 200 attempts is more
|
|
229
|
+
// than enough until ~11 colors are taken.
|
|
230
|
+
let seed = null;
|
|
231
|
+
if (usedIndices.size < paletteSize) {
|
|
232
|
+
for (let i = 0; i < 200; i++) {
|
|
233
|
+
const candidate = createRandomSeed(i);
|
|
234
|
+
if (!usedIndices.has(hashToIndex(candidate, paletteSize))) {
|
|
235
|
+
seed = candidate;
|
|
236
|
+
break;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
211
239
|
}
|
|
212
|
-
|
|
240
|
+
// Fallback: more projects than colors, or 200 attempts didn't find a clean
|
|
241
|
+
// index — just take a random seed and accept the collision.
|
|
242
|
+
if (!seed) seed = createRandomSeed();
|
|
243
|
+
|
|
244
|
+
projectSeedCache.set(root, seed);
|
|
245
|
+
return seed;
|
|
213
246
|
}
|
|
214
247
|
|
|
215
248
|
exports.decorateSessionOptions = (options) => {
|
|
@@ -356,13 +389,9 @@ exports.onUnload = () => {
|
|
|
356
389
|
|
|
357
390
|
// uid → seed, populated by the rpc listener installed lazily inside middleware.
|
|
358
391
|
const uidToSeed = new Map();
|
|
359
|
-
const uidToColor = new Map();
|
|
360
392
|
let rpcListenerInstalled = false;
|
|
361
393
|
let rpcSeedListener = null;
|
|
362
394
|
let currentSeed = null;
|
|
363
|
-
let tintVersion = 0;
|
|
364
|
-
|
|
365
|
-
const WINDOWTINT_COLOR_CHANGE = 'WINDOWTINT_COLOR_CHANGE';
|
|
366
395
|
|
|
367
396
|
function applyTint(color, opts) {
|
|
368
397
|
if (typeof document === 'undefined' || !color) return;
|
|
@@ -378,16 +407,11 @@ function tintForUid(uid) {
|
|
|
378
407
|
const seed = uidToSeed.get(uid) || uid;
|
|
379
408
|
if (seed === currentSeed) return;
|
|
380
409
|
currentSeed = seed;
|
|
381
|
-
|
|
382
|
-
applyTint(color, userOpts);
|
|
383
|
-
uidToColor.set(uid, color);
|
|
410
|
+
applyTint(colorForSeed(seed), userOpts);
|
|
384
411
|
}
|
|
385
412
|
|
|
386
413
|
function setSeedForUid(uid, seed) {
|
|
387
414
|
uidToSeed.set(uid, seed);
|
|
388
|
-
const color = colorForSeed(seed);
|
|
389
|
-
uidToColor.set(uid, color);
|
|
390
|
-
return color;
|
|
391
415
|
}
|
|
392
416
|
|
|
393
417
|
function installRpcListener(store) {
|
|
@@ -399,8 +423,6 @@ function installRpcListener(store) {
|
|
|
399
423
|
try {
|
|
400
424
|
if (!payload || !payload.uid || !payload.seed) return;
|
|
401
425
|
setSeedForUid(payload.uid, payload.seed);
|
|
402
|
-
tintVersion += 1;
|
|
403
|
-
store.dispatch({ type: WINDOWTINT_COLOR_CHANGE, version: tintVersion });
|
|
404
426
|
|
|
405
427
|
// If the seed arrived after the session was already tinted (race —
|
|
406
428
|
// shouldn't happen in practice because we emit seed before `session
|
|
@@ -425,9 +447,7 @@ exports.onRendererUnload = () => {
|
|
|
425
447
|
rpcSeedListener = null;
|
|
426
448
|
rpcListenerInstalled = false;
|
|
427
449
|
uidToSeed.clear();
|
|
428
|
-
uidToColor.clear();
|
|
429
450
|
currentSeed = null;
|
|
430
|
-
tintVersion = 0;
|
|
431
451
|
} catch (e) { /* swallow */ }
|
|
432
452
|
};
|
|
433
453
|
|
|
@@ -590,10 +610,6 @@ exports.decorateConfig = (config) => {
|
|
|
590
610
|
.hyper_main .tab_tab.tab_active {
|
|
591
611
|
background: linear-gradient(180deg, var(--tint-tab-bg, transparent), transparent);
|
|
592
612
|
}
|
|
593
|
-
.hyper_main .tab_tab.tab_active .windowtint_tabAccent {
|
|
594
|
-
height: 3px;
|
|
595
|
-
opacity: 1;
|
|
596
|
-
}
|
|
597
613
|
${badgeCSS}
|
|
598
614
|
`;
|
|
599
615
|
|
|
@@ -602,118 +618,15 @@ exports.decorateConfig = (config) => {
|
|
|
602
618
|
});
|
|
603
619
|
};
|
|
604
620
|
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
// adjacent tabs put their stripes back-to-back at the boundary, so the
|
|
615
|
-
// separator visually shows both projects' colors. The first tab's left
|
|
616
|
-
// edge is often hidden under macOS traffic lights, so doing both sides
|
|
617
|
-
// means the color is always visible somewhere on every tab.
|
|
618
|
-
const stripeStyle = (side) => ({
|
|
619
|
-
position: 'absolute',
|
|
620
|
-
[side]: 0,
|
|
621
|
-
top: 0,
|
|
622
|
-
bottom: 0,
|
|
623
|
-
width: this.props.isActive ? 4 : 3,
|
|
624
|
-
background: hex,
|
|
625
|
-
opacity: color ? 1 : 0,
|
|
626
|
-
boxShadow: color ? `0 0 8px ${withAlpha(color.hex, '44')}` : 'none',
|
|
627
|
-
pointerEvents: 'none',
|
|
628
|
-
transition: 'background 0.2s ease, opacity 0.2s ease, width 0.2s ease, box-shadow 0.2s ease',
|
|
629
|
-
});
|
|
630
|
-
const sideAccentLeft = React.createElement('span', {
|
|
631
|
-
key: 'windowtint-side-left',
|
|
632
|
-
className: 'windowtint_tabAccentSide windowtint_tabAccentSideLeft',
|
|
633
|
-
'data-windowtint-uid': uid,
|
|
634
|
-
style: stripeStyle('left'),
|
|
635
|
-
});
|
|
636
|
-
const sideAccentRight = React.createElement('span', {
|
|
637
|
-
key: 'windowtint-side-right',
|
|
638
|
-
className: 'windowtint_tabAccentSide windowtint_tabAccentSideRight',
|
|
639
|
-
'data-windowtint-uid': uid,
|
|
640
|
-
style: stripeStyle('right'),
|
|
641
|
-
});
|
|
642
|
-
|
|
643
|
-
// Bottom accent — kept for the soft glow under the active tab.
|
|
644
|
-
const bottomAccent = React.createElement('span', {
|
|
645
|
-
key: 'windowtint-bottom',
|
|
646
|
-
className: 'windowtint_tabAccent',
|
|
647
|
-
'data-windowtint-uid': uid,
|
|
648
|
-
style: {
|
|
649
|
-
position: 'absolute',
|
|
650
|
-
left: 0,
|
|
651
|
-
right: 0,
|
|
652
|
-
bottom: 0,
|
|
653
|
-
height: this.props.isActive ? 3 : 2,
|
|
654
|
-
background: hex,
|
|
655
|
-
boxShadow: color ? `0 0 12px ${withAlpha(color.hex, '66')}` : 'none',
|
|
656
|
-
opacity: baseOpacity,
|
|
657
|
-
pointerEvents: 'none',
|
|
658
|
-
transition: 'background 0.2s ease, box-shadow 0.2s ease, opacity 0.2s ease, height 0.2s ease',
|
|
659
|
-
},
|
|
660
|
-
});
|
|
661
|
-
|
|
662
|
-
const existing = this.props.customChildrenBefore;
|
|
663
|
-
const accents = [sideAccentLeft, sideAccentRight, bottomAccent];
|
|
664
|
-
const customChildrenBefore = existing
|
|
665
|
-
? accents.concat(Array.isArray(existing) ? existing : [existing])
|
|
666
|
-
: accents;
|
|
667
|
-
return React.createElement(Tab, Object.assign({}, this.props, { customChildrenBefore }));
|
|
668
|
-
}
|
|
669
|
-
};
|
|
670
|
-
};
|
|
671
|
-
|
|
672
|
-
exports.getTabProps = (tab, parentProps, props) => {
|
|
673
|
-
try {
|
|
674
|
-
if (!tab || !tab.uid) return props;
|
|
675
|
-
const colors = parentProps && parentProps.windowTintTabColors;
|
|
676
|
-
return Object.assign({}, props, {
|
|
677
|
-
windowTintUid: tab.uid,
|
|
678
|
-
windowTintColor: colors && colors[tab.uid] ? colors[tab.uid] : null,
|
|
679
|
-
windowTintVersion: parentProps && parentProps.windowTintVersion,
|
|
680
|
-
});
|
|
681
|
-
} catch (e) {
|
|
682
|
-
return props;
|
|
683
|
-
}
|
|
684
|
-
};
|
|
685
|
-
|
|
686
|
-
exports.mapHeaderState = (state, props) => {
|
|
687
|
-
try {
|
|
688
|
-
const colors = {};
|
|
689
|
-
const activeSessions = state.termGroups && state.termGroups.activeSessions;
|
|
690
|
-
if (activeSessions) {
|
|
691
|
-
Object.keys(activeSessions).forEach((rootGroupUid) => {
|
|
692
|
-
const sessionUid = activeSessions[rootGroupUid];
|
|
693
|
-
const color = uidToColor.get(sessionUid);
|
|
694
|
-
if (color) colors[rootGroupUid] = color;
|
|
695
|
-
});
|
|
696
|
-
}
|
|
697
|
-
return Object.assign({}, props, {
|
|
698
|
-
windowTintTabColors: colors,
|
|
699
|
-
windowTintVersion: state.ui && state.ui.windowTintVersion,
|
|
700
|
-
});
|
|
701
|
-
} catch (e) {
|
|
702
|
-
return props;
|
|
703
|
-
}
|
|
704
|
-
};
|
|
705
|
-
|
|
706
|
-
exports.reduceUI = (state, action) => {
|
|
707
|
-
if (!action || action.type !== WINDOWTINT_COLOR_CHANGE) return state;
|
|
708
|
-
try {
|
|
709
|
-
if (state && typeof state.set === 'function') {
|
|
710
|
-
return state.set('windowTintVersion', action.version);
|
|
711
|
-
}
|
|
712
|
-
return Object.assign({}, state, { windowTintVersion: action.version });
|
|
713
|
-
} catch (e) {
|
|
714
|
-
return state;
|
|
715
|
-
}
|
|
716
|
-
};
|
|
621
|
+
// No `decorateTab` export in this version. Hyper 3.x's Tab component drops
|
|
622
|
+
// most plugin-injected props (`customChildrenBefore`, `style`, `className`,
|
|
623
|
+
// `borderColor` all observed dropped on this user's build), so per-tab
|
|
624
|
+
// decoration via the documented API doesn't actually paint. The window-level
|
|
625
|
+
// CSS variables set by the middleware already communicate the active
|
|
626
|
+
// project's color via the window border, the active-tab background gradient,
|
|
627
|
+
// and the colored top line in the tab bar — that's enough for now.
|
|
628
|
+
// Per-tab outlines for inactive tabs may come back in a future release using
|
|
629
|
+
// a different mechanism (e.g. a renderer-side DOM observer).
|
|
717
630
|
|
|
718
631
|
// Redux middleware: re-tint when sessions are added or switched, using the
|
|
719
632
|
// project-group seed if available, falling back to session UID.
|
package/package.json
CHANGED