@nonamelego/dsh-catppuccin 0.2.7-beta.1 → 0.2.7

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/README.md CHANGED
@@ -22,7 +22,10 @@
22
22
 
23
23
  它内置 Catppuccin 的四个主题——**Latte**、**Frappé**、**Macchiato**、**Mocha**——
24
24
  把整个界面的配色都换成对应的 Catppuccin 色板;并在 **设置 → 常规 → 外观**
25
- 下方提供一行 **Catppuccin** 快捷切换,选择会自动保存、下次启动自动恢复。
25
+ 下方提供一行 **Catppuccin** 快捷切换,选择会自动保存、重启自动恢复——
26
+ 偏好持久保存在 DSH home 下的 `catppuccin-state.json`(浏览器 localStorage
27
+ 作为同机的即时缓存),因此即使在 **DSH Desktop** 里每次启动端口都变化,
28
+ 主题和玻璃设置也能跨重启稳定恢复。
26
29
 
27
30
  同时内置一套可开关的**玻璃质感**(Glassmorphism)皮肤:顶栏、侧边栏、
28
31
  输入框、统计行、轨迹视图、聊天气泡、新会话按钮都变成磨砂玻璃卡片,
@@ -88,6 +91,18 @@ dsh plugin --profile web add @nonamelego/dsh-catppuccin
88
91
  装完重启 `dsh web` 即可,`dsh plugin` 会自动把它加进 profile 的 bundles。
89
92
  其他 profile 把命令里的 `web` 换成对应名字即可(如 `headless`)。
90
93
 
