Skip to content

Commit d6758cf

Browse files
authored
Merge staging into master for 1.7.7
Release 1.7.7
2 parents 7add5e1 + a8beb58 commit d6758cf

17 files changed

Lines changed: 290 additions & 133 deletions

AGENTS.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -111,9 +111,11 @@ Interpretation:
111111
| `staging` | Entwicklung, Standardziel fuer laufende Arbeit |
112112
| `master` | Production, nur via PR |
113113

114-
### Commit- und Push-Regeln
114+
### Test-, Commit- und Push-Regeln
115115

116-
Vor jedem Commit alle Checks ausfuehren:
116+
Waehrend laufender Arbeit gezielt pruefen: passende Unit-/Smoke-Tests, Build oder Lint nach Risiko und Aenderungsumfang auswaehlen. Die volle Suite ist kein Pflichtschritt nach jedem kleinen Zwischenstand.
117+
118+
Vor jedem Commit bleiben alle Checks Pflicht:
117119

118120
```bash
119121
npm run build

CLAUDE.md

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -87,29 +87,27 @@ Interpretation:
8787
| `staging` | Entwicklung - hierhin committen |
8888
| `master` | Production - nur via PR |
8989

90-
### Commit-Regeln
90+
### Test- und Commit-Regeln
9191
```bash
9292
# VOR jedem Commit: ALLE Checks durchführen!
9393
npm run build # 1. Build muss fehlerfrei sein
94-
npx eslint . # 2. Lint: 0 Errors (Warnings OK)
94+
npm run lint # 2. Lint: 0 Errors (Warnings OK)
9595
npm run test # 3. ALLE Tests müssen grün sein
96-
97-
# Nach JEDER Änderung sofort committen (NICHT pushen!):
98-
git add . && git commit -m "description"
9996
```
10097

98+
Während laufender Arbeit gezielt prüfen: passende Unit-/Smoke-Tests, Build oder Lint nach Risiko und Änderungsumfang auswählen. Die volle Suite ist kein Pflichtschritt nach jedem kleinen Zwischenstand.
99+
101100
**IMMER vor Commit:**
102101
- `npm run build` ausführen — muss fehlerfrei sein
103-
- `npx eslint .` ausführen — 0 Errors (Warnings sind OK)
102+
- `npm run lint` ausführen — 0 Errors (Warnings sind OK)
104103
- `npm run test` ausführen — ALLE Tests müssen grün sein
105104
- Erst dann committen
106105

107106
**NIEMALS:**
108107
- Direkt auf `master` committen
109108
- Selbstständig zu `master` mergen
110109
- Selbstständig pushen (nur wenn User es explizit verlangt)
111-
- Mehrere Änderungen sammeln
112-
- Committen ohne vorherigen Build-Check
110+
- Committen, wenn Build, Lint oder Tests fehlschlagen
113111

114112
### Merge zu Master (nur wenn User es verlangt!)
115113
```bash

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
</p>
2020

