@nonamelego/dsh-catppuccin 0.5.5 → 0.5.6-beta.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/lib/client.js +108 -18
- package/lib/client.js.map +1 -1
- package/lib/index.js +1 -1
- package/lib/index.js.map +1 -1
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -134,6 +134,7 @@ window.__ModuleLoader__.load({
|
|
|
134
134
|
const style = (0, react.useSyncExternalStore)(subscribePrefs, shikiStyle);
|
|
135
135
|
const [overridesOpen, setOverridesOpen] = (0, react.useState)(false);
|
|
136
136
|
const [draftRows, setDraftRows] = (0, react.useState)([]);
|
|
137
|
+
const nextDraftId = (0, react.useRef)(0);
|
|
137
138
|
const isTokenKey = (key) => key.trim().startsWith("--");
|
|
138
139
|
/** Commit a draft row on blur. If both fields are valid, promote to persisted;
|
|
139
140
|
* otherwise keep in draft state. Item XX: changed from onChange to onBlur so
|
|
@@ -457,11 +458,13 @@ window.__ModuleLoader__.load({
|
|
|
457
458
|
children: "✕"
|
|
458
459
|
})
|
|
459
460
|
]
|
|
460
|
-
},
|
|
461
|
+
}, row.id)),
|
|
461
462
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
462
463
|
type: "button",
|
|
463
464
|
onClick: () => {
|
|
465
|
+
nextDraftId.current += 1;
|
|
464
466
|
setDraftRows([...draftRows, {
|
|
467
|
+
id: nextDraftId.current,
|
|
465
468
|
key: "",
|
|
466
469
|
value: ""
|
|
467
470
|
}]);
|
|
@@ -1959,6 +1962,21 @@ window.__ModuleLoader__.load({
|
|
|
1959
1962
|
for (const [key, value] of Object.entries(input)) if (typeof value === "string" && key.startsWith("--")) out[key] = value;
|
|
1960
1963
|
return out;
|
|
1961
1964
|
}
|
|
1965
|
+
/**
|
|
1966
|
+
* Whether a raw overrides map carries entries {@link sanitizeOverrides} would
|
|
1967
|
+
* drop — a key that is not a `--` token, or a non-string value.
|
|
1968
|
+
*
|
|
1969
|
+
* The settings-document schema types `overrides` as a dict of strings only, so
|
|
1970
|
+
* a hand-edited document can hold such entries. The client reads them away but
|
|
1971
|
+
* never wrote the cleaned map back, so every settings publish re-ran the
|
|
1972
|
+
* "document wins" adoption for a difference nothing could clear (audit F7).
|
|
1973
|
+
* The caller uses this to push the sanitized shape back once, so the document
|
|
1974
|
+
* converges instead of drifting forever.
|
|
1975
|
+
*/
|
|
1976
|
+
function hasUnpersistableOverrides(raw) {
|
|
1977
|
+
if (typeof raw !== "object" || raw === null) return false;
|
|
1978
|
+
return Object.entries(raw).some(([key, value]) => typeof value !== "string" || !key.startsWith("--"));
|
|
1979
|
+
}
|
|
1962
1980
|
/** Clamp a finite number into [min, max]; non-finite values fall back. */
|
|
1963
1981
|
function clampFinite(value, min, max, fallback) {
|
|
1964
1982
|
return typeof value === "number" && Number.isFinite(value) ? Math.min(max, Math.max(min, value)) : fallback;
|
|
@@ -2057,6 +2075,13 @@ window.__ModuleLoader__.load({
|
|
|
2057
2075
|
frost: "dsh.catppuccin.glass.frost",
|
|
2058
2076
|
brightness: "dsh.catppuccin.glass.brightness"
|
|
2059
2077
|
};
|
|
2078
|
+
/** The localStorage names of the numeric knobs — the reverse of
|
|
2079
|
+
* `NUMERIC_KEYS`. Used by the cross-tab `storage` handler: the event's `key`
|
|
2080
|
+
* is a localStorage name (`dsh.catppuccin.glass.blur`), so it must be matched
|
|
2081
|
+
* against the VALUES, never with `key in NUMERIC_KEYS` (that tests the object's
|
|
2082
|
+
* property names — `blur`/`frost`/`brightness` — and is false for every real
|
|
2083
|
+
* storage key, which silently killed cross-tab knob sync; audit F1). */
|
|
2084
|
+
const NUMERIC_STORAGE_KEYS = Object.values(NUMERIC_KEYS);
|
|
2060
2085
|
const MODE_KEY = "dsh.catppuccin.glass.mode";
|
|
2061
2086
|
/** Clamp a numeric knob into its sane range. */
|
|
2062
2087
|
function clampSetting(key, value) {
|
|
@@ -2156,7 +2181,7 @@ window.__ModuleLoader__.load({
|
|
|
2156
2181
|
} else if (event.key === "dsh.catppuccin.glass.enabled") {
|
|
2157
2182
|
this.enabled = readEnabled();
|
|
2158
2183
|
this.sync();
|
|
2159
|
-
} else if (event.key === MODE_KEY || event.key
|
|
2184
|
+
} else if (event.key === MODE_KEY || NUMERIC_STORAGE_KEYS.includes(event.key)) {
|
|
2160
2185
|
this.reloadSettings();
|
|
2161
2186
|
if (this.enabled) this.applySettings();
|
|
2162
2187
|
this.publish();
|
|
@@ -2446,8 +2471,10 @@ window.__ModuleLoader__.load({
|
|
|
2446
2471
|
const { label, value, options, onSelect } = props;
|
|
2447
2472
|
const [focused, setFocused] = (0, react.useState)(null);
|
|
2448
2473
|
const refs = (0, react.useRef)(/* @__PURE__ */ new Map());
|
|
2474
|
+
const matchesValue = options.some((option) => option.id === value);
|
|
2475
|
+
const anchor = focused ?? (matchesValue ? value : options[0]?.id);
|
|
2449
2476
|
const moveFocus = (direction) => {
|
|
2450
|
-
const current =
|
|
2477
|
+
const current = anchor;
|
|
2451
2478
|
const index = options.findIndex((option) => option.id === current);
|
|
2452
2479
|
let next;
|
|
2453
2480
|
if (direction === "home") next = 0;
|
|
@@ -2491,7 +2518,7 @@ window.__ModuleLoader__.load({
|
|
|
2491
2518
|
type: "button",
|
|
2492
2519
|
className: option.id === value ? GlassRow_module_css_default.segActive : GlassRow_module_css_default.seg,
|
|
2493
2520
|
"aria-pressed": option.id === value,
|
|
2494
|
-
tabIndex: option.id ===
|
|
2521
|
+
tabIndex: option.id === anchor ? 0 : -1,
|
|
2495
2522
|
onFocus: () => {
|
|
2496
2523
|
setFocused(option.id);
|
|
2497
2524
|
},
|
|
@@ -2807,13 +2834,13 @@ window.__ModuleLoader__.load({
|
|
|
2807
2834
|
}
|
|
2808
2835
|
setRetryRemaining(null);
|
|
2809
2836
|
};
|
|
2810
|
-
const runCheck = async (withChannel
|
|
2837
|
+
const runCheck = async (withChannel) => {
|
|
2811
2838
|
cancelRetry();
|
|
2812
2839
|
setPhase("checking");
|
|
2813
2840
|
setCopied(false);
|
|
2814
2841
|
let next;
|
|
2815
2842
|
try {
|
|
2816
|
-
next = await check(withChannel);
|
|
2843
|
+
next = await check(withChannel ?? channel());
|
|
2817
2844
|
} catch {
|
|
2818
2845
|
next = {
|
|
2819
2846
|
ok: false,
|
|
@@ -3359,6 +3386,39 @@ window.__ModuleLoader__.load({
|
|
|
3359
3386
|
pendingTimer = void 0;
|
|
3360
3387
|
pendingWrite = void 0;
|
|
3361
3388
|
}
|
|
3389
|
+
/**
|
|
3390
|
+
* Capture-once holder for the revision a debounced write is based on (audit
|
|
3391
|
+
* F2, 2026-09-22).
|
|
3392
|
+
*
|
|
3393
|
+
* The read-side guard in {@link persistStateToScope} only means anything if
|
|
3394
|
+
* `baseRevision` is the revision the LOCAL state was derived from — i.e.
|
|
3395
|
+
* sampled when the change was SCHEDULED. Reading it inside the flush instead
|
|
3396
|
+
* made the comparison a tautology: the flush and the guard's own
|
|
3397
|
+
* `scope.getSnapshot()` run in the same synchronous block, so the two
|
|
3398
|
+
* revisions are always equal and `'stale'` is unreachable. The effect was a
|
|
3399
|
+
* silently lost read-side protection: with two windows open, an external edit
|
|
3400
|
+
* landing inside the 300 ms debounce window was overwritten by the stale local
|
|
3401
|
+
* state and the conflict banner never appeared.
|
|
3402
|
+
*
|
|
3403
|
+
* `capture` keeps the FIRST revision of a burst (`??=`, not assignment): a
|
|
3404
|
+
* slider drag emits several changes, and the local state each of them is based
|
|
3405
|
+
* on is still the one that was current when the burst started. `take` clears
|
|
3406
|
+
* the slot as it reads, so the next burst starts fresh — including when the
|
|
3407
|
+
* flush bails out (unusable scope), where the value must not leak forward.
|
|
3408
|
+
*/
|
|
3409
|
+
function createBaseRevisionTracker() {
|
|
3410
|
+
let pending;
|
|
3411
|
+
return {
|
|
3412
|
+
capture(read) {
|
|
3413
|
+
if (pending === void 0) pending = read();
|
|
3414
|
+
},
|
|
3415
|
+
take() {
|
|
3416
|
+
const value = pending;
|
|
3417
|
+
pending = void 0;
|
|
3418
|
+
return value;
|
|
3419
|
+
}
|
|
3420
|
+
};
|
|
3421
|
+
}
|
|
3362
3422
|
//#endregion
|
|
3363
3423
|
//#region src/client/index.ts
|
|
3364
3424
|
/** Locale namespace owned by this plugin. */
|
|
@@ -3396,6 +3456,21 @@ window.__ModuleLoader__.load({
|
|
|
3396
3456
|
return "off";
|
|
3397
3457
|
}
|
|
3398
3458
|
}
|
|
3459
|
+
/** Whether localStorage carries an EXPLICIT `off` flavour choice.
|
|
3460
|
+
*
|
|
3461
|
+
* `readFlavor()` maps both "no value" and "off" to `off`, and the two are not
|
|
3462
|
+
* the same thing: DSH Desktop boots the GUI on a fresh random loopback port, so
|
|
3463
|
+
* its localStorage starts empty on every launch and the settings document —
|
|
3464
|
+
* which may legitimately hold a flavour — is what should win. Only a literal
|
|
3465
|
+
* `off` in storage proves the user (or another window) actually turned the
|
|
3466
|
+
* flavour off, which is the case the cross-tab restore must act on (audit F4). */
|
|
3467
|
+
function readExplicitFlavorOff() {
|
|
3468
|
+
try {
|
|
3469
|
+
return localStorage.getItem(FLAVOR_STORAGE_KEY) === "off";
|
|
3470
|
+
} catch {
|
|
3471
|
+
return false;
|
|
3472
|
+
}
|
|
3473
|
+
}
|
|
3399
3474
|
/** Persist the flavour choice (storage failures keep the in-memory state). */
|
|
3400
3475
|
function writeFlavor(choice) {
|
|
3401
3476
|
try {
|
|
@@ -3637,11 +3712,15 @@ window.__ModuleLoader__.load({
|
|
|
3637
3712
|
};
|
|
3638
3713
|
let lastWrittenSection;
|
|
3639
3714
|
let handleStaleConflict;
|
|
3715
|
+
const baseRevisionTracker = createBaseRevisionTracker();
|
|
3716
|
+
const queuePersist = () => {
|
|
3717
|
+
baseRevisionTracker.capture(() => scope.getSnapshot().revision);
|
|
3718
|
+
scheduleDurablePersist(persistLocal);
|
|
3719
|
+
};
|
|
3640
3720
|
const persistLocal = () => {
|
|
3641
|
-
const
|
|
3642
|
-
if (!isScopeUsable(
|
|
3721
|
+
const baseRevision = baseRevisionTracker.take();
|
|
3722
|
+
if (!isScopeUsable(scope.getSnapshot())) return;
|
|
3643
3723
|
const state = buildLocalState();
|
|
3644
|
-
const baseRevision = snapshot.revision;
|
|
3645
3724
|
persistStateToScope(scope, state, {
|
|
3646
3725
|
baseRevision,
|
|
3647
3726
|
lastWrittenSection
|
|
@@ -3682,7 +3761,11 @@ window.__ModuleLoader__.load({
|
|
|
3682
3761
|
const applyDesired = () => {
|
|
3683
3762
|
rememberBuiltinPreference(theme.getTheme().preference);
|
|
3684
3763
|
const desired = readFlavor();
|
|
3685
|
-
if (desired === "off")
|
|
3764
|
+
if (desired === "off") {
|
|
3765
|
+
const preference = theme.getTheme().preference;
|
|
3766
|
+
if (readExplicitFlavorOff() && flavorFromThemeId(preference) !== "off") scheduleRestore(readRestoredPreference());
|
|
3767
|
+
return;
|
|
3768
|
+
}
|
|
3686
3769
|
const preference = theme.getTheme().preference;
|
|
3687
3770
|
if (preference === desired) return;
|
|
3688
3771
|
if (builtinPickWins(preference, liveBuiltinPick)) return;
|
|
@@ -3698,11 +3781,16 @@ window.__ModuleLoader__.load({
|
|
|
3698
3781
|
try {
|
|
3699
3782
|
theme.setTheme(next);
|
|
3700
3783
|
} catch {}
|
|
3784
|
+
return;
|
|
3701
3785
|
}
|
|
3786
|
+
if (flavorFromThemeId(theme.getTheme().preference) === "off") return;
|
|
3787
|
+
try {
|
|
3788
|
+
theme.setTheme(readRestoredPreference());
|
|
3789
|
+
} catch {}
|
|
3702
3790
|
};
|
|
3703
3791
|
window.addEventListener("storage", onStorage);
|
|
3704
3792
|
const offGlass = glass.subscribe(() => {
|
|
3705
|
-
|
|
3793
|
+
queuePersist();
|
|
3706
3794
|
});
|
|
3707
3795
|
const applyScopeSnapshot = () => {
|
|
3708
3796
|
const snapshot = scope.getSnapshot();
|
|
@@ -3710,9 +3798,10 @@ window.__ModuleLoader__.load({
|
|
|
3710
3798
|
if (state === null) return;
|
|
3711
3799
|
const local = buildLocalState();
|
|
3712
3800
|
if (snapshot.user === void 0) {
|
|
3713
|
-
if (!isDefaultState(local))
|
|
3801
|
+
if (!isDefaultState(local)) queuePersist();
|
|
3714
3802
|
return;
|
|
3715
3803
|
}
|
|
3804
|
+
if (hasUnpersistableOverrides(snapshot.value?.overrides)) queuePersist();
|
|
3716
3805
|
if (settingsSectionsEqual(settingsSectionFromState(local), settingsSectionFromState(state))) return;
|
|
3717
3806
|
writeFlavor(state.flavor);
|
|
3718
3807
|
glass.applyRemote(state.glass);
|
|
@@ -3776,7 +3865,7 @@ window.__ModuleLoader__.load({
|
|
|
3776
3865
|
if (choice === "off") {
|
|
3777
3866
|
writeFlavor("off");
|
|
3778
3867
|
theme.setTheme(readRestoredPreference());
|
|
3779
|
-
|
|
3868
|
+
queuePersist();
|
|
3780
3869
|
return;
|
|
3781
3870
|
}
|
|
3782
3871
|
const flavor = flavorInfo(choice);
|
|
@@ -3784,21 +3873,21 @@ window.__ModuleLoader__.load({
|
|
|
3784
3873
|
writeFlavor(flavor.themeId);
|
|
3785
3874
|
ensureThemeRegistered(flavor.themeId);
|
|
3786
3875
|
theme.setTheme(flavor.themeId);
|
|
3787
|
-
|
|
3876
|
+
queuePersist();
|
|
3788
3877
|
},
|
|
3789
3878
|
overrides: overridesSnapshot,
|
|
3790
3879
|
setOverrides: (overrides) => {
|
|
3791
3880
|
writeOverrides(overrides);
|
|
3792
3881
|
emitPrefs();
|
|
3793
3882
|
reapplyThemePrefs();
|
|
3794
|
-
|
|
3883
|
+
queuePersist();
|
|
3795
3884
|
},
|
|
3796
3885
|
shikiStyle: () => readShikiStyle(),
|
|
3797
3886
|
setShikiStyle: (style) => {
|
|
3798
3887
|
writeShikiStyle(style);
|
|
3799
3888
|
emitPrefs();
|
|
3800
3889
|
reapplyThemePrefs();
|
|
3801
|
-
|
|
3890
|
+
queuePersist();
|
|
3802
3891
|
},
|
|
3803
3892
|
subscribePrefs: (listener) => {
|
|
3804
3893
|
prefsListeners.add(listener);
|
|
@@ -3859,14 +3948,14 @@ window.__ModuleLoader__.load({
|
|
|
3859
3948
|
setAutoCheck: (value) => {
|
|
3860
3949
|
writeAutoCheck(value);
|
|
3861
3950
|
emitPrefs();
|
|
3862
|
-
|
|
3951
|
+
queuePersist();
|
|
3863
3952
|
armAutoCheck?.();
|
|
3864
3953
|
},
|
|
3865
3954
|
channel: () => readUpdateChannel(),
|
|
3866
3955
|
setChannel: (channel) => {
|
|
3867
3956
|
writeUpdateChannel(channel);
|
|
3868
3957
|
emitPrefs();
|
|
3869
|
-
|
|
3958
|
+
queuePersist();
|
|
3870
3959
|
},
|
|
3871
3960
|
subscribePrefs: (listener) => {
|
|
3872
3961
|
prefsListeners.add(listener);
|
|
@@ -3909,6 +3998,7 @@ window.__ModuleLoader__.load({
|
|
|
3909
3998
|
exports.inject = inject;
|
|
3910
3999
|
exports.overridesSnapshot = overridesSnapshot;
|
|
3911
4000
|
exports.readAutoCheck = readAutoCheck;
|
|
4001
|
+
exports.readExplicitFlavorOff = readExplicitFlavorOff;
|
|
3912
4002
|
exports.readFlavor = readFlavor;
|
|
3913
4003
|
exports.readOverrides = readOverrides;
|
|
3914
4004
|
exports.readRestoredPreference = readRestoredPreference;
|