94
+ **[DSH Desktop](https://github.com/anywhere-labs/deepseek-harness-desktop)**:桌面版默认激活的 profile 就叫 `desktop`,把命令里的 `web`
95
+ 换成 `desktop` 即可:
96
+
97
+ ```sh
98
+ dsh plugin --profile desktop add @nonamelego/dsh-catppuccin
99
+ ```
100
+
101
+ 在桌面的 **DSH 终端**里运行即可(终端里的 `dsh plugin` 默认就作用于当前激活的
102
+ profile,也可以直接写 `dsh plugin add @nonamelego/dsh-catppuccin`;若你在托盘里
103
+ 选了别的 profile,就把 `desktop` 换成那个名字),装完重启 **[DSH Desktop](https://github.com/anywhere-labs/deepseek-harness-desktop)** 生效。
104
+ 从仓库安装的方式同理:`dsh plugin --profile desktop add https://github.com/NoNameLeGo/dsh-catppuccin-theme`。
105
+
91
106
  ### 方式二:从仓库安装
92
107
 
93
108
  ```sh
@@ -106,11 +121,12 @@ pnpm --dir C:\Users\LeGo\.dsh\profiles\web add link:D:\Vibe-Coding\dsh-catppucci
106
121
  ```
107
122
 
108
123
  然后在 profile 的 `package.json` 中把 `@nonamelego/dsh-catppuccin` 加进 `dsh.profile.bundles`,
109
- 重启 `dsh web` 即可。
124
+ 重启 `dsh web` 即可。在 [DSH Desktop](https://github.com/anywhere-labs/deepseek-harness-desktop) 中使用时,把上面的路径换成桌面 profile
125
+ (默认 `desktop`,位于 `~/.dsh/profiles/desktop`),装完重启 **[DSH Desktop](https://github.com/anywhere-labs/deepseek-harness-desktop)** 即可。
110
126
 
111
127
  ## 使用
112
128
 
113
- 1. 打开 Web GUI(默认 `http://127.0.0.1:3080`)。
129
+ 1. 打开 Web GUI(默认 `http://127.0.0.1:3080`);在 [DSH Desktop](https://github.com/anywhere-labs/deepseek-harness-desktop) 中则直接打开桌面应用即可。
114
130
  2. 进入 **设置 → 常规**。
115
131
  3. 在 **外观** 区域下方找到 **Catppuccin** 行,选择主题:
116
132
  **Latte**(浅色)、**Frappé**、**Macchiato** 或 **Mocha**(深色)。
@@ -130,7 +146,11 @@ pnpm --dir C:\Users\LeGo\.dsh\profiles\web add link:D:\Vibe-Coding\dsh-catppucci
130
146
  直接调和进纯色背景。
131
147
 
132
148
  玻璃配色取自当前主题的设计 token,切换 Latte / Frappé / Macchiato / Mocha
133
- 时玻璃颜色自动跟随;设置保存在浏览器本地(localStorage),重启自动恢复。
149
+ 时玻璃颜色自动跟随;设置最终持久保存在 DSH home 下的
150
+ `catppuccin-state.json`(同主题一起,跨重启、跨端口恢复),浏览器
151
+ localStorage 只作为即时缓存与多标签页同步——**DSH Desktop** 每次启动使用
152
+ 不同的随机回环端口,localStorage 因此无法跨重启保留,而 DSH home 下的文件
153
+ 不受端口影响,重启后自动恢复。
134
154
 
135
155
  ### 检查 Catppuccin 插件更新
136
156
 
@@ -149,6 +169,9 @@ pnpm --dir C:\Users\LeGo\.dsh\profiles\web add link:D:\Vibe-Coding\dsh-catppucci
149
169
  升级到 `@latest`。
150
170
  - 检查走宿主进程的 Node fetch(无跨域问题);离线或 registry 不可达时
151
171
  显示具体失败原因并支持重试。
172
+ - **环境自适配**:在 [DSH Desktop](https://github.com/anywhere-labs/deepseek-harness-desktop) 中运行时,目标 profile 直接取桌面版的
173
+ 当前 profile(不靠扫描猜测),提示与重启文案也切换为 Desktop 口径
174
+ (DSH 终端);普通 dsh web / CLI 下保持原有终端命令。
152
175
 
153
176
  ## 玻璃拟态(Glassmorphism)
154
177
 
@@ -204,13 +227,18 @@ node scripts/generate-palettes.mjs
204
227
  A: 官方外观行只列出内置的浅色/深色/跟随系统偏好。四个主题在它正下方的
205
228
  **Catppuccin** 行里。
206
229
  - Q: **_"我的主题选择是怎么记住的?"_**
207
- A: 选择保存在浏览器本地(localStorage),下次打开同一浏览器会自动恢复;
208
- 玻璃质感开关与各旋钮同样保存在本地。
230
+ A: 选择持久保存在 DSH home 下的 `catppuccin-state.json`(同一台机器的
231
+ DSH 共享这份偏好),浏览器 localStorage 作为即时缓存与多标签页同步。
232
+ 因此普通 `dsh web`、`dsh web --port` 自定义端口,以及 **DSH Desktop**
233
+ (每次启动用随机回环端口)都能跨重启自动恢复;玻璃质感开关与各旋钮同样
234
+ 持久保存。
209
235
  - Q: **_"怎么知道这个插件有没有新版本?"_**
210
236
  A: 设置 → 常规 → **检查 Catppuccin 插件更新** 一键检测本插件在 npm 上的最新版本,
211
237
  发现新版会给出可复制的升级命令;也可以随时手动执行
212
238
  `dsh plugin --profile web update @nonamelego/dsh-catppuccin`
213
- (或重新 `add` 最新版)。
239
+ (或重新 `add` 最新版)。在 [DSH Desktop](https://github.com/anywhere-labs/deepseek-harness-desktop) 中,把 `web` 换成 `desktop`
240
+ (`dsh plugin --profile desktop update @nonamelego/dsh-catppuccin`),
241
+ 或者直接在 DSH 终端里运行 `dsh plugin update`(默认作用于当前 profile)。
214
242
 
215
243
  ## 💝 致谢
216
244
 
package/lib/client.js CHANGED
@@ -144,7 +144,9 @@ window.__ModuleLoader__.load({
144
144
  "update.available": "发现新版本:",
145
145
  "update.commandHint": "在终端运行以下命令升级(把 web 换成你的 profile 名):",
146
146
  "update.commandHintDetected": "已自动探测到 profile「{profile}」,在终端运行以下命令升级:",
147
+ "update.commandHintDesktop": "已检测到你在使用 DSH Desktop,目标 profile「{profile}」,请在 DSH 终端中运行以下命令升级:",
147
148
  "update.restartHint": "升级完成后重启 dsh web 生效。",
149
+ "update.restartHintDesktop": "升级完成后重启 DSH Desktop 生效。",
148
150
  "update.copy": "复制命令",
149
151
  "update.copied": "已复制 ✓",
150
152
  "update.failed": "检查失败,请稍后重试。",
@@ -190,7 +192,9 @@ window.__ModuleLoader__.load({
190
192
  "update.available": "New version available:",
191
193
  "update.commandHint": "Run this in a terminal to upgrade (replace web with your profile name):",
192
194
  "update.commandHintDetected": "Profile \"{profile}\" detected — run this in a terminal to upgrade:",
195
+ "update.commandHintDesktop": "DSH Desktop detected — target profile \"{profile}\", run this in the DSH terminal to upgrade:",
193
196
  "update.restartHint": "Restart dsh web after the upgrade.",
197
+ "update.restartHintDesktop": "Restart DSH Desktop after the upgrade.",
194
198
  "update.copy": "Copy command",
195
199
  "update.copied": "Copied ✓",
196
200
  "update.failed": "Check failed — try again later.",
@@ -965,7 +969,8 @@ window.__ModuleLoader__.load({
965
969
  * Shipped knob defaults — aligned with the reference project
966
970
  * (DSH-Transparent-UI-Plugin / Aqua): mica mode, blur 2px, frost 20
967
971
  * (the 0-100 slider maps to a 0-1.4 alpha multiplier via frost/50, so 20
968
- * reads as a light frost) and neutral backdrop brightness.
972
+ * reads as a light frost) and neutral backdrop brightness. MUST stay in sync
973
+ * with `DEFAULT_GLASS` in `src/state.ts` (guarded by `tests/state.spec.ts`).
969
974
  */
970
975
  const SETTINGS_DEFAULTS = {
971
976
  mode: "mica",
@@ -1109,6 +1114,34 @@ window.__ModuleLoader__.load({
1109
1114
  this.listeners.delete(listener);
1110
1115
  };
1111
1116
  }
1117
+ /** The durable subset of the current layer state (no derived `dark`). */
1118
+ getRemoteState() {
1119
+ return {
1120
+ enabled: this.enabled,
1121
+ mode: this.settings.mode,
1122
+ blur: this.settings.blur,
1123
+ frost: this.settings.frost,
1124
+ brightness: this.settings.brightness
1125
+ };
1126
+ }
1127
+ /** Overlay durable state hydrated from the Host file: persist it to
1128
+ * localStorage (the cache), apply it to the live layer, and republish so
1129
+ * the settings row reflects the restored choice. */
1130
+ applyRemote(remote) {
1131
+ this.enabled = remote.enabled;
1132
+ this.settings = {
1133
+ mode: remote.mode === "compat" ? "compat" : "mica",
1134
+ blur: clampSetting("blur", remote.blur),
1135
+ frost: clampSetting("frost", remote.frost),
1136
+ brightness: clampSetting("brightness", remote.brightness)
1137
+ };
1138
+ writeEnabled(this.enabled);
1139
+ writeMode(this.settings.mode);
1140
+ writeSetting("blur", this.settings.blur);
1141
+ writeSetting("frost", this.settings.frost);
1142
+ writeSetting("brightness", this.settings.brightness);
1143
+ this.sync();
1144
+ }
1112
1145
  /** Flip the layer: persist, then apply or retract every owned effect. */
1113
1146
  setEnabled(value) {
1114
1147
  if (value === this.enabled) return;
@@ -1508,6 +1541,7 @@ window.__ModuleLoader__.load({
1508
1541
  opacity: phase === "checking" ? .6 : 1
1509
1542
  };
1510
1543
  const localInstall = payload?.ok === true && payload.installSource !== void 0 && payload.installSource !== "registry";
1544
+ const isDesktop = payload?.ok === true && payload.env === "desktop";
1511
1545
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1512
1546
  style: {
1513
1547
  borderBottom: "1px solid var(--dsw-alias-border-l2)",
@@ -1603,7 +1637,7 @@ window.__ModuleLoader__.load({
1603
1637
  fontSize: 12,
1604
1638
  lineHeight: "18px"
1605
1639
  },
1606
- children: payload.profileDetected === true && payload.profile !== void 0 ? t("update.commandHintDetected").replace("{profile}", payload.profile) : t("update.commandHint")
1640
+ children: isDesktop ? t("update.commandHintDesktop").replace("{profile}", payload.profile ?? "") : payload.profileDetected === true && payload.profile !== void 0 ? t("update.commandHintDetected").replace("{profile}", payload.profile) : t("update.commandHint")
1607
1641
  }),
1608
1642
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1609
1643
  style: {
@@ -1654,7 +1688,7 @@ window.__ModuleLoader__.load({
1654
1688
  fontSize: 12,
1655
1689
  lineHeight: "18px"
1656
1690
  },
1657
- children: t("update.restartHint")
1691
+ children: t(isDesktop ? "update.restartHintDesktop" : "update.restartHint")
1658
1692
  })
1659
1693
  ] })] }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1660
1694
  style: {
@@ -1695,6 +1729,140 @@ window.__ModuleLoader__.load({
1695
1729
  `${"@nonamelego/dsh-catppuccin".replace("/", "%2F")}`;
1696
1730
  /** Host route the Client row fetches (exact match, same origin). */
1697
1731
  const UPDATE_ROUTE_PATH = "/catppuccin/check-update";
1732
+ /** Host route answering the durable state (GET reads, PUT writes). */
1733
+ const STATE_ROUTE_PATH = "/catppuccin/state";
1734
+ /** The four registered Catppuccin theme ids. MUST stay in sync with the
1735
+ * `themeId` of `CATPPUCCIN_FLAVORS` in `src/client/palettes.ts` — guarded by
1736
+ * `tests/state.spec.ts` — because the persisted flavour value IS the theme id. */
1737
+ const CATPPUCCIN_THEME_IDS = [
1738
+ "catppuccin-latte",
1739
+ "catppuccin-frappe",
1740
+ "catppuccin-macchiato",
1741
+ "catppuccin-mocha"
1742
+ ];
1743
+ /** Shipped glass defaults — aligned with `SETTINGS_DEFAULTS` in
1744
+ * `src/client/glass/glass-layer.ts` (guarded by `tests/state.spec.ts`). */
1745
+ const DEFAULT_GLASS = {
1746
+ enabled: false,
1747
+ mode: "mica",
1748
+ blur: 2,
1749
+ frost: 20,
1750
+ brightness: 50
1751
+ };
1752
+ /** A fully default state: no Catppuccin flavour and no glass. */
1753
+ function defaultState() {
1754
+ return {
1755
+ version: 1,
1756
+ flavor: "off",
1757
+ glass: { ...DEFAULT_GLASS }
1758
+ };
1759
+ }
1760
+ /** Clamp a finite number into [min, max]; non-finite values fall back. */
1761
+ function clampFinite(value, min, max, fallback) {
1762
+ return typeof value === "number" && Number.isFinite(value) ? Math.min(max, Math.max(min, value)) : fallback;
1763
+ }
1764
+ /**
1765
+ * Normalize arbitrary input (JSON from disk or the wire) into a valid state:
1766
+ * unknown/missing fields fall back to defaults, out-of-range numbers clamp,
1767
+ * and unknown flavours/invalid enums fall back too. The Host applies this on
1768
+ * both read and write so a corrupt file or a hand-crafted payload can never
1769
+ * poison the plugin's settings.
1770
+ */
1771
+ function sanitizeState(input) {
1772
+ const state = defaultState();
1773
+ if (typeof input !== "object" || input === null) return state;
1774
+ const raw = input;
1775
+ const flavor = raw.flavor;
1776
+ if (typeof flavor === "string" && CATPPUCCIN_THEME_IDS.includes(flavor)) state.flavor = flavor;
1777
+ else if (flavor === "off") state.flavor = "off";
1778
+ const glass = typeof raw.glass === "object" && raw.glass !== null ? raw.glass : {};
1779
+ state.glass.enabled = glass.enabled === true;
1780
+ state.glass.mode = glass.mode === "compat" ? "compat" : "mica";
1781
+ state.glass.blur = clampFinite(glass.blur, 0, 40, DEFAULT_GLASS.blur);
1782
+ state.glass.frost = clampFinite(glass.frost, 0, 100, DEFAULT_GLASS.frost);
1783
+ state.glass.brightness = clampFinite(glass.brightness, 0, 100, DEFAULT_GLASS.brightness);
1784
+ return state;
1785
+ }
1786
+ /** Whether a normalized state carries nothing beyond the shipped defaults —
1787
+ * i.e. the user never actually chose anything worth persisting. Used to tell
1788
+ * "no durable state yet" apart from "an explicit default choice". */
1789
+ function isDefaultState(state) {
1790
+ const expected = defaultState();
1791
+ return state.flavor === expected.flavor && state.glass.enabled === expected.glass.enabled && state.glass.mode === expected.glass.mode && state.glass.blur === expected.glass.blur && state.glass.frost === expected.glass.frost && state.glass.brightness === expected.glass.brightness;
1792
+ }
1793
+ //#endregion
1794
+ //#region src/client/state-sync.ts
1795
+ /**
1796
+ * Browser half of the durable Catppuccin state — a thin wrapper over the
1797
+ * Host's `/catppuccin/state` route (same-origin relative fetch, so it works on
1798
+ * DSH Desktop's per-launch random loopback port). The Host file is the source
1799
+ * of truth; this module adds the fetch wrapper plus a debounced trailing
1800
+ * writer so rapid knob drags coalesce into a single PUT. Every failure is
1801
+ * non-fatal: the plugin keeps working from browser localStorage alone.
1802
+ */
1803
+ /** Read the durable state from the Host file (same-origin GET). */
1804
+ async function readDurableStateRemote() {
1805
+ try {
1806
+ const response = await fetch(STATE_ROUTE_PATH, { headers: { accept: "application/json" } });
1807
+ if (!response.ok) return {
1808
+ available: false,
1809
+ state: null
1810
+ };
1811
+ const payload = await response.json();
1812
+ if (payload.ok !== true) return {
1813
+ available: false,
1814
+ state: null
1815
+ };
1816
+ return {
1817
+ available: true,
1818
+ state: payload.state === null || payload.state === void 0 ? null : sanitizeState(payload.state)
1819
+ };
1820
+ } catch {
1821
+ return {
1822
+ available: false,
1823
+ state: null
1824
+ };
1825
+ }
1826
+ }
1827
+ /** Write the durable state to the Host file (same-origin PUT). */
1828
+ async function persistDurableState(state) {
1829
+ try {
1830
+ return (await fetch(STATE_ROUTE_PATH, {
1831
+ method: "PUT",
1832
+ headers: {
1833
+ accept: "application/json",
1834
+ "content-type": "application/json"
1835
+ },
1836
+ body: JSON.stringify(sanitizeState(state))
1837
+ })).ok;
1838
+ } catch {
1839
+ return false;
1840
+ }
1841
+ }
1842
+ let pendingTimer;
1843
+ let pendingGetState;
1844
+ /**
1845
+ * Debounced trailing persist: after a burst of changes (a slider drag emits a
1846
+ * stream of snapshot updates) the latest state is written to the Host once,
1847
+ * a short beat after the last change. Pass an accessor so the snapshot is
1848
+ * taken at flush time, never stale.
1849
+ */
1850
+ function scheduleDurablePersist(getState, delayMs = 300) {
1851
+ if (pendingTimer !== void 0) window.clearTimeout(pendingTimer);
1852
+ pendingGetState = getState;
1853
+ pendingTimer = window.setTimeout(() => {
1854
+ pendingTimer = void 0;
1855
+ const state = pendingGetState;
1856
+ pendingGetState = void 0;
1857
+ if (state !== void 0) persistDurableState(state()).catch(() => {});
1858
+ }, delayMs);
1859
+ }
1860
+ /** Drop any queued persist (plugin unload / page teardown). */
1861
+ function cancelDurablePersist() {
1862
+ if (pendingTimer !== void 0) window.clearTimeout(pendingTimer);
1863
+ pendingTimer = void 0;
1864
+ pendingGetState = void 0;
1865
+ }
1698
1866
  //#endregion
1699
1867
  //#region \0dsh-css:/home/runner/work/dsh-catppuccin-theme/dsh-catppuccin-theme/src/client/glass/glass.module.css.mjs
1700
1868
  const css = "[data-dsh-glass] body{--dsh-glass-card:color-mix(in srgb, var(--dsw-alias-bg-layer-1) calc(52% * var(--dsh-glass-frost,1)), transparent);--dsh-glass-card-raised:color-mix(in srgb, var(--dsw-alias-bg-layer-1) calc(64% * var(--dsh-glass-frost,1)), transparent);--dsh-glass-card-hover:color-mix(in srgb, var(--dsw-alias-bg-layer-1) calc(76% * var(--dsh-glass-frost,1)), transparent);--dsh-glass-rim:color-mix(in srgb, var(--dsw-alias-label-primary) 18%, transparent);--dsh-glass-rim-soft:color-mix(in srgb, var(--dsw-alias-label-primary) 12%, transparent);--dsh-glass-edge:inset 0 1px 0 #ffffff6b;--dsh-glass-drop:0 10px 32px color-mix(in srgb, var(--dsw-alias-bg-mask-2) 38%, transparent)}[data-dsh-glass] body[data-ds-dark-theme]{--dsh-glass-rim:color-mix(in srgb, var(--dsw-alias-label-primary) 24%, transparent);--dsh-glass-rim-soft:color-mix(in srgb, var(--dsw-alias-label-primary) 16%, transparent);--dsh-glass-edge:inset 0 1px 0 #ffffff14;--dsh-glass-drop:0 10px 32px #02060e73}[data-dsh-glass] body{background:color-mix(in srgb, color-mix(in srgb, var(--dsw-alias-bg-base), #fff calc(var(--dsh-glass-brightness-white,0) * 100%)), #000 calc(var(--dsh-glass-brightness-black,0) * 100%))}[data-dsh-glass] [data-dsh-glass-frame],[data-dsh-glass] [data-phase],[data-dsh-glass] [data-dsh-glass-details]{background:0 0}[data-dsh-glass-float] [data-phase=active] header{z-index:8;position:relative}[data-dsh-glass-float] [class*=banner]{position:static}[data-dsh-glass-float] [data-phase=active] [data-conversation-scroll]{margin-top:-95px;padding-top:107px}[data-dsh-glass] [data-phase] [class*=composerSeat][class*=composerSeat]{background:0 0}[data-dsh-glass] [data-dsh-glass-fade]{z-index:7;pointer-events:none;backdrop-filter:blur(5px);background:#ffffff2e;height:13px;position:fixed;left:0;right:0}[data-dsh-glass] body[data-ds-dark-theme] [data-dsh-glass-fade]{background:#00000024}[data-dsh-glass] [data-dsh-glass-fade=top]{top:0;-webkit-mask-image:linear-gradient(#000 0%,#0000 100%);mask-image:linear-gradient(#000 0%,#0000 100%)}[data-dsh-glass] [data-dsh-glass-fade=bottom]{bottom:0;-webkit-mask-image:linear-gradient(#0000 0%,#000 100%);mask-image:linear-gradient(#0000 0%,#000 100%)}[data-dsh-glass-float] [role=menu],[data-dsh-glass-float] [role=dialog],[data-dsh-glass-float] [role=alert],[data-dsh-glass-float] [data-dsh-glass-surface]{border-radius:14px}[data-dsh-glass-float] [role=menuitem],[data-dsh-glass-float] [role=tooltip],[data-dsh-glass-float] [class*=pill]{border-radius:8px}[data-dsh-glass-float] button[class*=button]{border-radius:10px}[data-dsh-glass-float] [class*=iconButton],[data-dsh-glass-float] [class*=searchButton]{border-radius:8px}[data-dsh-glass-float] [class*=block]{--dsl-code-block-border-radius:14px;--dsl-diff-radius:14px;--dsl-read-radius:14px;--dsl-terminal-radius:14px;--dsl-web-radius:14px;--dsl-search-radius:14px;border-radius:14px}[data-dsh-glass-float] [data-dsh-glass-add]{background:var(--dsh-glass-card-raised);border:1px solid var(--dsh-glass-rim);box-shadow:var(--dsh-glass-edge);backdrop-filter:blur(var(--dsh-glass-blur,14px))}[data-dsh-glass-float] [data-dsh-glass-add]:hover:not(:disabled){background:var(--dsh-glass-card-hover)}[data-dsh-glass-float] [data-dsh-glass-surface]{background:var(--dsh-glass-card-raised);border:1px solid var(--dsh-glass-rim);box-shadow:var(--dsh-glass-edge);backdrop-filter:blur(var(--dsh-glass-blur,14px))}[data-dsh-glass-float] [data-dsh-glass-surface]:hover:not(:disabled){background:var(--dsh-glass-card-hover)}[data-dsh-glass-float] [class*=bubble]{border:1px solid var(--dsh-glass-rim-soft);background:color-mix(in srgb, var(--dsw-alias-bg-layer-1) calc(46% * var(--dsh-glass-frost,1)), transparent);backdrop-filter:blur(var(--dsh-glass-blur,14px));border-radius:14px}[data-dsh-glass-float] [class*=card]{border-radius:14px}[data-dsh-glass-float] header{border:1px solid var(--dsh-glass-rim);background:var(--dsh-glass-card);box-shadow:var(--dsh-glass-edge), var(--dsh-glass-drop);backdrop-filter:blur(var(--dsh-glass-blur,14px));border-bottom-color:#0000;border-radius:20px;margin:12px 16px 0;padding:10px 16px 8px}[data-dsh-glass-float] header:after{display:none}[data-dsh-glass-float] [data-dsh-glass-frame][data-sidebar-collapsed] header{margin-left:28px}[data-dsh-glass-float] [class*=sidebarCol]{--dsw-specific-sidebar-fill:transparent;z-index:9;border:1px solid var(--dsh-glass-rim);border-right-color:color-mix(in srgb, var(--dsw-alias-brand-primary-new-colorprimary-new-color) 40%, transparent);background:var(--dsh-glass-card);box-shadow:var(--dsh-glass-edge), var(--dsh-glass-drop);border-radius:20px;margin:12px;padding:10px 12px 14px;position:relative;overflow:hidden}[data-dsh-glass-float] body[data-ds-dark-theme] [class*=sidebarCol]{border-right-color:color-mix(in srgb, var(--dsw-alias-brand-primary-new-colorprimary-new-color) 22%, transparent)}[data-dsh-glass-float] [class*=sidebarCol]:has([role=dialog]){backdrop-filter:none}[data-dsh-glass-float] [class*=sidebarCol]{backdrop-filter:blur(var(--dsh-glass-blur,14px))}[data-dsh-glass-float] [data-dsh-glass-frame][data-sidebar-collapsed] [class*=sidebarCol]{transition:margin .15s var(--ds-ease-in-out), border-radius .15s var(--ds-ease-in-out);border-radius:16px;margin:12px -12px 12px 12px;padding:0}[data-dsh-glass-float] [data-dsh-glass-frame]:not([data-sidebar-collapsed]) [data-dsh-glass-sidebar-root]{width:100%!important}[data-dsh-glass-float] [data-dsh-glass-trajectory]{border:1px solid var(--dsh-glass-rim);background:var(--dsh-glass-card);width:calc(100% - 32px);height:calc(100% - 20px);box-shadow:var(--dsh-glass-edge), var(--dsh-glass-drop);backdrop-filter:blur(var(--dsh-glass-blur,14px));border-radius:20px;margin:8px 16px 12px;overflow:hidden}[data-dsh-glass-float] [data-dsh-glass-trajectory] [role=toolbar],[data-dsh-glass-float] [data-dsh-glass-trajectory] section[aria-label=Trajectory\\ timeline]{background:0 0}[data-dsh-glass-float] [data-composer-card],[data-dsh-glass-float] [data-composer-card]:after{border-radius:24px}[data-dsh-glass-float] [data-composer-card]{z-index:8;background:var(--dsh-glass-card);border:1px solid var(--dsh-glass-rim);box-shadow:var(--dsh-glass-edge), var(--dsh-glass-drop);backdrop-filter:blur(var(--dsh-glass-blur,14px));position:relative}[data-dsh-glass-float] [data-dsh-glass-inputbar]:not([class*=hero]):not(:has([data-dsh-glass-stats])){padding-bottom:12px}[data-dsh-glass][data-dsh-glass-float] [data-dsh-glass-inputbar]:has([data-dsh-glass-stats]){border:1px solid var(--dsh-glass-rim);background:var(--dsh-glass-card);box-shadow:var(--dsh-glass-edge), var(--dsh-glass-drop);backdrop-filter:blur(var(--dsh-glass-blur,14px));border-radius:24px;margin:0 auto 12px;padding:0;width:var(--dsh-composer-card-max-width)!important;max-width:var(--dsh-composer-card-max-width)!important}[data-dsh-glass-float] [data-dsh-glass-inputbar]:has([data-dsh-glass-stats]) [data-composer-card]{box-shadow:none;backdrop-filter:none;background:0 0;border:none;border-radius:0}[data-dsh-glass-float] [data-dsh-glass-inputbar]:has([data-dsh-glass-stats]) [data-composer-card]:after{display:none}[data-dsh-glass][data-dsh-glass-float] [data-dsh-glass-inputbar]:has([data-dsh-glass-stats]) [data-slot]{border:none;border-top:1px solid var(--dsh-glass-rim-soft);box-shadow:none;backdrop-filter:none;background:0 0;border-radius:0;margin:0;padding:2px 16px 6px;width:100%!important;max-width:none!important}[data-dsh-glass][data-dsh-glass-float] [data-dsh-glass-inputbar] [data-slot] [data-dsh-glass-stats]{min-width:0;box-shadow:none;backdrop-filter:none;background:0 0;border:none;border-radius:0;margin:0;width:100%!important;padding:4px 0 0!important}[data-dsh-glass][data-dsh-glass-float] [data-dsh-glass-inputbar] [data-slot] [data-dsh-live-tps]{padding-right:0}[data-dsh-glass-float] [role=treeitem][aria-selected=true]{box-shadow:inset 2px 0 0 var(--dsw-specific-sidebar-nav-item-active-accent), 0 0 16px color-mix(in srgb, var(--dsw-alias-brand-primary-new-colorprimary-new-color) 20%, transparent)}[data-dsh-glass-float] button[class*=button]:hover:not(:disabled),[data-dsh-glass-float] [role=menuitem]:hover:not(:disabled){box-shadow:0 0 12px color-mix(in srgb, var(--dsw-alias-brand-primary-new-colorprimary-new-color) 20%, transparent), inset 0 0 0 1px color-mix(in srgb, var(--dsw-alias-border-l3) 60%, transparent)}[data-dsh-glass-float] [role=menu]{background:var(--dsh-glass-card-raised);border:1px solid var(--dsh-glass-rim-soft);backdrop-filter:blur(var(--dsh-glass-blur,14px))}[data-dsh-glass-float] [role=menu]:has([role=group]>[role=menuitemradio]){background:color-mix(in srgb, var(--dsw-alias-bg-layer-2) 70%, transparent)}[data-dsh-glass-float] [role=menu] [role=group]:has(>[role=menuitemradio])>:first-child{border:1px solid var(--dsh-glass-rim);background:var(--dsh-glass-card-raised);backdrop-filter:blur(var(--dsh-glass-blur,14px));border-radius:999px;align-items:center;width:fit-content;margin:3px 0 5px 6px;padding:3px 10px;display:inline-flex}[data-dsh-glass-float] [role=dialog][aria-modal=true]{background:var(--dsh-glass-card-raised);border:1px solid var(--dsh-glass-rim);box-shadow:var(--dsh-glass-edge);backdrop-filter:blur(var(--dsh-glass-blur,14px))}[data-dsh-glass] [data-conversation-scroll]{text-shadow:0 0 1px #ffffff80}[data-dsh-glass] body[data-ds-dark-theme] [data-conversation-scroll]{text-shadow:0 0 1px #0006}[data-dsh-glass-float] [data-composer-card] textarea::placeholder{color:color-mix(in srgb, var(--dsw-alias-label-primary) 45%, transparent)}[data-dsh-glass-float] [data-dsh-glass-inputbar] [data-slot]{color:var(--dsw-alias-label-secondary)}[data-dsh-glass-float] [data-phase=hero]{animation:SsOp2G_dsh-glass-in .32s var(--ds-ease-in-out)}[data-dsh-glass-float] [data-phase=active]{animation:SsOp2G_dsh-glass-in .3s var(--ds-ease-in-out)}[data-dsh-glass-float] [data-testid^=view-]{animation:SsOp2G_dsh-glass-in .26s var(--ds-ease-in-out)}[data-dsh-glass-float] [data-tool]{animation:SsOp2G_dsh-glass-rise .3s var(--ds-ease-in-out) both}[data-dsh-glass-float] [role=dialog]{animation:SsOp2G_dsh-glass-dialog-in .24s var(--ds-ease-in-out)}@keyframes SsOp2G_dsh-glass-in{0%{opacity:0}}@keyframes SsOp2G_dsh-glass-rise{0%{opacity:0;transform:translateY(6px)}}@keyframes SsOp2G_dsh-glass-dialog-in{0%{opacity:0;transform:translateY(8px)scale(.985)}}[data-dsh-glass] :focus-visible{outline:2px solid color-mix(in srgb, var(--dsw-alias-brand-primary-new-colorprimary-new-color) 85%, transparent);outline-offset:1px}[data-dsh-glass] ::selection{background:color-mix(in srgb, var(--dsw-alias-brand-primary-new-colorprimary-new-color) 35%, transparent)}[data-dsh-glass-compat] [role=menu],[data-dsh-glass-compat] [role=tooltip],[data-dsh-glass-compat] [class*=card],[data-dsh-glass-compat] [class*=bubble],[data-dsh-glass-compat] [class*=panel],[data-dsh-glass-compat] [class*=popover],[data-dsh-glass-compat] [class*=dropdown]{backdrop-filter:blur(12px)}[data-dsh-glass-compat] [role=dialog][aria-modal=true]{background:var(--dsh-glass-card-raised);border:1px solid var(--dsh-glass-rim);backdrop-filter:blur(var(--dsh-glass-blur,12px))}@media (prefers-reduced-motion:reduce){[data-dsh-glass-float] [data-phase=hero],[data-dsh-glass-float] [data-phase=active],[data-dsh-glass-float] [data-testid^=view-],[data-dsh-glass-float] [data-tool],[data-dsh-glass-float] [role=dialog]{animation:none}}";
@@ -1766,23 +1934,29 @@ window.__ModuleLoader__.load({
1766
1934
  for (const dispose of disposers) dispose();
1767
1935
  };
1768
1936
  }, "catppuccin: flavour themes");
1937
+ const glass = new GlassLayer(ctx);
1938
+ const buildLocalState = () => ({
1939
+ version: 1,
1940
+ flavor: readFlavor(),
1941
+ glass: glass.getRemoteState()
1942
+ });
1769
1943
  ctx.effect(() => {
1770
- const saved = readFlavor();
1771
- if (saved === "off") return () => {};
1944
+ let desired = readFlavor();
1772
1945
  let settled = false;
1773
1946
  const started = Date.now();
1774
- const applySaved = () => {
1947
+ const applyDesired = () => {
1775
1948
  if (settled) return;
1776
1949
  if (Date.now() - started > 5e3) {
1777
1950
  settled = true;
1778
1951
  return;
1779
1952
  }
1953
+ if (desired === "off") return;
1780
1954
  try {
1781
- if (theme.getTheme().preference !== saved) theme.setTheme(saved);
1955
+ if (theme.getTheme().preference !== desired) theme.setTheme(desired);
1782
1956
  } catch {}
1783
1957
  };
1784
- applySaved();
1785
- const disposer = ctx.on("theme/change", applySaved);
1958
+ applyDesired();
1959
+ const disposer = ctx.on("theme/change", applyDesired);
1786
1960
  const timer = window.setTimeout(() => {
1787
1961
  settled = true;
1788
1962
  }, 5e3);
@@ -1794,10 +1968,29 @@ window.__ModuleLoader__.load({
1794
1968
  } catch {}
1795
1969
  };
1796
1970
  window.addEventListener("storage", onStorage);
1971
+ const offGlass = glass.subscribe(() => {
1972
+ scheduleDurablePersist(buildLocalState);
1973
+ });
1974
+ let cancelled = false;
1975
+ readDurableStateRemote().then((result) => {
1976
+ if (cancelled) return;
1977
+ if (!result.available) return;
1978
+ if (result.state === null) {
1979
+ if (!isDefaultState(buildLocalState())) scheduleDurablePersist(buildLocalState);
1980
+ return;
1981
+ }
1982
+ writeFlavor(result.state.flavor);
1983
+ glass.applyRemote(result.state.glass);
1984
+ desired = result.state.flavor;
1985
+ applyDesired();
1986
+ });
1797
1987
  return () => {
1988
+ cancelled = true;
1989
+ offGlass();
1798
1990
  disposer();
1799
1991
  window.clearTimeout(timer);
1800
1992
  window.removeEventListener("storage", onStorage);
1993
+ cancelDurablePersist();
1801
1994
  };
1802
1995
  }, "catppuccin: boot restore");
1803
1996
  const injected = () => ({
@@ -1815,12 +2008,14 @@ window.__ModuleLoader__.load({
1815
2008
  if (choice === "off") {
1816
2009
  theme.setTheme("system");
1817
2010
  writeFlavor("off");
2011
+ scheduleDurablePersist(buildLocalState);
1818
2012
  return;
1819
2013
  }
1820
2014
  const flavor = flavorInfo(choice);
1821
2015
  if (!flavor) return;
1822
2016
  theme.setTheme(flavor.themeId);
1823
2017
  writeFlavor(flavor.themeId);
2018
+ scheduleDurablePersist(buildLocalState);
1824
2019
  }
1825
2020
  });
1826
2021
  ctx.slots.inject("settings.general.item", () => ctx.slots.register({
@@ -1830,7 +2025,6 @@ window.__ModuleLoader__.load({
1830
2025
  locale: NS,
1831
2026
  inject: injected
1832
2027
  }, CatppuccinRow));
1833
- const glass = new GlassLayer(ctx);
1834
2028
  const glassInjected = () => ({
1835
2029
  getState: () => glass.getSnapshot(),
1836
2030
  subscribe: (listener) => glass.subscribe(listener),