2121
<p>
22-
<a href="https://github.com/Sportinger/MasterSelects/releases"><img src="https://img.shields.io/badge/version-1.7.6-blue.svg" alt="Version"></a>
22+
<a href="https://github.com/Sportinger/MasterSelects/releases"><img src="https://img.shields.io/badge/version-1.7.7-blue.svg" alt="Version"></a>
2323
<a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-green.svg" alt="License"></a>
2424
<a href="https://app.fossa.com/projects/custom%2b61097%2fmasterselects"><img src="https://app.fossa.com/api/projects/custom%2b61097%2fmasterselects.svg?type=shield" alt="FOSSA Status"></a>
2525
</p>

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "masterselects",
33
"private": true,
4-
"version": "1.7.6",
4+
"version": "1.7.7",
55
"type": "module",
66
"scripts": {
77
"dev": "vite",

src/App.css

Lines changed: 18 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -6443,6 +6443,24 @@ input[type="checkbox"] {
64436443
white-space: nowrap;
64446444
}
64456445

6446+
.timeline-ruler-cache-indicator {
6447+
position: absolute;
6448+
bottom: 1px;
6449+
height: 3px;
6450+
background: var(--amber-300);
6451+
opacity: 0.82;
6452+
cursor: pointer;
6453+
z-index: 2;
6454+
}
6455+
6456+
.timeline-ruler-cache-indicator.cache {
6457+
box-shadow: 0 0 5px rgba(251, 191, 36, 0.32);
6458+
}
6459+
6460+
.timeline-ruler-cache-indicator.proxy {
6461+
box-shadow: 0 0 5px rgba(251, 191, 36, 0.32);
6462+
}
6463+
64466464
/* Track lanes */
64476465
.track-lane {
64486466
position: relative;
@@ -8905,28 +8923,6 @@ input[type="checkbox"] {
89058923
z-index: 89;
89068924
}
89078925

8908-
/* Proxy frame cache indicator (yellow line for proxy frames loaded in memory) */
8909-
.proxy-cache-indicator {
8910-
position: absolute;
8911-
top: 3px;
8912-
height: 3px;
8913-
background: var(--amber-300);
8914-
opacity: 0.7;
8915-
pointer-events: none;
8916-
z-index: 88;
8917-
}
8918-
8919-
/* Non-proxy scrub cache indicator (yellow line for background-filled source frames) */
8920-
.scrub-cache-indicator {
8921-
position: absolute;
8922-
top: 6px;
8923-
height: 3px;
8924-
background: var(--amber-300);
8925-
opacity: 0.5;
8926-
pointer-events: none;
8927-
z-index: 87;
8928-
}
8929-
89308926
/* Timeline export progress overlay */
89318927
.timeline-export-overlay {
89328928
position: absolute;

src/changelog-data.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,12 @@
11
[
2+
{
3+
"date": "2026-05-09",
4+
"type": "improve",
5+
"title": "Timeline Cache Indicators and Slider Ranges",
6+
"description": "Proxy and scrub cache range indicators now render directly in the timeline ruler, while persisted custom ranges update matching color, effect, and blendshape sliders immediately.",
7+
"section": "Timeline / Controls",
8+
"commits": ["103c205e"]
9+
},
210
{
311
"date": "2026-05-08",
412
"type": "fix",

src/components/common/EditableDraggableNumber.tsx

Lines changed: 21 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,14 @@
11
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
22
import type { CSSProperties } from 'react';
33
import { createPortal } from 'react-dom';
4-
5-
const SETTINGS_STORAGE_PREFIX = 'editable-draggable-number-settings:';
6-
const LEGACY_BOUNDS_STORAGE_PREFIX = 'editable-draggable-number-bounds:';
7-
8-
interface PersistedSettings {
9-
min?: number;
10-
max?: number;
11-
defaultValue?: number;
12-
}
4+
import {
5+
clearEditableDraggableNumberSettings,
6+
dispatchEditableDraggableNumberSettingsUpdated,
7+
getEditableDraggableNumberSettings,
8+
saveEditableDraggableNumberSettings,
9+
type EditableDraggableNumberSettings,
10+
useEditableDraggableNumberSettingsRevision,
11+
} from './EditableDraggableNumberSettings';
1312

1413
interface PopoverPlacement {
1514
top: number;
@@ -82,32 +81,6 @@ function parseOptionalNumber(value: string): number | undefined {
8281
return Number.isFinite(parsed) ? parsed : undefined;
8382
}
8483

85-
function loadPersistedSettings(persistenceKey?: string): PersistedSettings | null {
86-
if (!persistenceKey) return null;
87-
try {
88-
const raw = localStorage.getItem(`${SETTINGS_STORAGE_PREFIX}${persistenceKey}`);
89-
const legacyRaw = localStorage.getItem(`${LEGACY_BOUNDS_STORAGE_PREFIX}${persistenceKey}`);
90-
const source = raw ?? legacyRaw;
91-
if (!source) return null;
92-
const parsed = JSON.parse(source) as PersistedSettings;
93-
return {
94-
min: Number.isFinite(parsed.min) ? parsed.min : undefined,
95-
max: Number.isFinite(parsed.max) ? parsed.max : undefined,
96-
defaultValue: Number.isFinite(parsed.defaultValue) ? parsed.defaultValue : undefined,
97-
};
98-
} catch {
99-
return null;
100-
}
101-
}
102-
103-
function savePersistedSettings(persistenceKey: string, settings: PersistedSettings): void {
104-
localStorage.setItem(`${SETTINGS_STORAGE_PREFIX}${persistenceKey}`, JSON.stringify(settings));
105-
}
106-
107-
function clearPersistedSettings(persistenceKey: string): void {
108-
localStorage.removeItem(`${SETTINGS_STORAGE_PREFIX}${persistenceKey}`);
109-
}
110-
11184
export function EditableDraggableNumber({
11285
value,
11386
onChange,
@@ -136,17 +109,17 @@ export function EditableDraggableNumber({
136109
const [showBoundsPopover, setShowBoundsPopover] = useState(false);
137110
const [draftMin, setDraftMin] = useState('');
138111
const [draftMax, setDraftMax] = useState('');
139-
const [persistedSettings, setPersistedSettings] = useState<PersistedSettings | null>(() => loadPersistedSettings(persistenceKey));
112+
const [transientSettings, setTransientSettings] = useState<EditableDraggableNumberSettings | null>(null);
113+
useEditableDraggableNumberSettingsRevision(persistenceKey);
140114
const [popoverPlacement, setPopoverPlacement] = useState<PopoverPlacement>({
141115
top: 0,
142116
left: 0,
143117
transformOrigin: 'center bottom',
144118
visibility: 'hidden',
145119
});
146120

147-
useEffect(() => {
148-
setPersistedSettings(loadPersistedSettings(persistenceKey));
149-
}, [persistenceKey]);
121+
const storedSettings = getEditableDraggableNumberSettings(persistenceKey);
122+
const persistedSettings = persistenceKey ? storedSettings : transientSettings;
150123

151124
const effectiveMin = persistedSettings?.min ?? min;
152125
const effectiveMax = persistedSettings?.max ?? max;
@@ -370,15 +343,17 @@ export function EditableDraggableNumber({
370343
? clampValue(nextDefaultValueInput, nextMin, nextMax)
371344
: undefined;
372345

373-
const nextSettings: PersistedSettings = {
346+
const nextSettings: EditableDraggableNumberSettings = {
374347
min: nextMin,
375348
max: nextMax,
376349
defaultValue: nextDefaultValue,
377350
};
378351

379-
setPersistedSettings(nextSettings);
380352
if (persistenceKey) {
381-
savePersistedSettings(persistenceKey, nextSettings);
353+
saveEditableDraggableNumberSettings(persistenceKey, nextSettings);
354+
dispatchEditableDraggableNumberSettingsUpdated(persistenceKey);
355+
} else {
356+
setTransientSettings(nextSettings);
382357
}
383358

384359
const clampedCurrent = clampValue(value, nextSettings.min, nextSettings.max);
@@ -391,7 +366,6 @@ export function EditableDraggableNumber({
391366
}, [decimals, draftDefaultValue, draftMax, draftMin, onChange, persistenceKey, value]);
392367

393368
const resetBounds = useCallback(() => {
394-
setPersistedSettings(null);
395369
setDraftDefaultValue(
396370
defaultValue !== undefined
397371
? formatEditableValue(defaultValue, decimals)
@@ -400,7 +374,10 @@ export function EditableDraggableNumber({
400374
setDraftMin(min !== undefined ? String(min) : '');
401375
setDraftMax(max !== undefined ? String(max) : '');
402376
if (persistenceKey) {
403-
clearPersistedSettings(persistenceKey);
377+
clearEditableDraggableNumberSettings(persistenceKey);
378+
dispatchEditableDraggableNumberSettingsUpdated(persistenceKey);
379+
} else {
380+
setTransientSettings(null);
404381
}
405382
}, [decimals, defaultValue, max, min, persistenceKey]);
406383

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
import { useEffect, useState } from 'react';
2+
3+
const SETTINGS_STORAGE_PREFIX = 'editable-draggable-number-settings:';
4+
const LEGACY_BOUNDS_STORAGE_PREFIX = 'editable-draggable-number-bounds:';
5+
6+
export const EDITABLE_DRAGGABLE_NUMBER_SETTINGS_EVENT = 'editable-draggable-number-settings-updated';
7+
8+
export interface EditableDraggableNumberSettings {
9+
min?: number;
10+
max?: number;
11+
defaultValue?: number;
12+
}
13+
14+
export interface EditableDraggableNumberSettingsEventDetail {
15+
persistenceKey?: string;
16+
}
17+
18+
export function getEditableDraggableNumberSettings(
19+
persistenceKey?: string,
20+
): EditableDraggableNumberSettings | null {
21+
if (!persistenceKey) return null;
22+
try {
23+
const raw = localStorage.getItem(`${SETTINGS_STORAGE_PREFIX}${persistenceKey}`);
24+
const legacyRaw = localStorage.getItem(`${LEGACY_BOUNDS_STORAGE_PREFIX}${persistenceKey}`);
25+
const source = raw ?? legacyRaw;
26+
if (!source) return null;
27+
const parsed = JSON.parse(source) as EditableDraggableNumberSettings;
28+
return {
29+
min: Number.isFinite(parsed.min) ? parsed.min : undefined,
30+
max: Number.isFinite(parsed.max) ? parsed.max : undefined,
31+
defaultValue: Number.isFinite(parsed.defaultValue) ? parsed.defaultValue : undefined,
32+
};
33+
} catch {
34+
return null;
35+
}
36+
}
37+
38+
export function getEffectiveEditableDraggableNumberSettings({
39+
persistenceKey,
40+
min,
41+
max,
42+
defaultValue,
43+
}: {
44+
persistenceKey?: string;
45+
min?: number;
46+
max?: number;
47+
defaultValue?: number;
48+
}): EditableDraggableNumberSettings {
49+
const persistedSettings = getEditableDraggableNumberSettings(persistenceKey);
50+
return {
51+
min: persistedSettings?.min ?? min,
52+
max: persistedSettings?.max ?? max,
53+
defaultValue: persistedSettings?.defaultValue ?? defaultValue,
54+
};
55+
}
56+
57+
export function saveEditableDraggableNumberSettings(
58+
persistenceKey: string,
59+
settings: EditableDraggableNumberSettings,
60+
): void {
61+
localStorage.setItem(`${SETTINGS_STORAGE_PREFIX}${persistenceKey}`, JSON.stringify(settings));
62+
}
63+
64+
export function clearEditableDraggableNumberSettings(persistenceKey: string): void {
65+
localStorage.removeItem(`${SETTINGS_STORAGE_PREFIX}${persistenceKey}`);
66+
localStorage.removeItem(`${LEGACY_BOUNDS_STORAGE_PREFIX}${persistenceKey}`);
67+
}
68+
69+
export function dispatchEditableDraggableNumberSettingsUpdated(persistenceKey: string): void {
70+
window.dispatchEvent(
71+
new CustomEvent<EditableDraggableNumberSettingsEventDetail>(
72+
EDITABLE_DRAGGABLE_NUMBER_SETTINGS_EVENT,
73+
{ detail: { persistenceKey } },
74+
),
75+
);
76+
}
77+
78+
export function useEditableDraggableNumberSettingsRevision(persistenceKey?: string): number {
79+
const [revision, setRevision] = useState(0);
80+
81+
useEffect(() => {
82+
const shouldRefresh = (updatedKey?: string | null) =>
83+
!persistenceKey || !updatedKey || updatedKey === persistenceKey;
84+
85+
const handleSettingsUpdated = (event: Event) => {
86+
const detail = (event as CustomEvent<EditableDraggableNumberSettingsEventDetail>).detail;
87+
if (shouldRefresh(detail?.persistenceKey)) {
88+
setRevision((current) => current + 1);
89+
}
90+
};
91+
92+
const handleStorage = (event: StorageEvent) => {
93+
if (!persistenceKey) {
94+
setRevision((current) => current + 1);
95+
return;
96+
}
97+
98+
if (
99+
event.key === `${SETTINGS_STORAGE_PREFIX}${persistenceKey}` ||
100+
event.key === `${LEGACY_BOUNDS_STORAGE_PREFIX}${persistenceKey}`
101+
) {
102+
setRevision((current) => current + 1);
103+
}
104+
};
105+
106+
window.addEventListener(EDITABLE_DRAGGABLE_NUMBER_SETTINGS_EVENT, handleSettingsUpdated);
107+
window.addEventListener('storage', handleStorage);
108+
109+
return () => {
110+
window.removeEventListener(EDITABLE_DRAGGABLE_NUMBER_SETTINGS_EVENT, handleSettingsUpdated);
111+
window.removeEventListener('storage', handleStorage);
112+
};
113+
}, [persistenceKey]);
114+
115+
return revision;
116+
}

0 commit comments

Comments
 (0)