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

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.en.md CHANGED
@@ -318,11 +318,12 @@ Then add `@nonamelego/dsh-catppuccin` to the profile's `package.json`
318
318
  - **Q: "How is my theme choice remembered?"**
319
319
  A: The choice persists in DSH's official settings document (the `catppuccin` namespace,
320
320
  stored under the DSH home, shared across every DSH instance on the machine), with
321
- localStorage as an in-browser cache and cross-tab sync. So plain `dsh web`,
322
- `dsh web --port <custom>`, and **DSH Desktop** (which boots on a fresh random loopback
323
- port every launch) all restore the preference automatically. The glass switch and every
321
+ localStorage as an in-browser cache and cross-tab sync. So switching browser,
322
+ clearing site data, a custom port (`dsh web --port <custom>`) or a second desktop
323
+ instance never loses the preference; **DSH Desktop** (the official shell and
324
+ `anywhere-labs/dsh-desktop`) restores it across restarts too. The glass switch and every
324
325
  knob persist the same way. Since 0.5.0, a legacy `catppuccin-state.json` is migrated
325
- into the settings document once on first launch (the file is kept as a rollback copy).
326
+ into the official settings once on first launch (the file is kept as a rollback copy).
326
327
  - **Q: "How do I know if this plugin has a new version?"**
327
328
  A: Settings → General → **Check Catppuccin plugin updates** compares against npm in one
328
329
  click and gives a copyable upgrade command; or run
package/README.md CHANGED
@@ -292,12 +292,15 @@ pnpm --dir C:\Users\LeGo\.dsh\profiles\web add link:D:\Vibe-Coding\dsh-catppucci
292
292
  A: 官方外观行只列出内置的浅色/深色/跟随系统偏好。四个主题在它正下方的
293
293
  **Catppuccin** 行里。
294
294
  - Q: **_"我的主题选择是怎么记住的?"_**
295
- A: 选择持久保存在 DSH 的官方 settings 文档(`catppuccin` namespace,存于
296
- DSH home 下,同一台机器的 DSH 共享这份偏好),浏览器 localStorage 作为
297
- 即时缓存与多标签页同步。因此普通 `dsh web`、`dsh web --port` 自定义端口,
298
- 以及 **DSH Desktop**(每次启动用随机回环端口)都能跨重启自动恢复;玻璃质感
299
- 开关与各旋钮同样持久保存。0.5.0 起旧版 `catppuccin-state.json` 会在首次
300
- 启动时一次性迁移进 settings 文档(文件保留作回退)。
295
+ A: 选择持久保存在 DSH 的官方设置里(同一台机器的 DSH 共享这份偏好),浏览器
296
+ localStorage 作为即时缓存与多标签页同步。落点随 DSH 版本而异:**0.1.6 及更早**
297
+ 存于 DSH home 下的官方 settings 文档(`catppuccin` 命名空间);**0.1.7 起**上游
298
+ 改为「插件配置表单」,落点是当前 profile 的 patch 文件里本插件的 `config:` 段
299
+ (本插件两套都支持,按宿主实际提供的服务自动选择)。因此换浏览器、清站点数据、
300
+ 自定义端口(`dsh web --port`)或再开一个桌面实例,偏好都不会丢;
301
+ **DSH Desktop**(官方壳与 `anywhere-labs/dsh-desktop`)同样跨重启自动恢复。
302
+ 玻璃质感开关与各旋钮同样持久保存。0.5.0 起旧版 `catppuccin-state.json`
303
+ 会在首次启动时一次性迁移进官方设置(文件保留作回退)。
301
304
  - Q: **_"怎么知道这个插件有没有新版本?"_**
302
305
  A: 设置 → 常规 → **检查 Catppuccin 插件更新** 一键检测本插件在 npm 上的最新版本,
303
306
  发现新版会给出可复制的升级命令;也可以随时手动执行
package/lib/client.js CHANGED
@@ -1954,9 +1954,39 @@ window.__ModuleLoader__.load({
1954
1954
  * glass.module.css and rebuilding.
1955
1955
  */
1956
1956
  const GLASS_CSS_TEXT = "[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] [data-conversation-scroll] [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);border-radius:14px}[data-dsh-glass-float] [class*=card]{border-radius:14px}[data-dsh-glass] [data-dsh-glass-settings]{--dsh-glass-settings-raised:calc(38% + 38% * var(--dsh-glass-frost,1));--dsh-glass-settings-soft:calc(30% + 30% * var(--dsh-glass-frost,1));--dsw-alias-bg-module-platform:color-mix(in srgb, var(--dsw-static-neutral-bluish-800) var(--dsh-glass-settings-raised), transparent);--dsw-alias-bg-layer-3:color-mix(in srgb, var(--dsw-static-neutral-bluish-800) var(--dsh-glass-settings-soft), transparent);--dsw-alias-bg-layer-2:color-mix(in srgb, var(--dsw-static-neutral-bluish-850) var(--dsh-glass-settings-raised), transparent);--dsw-alias-bg-layer-1:color-mix(in srgb, var(--dsw-static-neutral-bluish-850) var(--dsh-glass-settings-soft), transparent);--dsw-specific-sidebar-nav-item-active:color-mix(in srgb, var(--dsw-static-neutral-bluish-750) var(--dsh-glass-settings-raised), transparent);--dsw-specific-sidebar-nav-item-hover:color-mix(in srgb, var(--dsw-static-neutral-bluish-850) var(--dsh-glass-settings-soft), transparent)}[data-dsh-glass] body:not([data-ds-dark-theme]) [data-dsh-glass-settings]{--dsw-alias-bg-module-platform:color-mix(in srgb, var(--dsw-static-neutral-bluish-60) var(--dsh-glass-settings-raised), transparent);--dsw-alias-bg-layer-3:color-mix(in srgb, var(--dsw-static-neutral-bluish-00) var(--dsh-glass-settings-soft), transparent);--dsw-alias-bg-layer-2:color-mix(in srgb, var(--dsw-static-neutral-bluish-00) var(--dsh-glass-settings-raised), transparent);--dsw-alias-bg-layer-1:color-mix(in srgb, var(--dsw-static-neutral-bluish-00) var(--dsh-glass-settings-soft), transparent);--dsw-specific-sidebar-nav-item-active:color-mix(in srgb, var(--dsw-static-neutral-bluish-100) var(--dsh-glass-settings-raised), transparent);--dsw-specific-sidebar-nav-item-hover:color-mix(in srgb, var(--dsw-static-neutral-bluish-75) var(--dsh-glass-settings-soft), transparent)}[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-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);box-shadow:var(--dsh-glass-drop);border-radius:20px;margin:12px;padding:10px 12px 14px;position:relative;overflow:hidden}[data-dsh-glass-float] [class*=sidebarCol]:before{content:\"\";border-radius:inherit;background:var(--dsh-glass-card);box-shadow:var(--dsh-glass-edge);z-index:-1;pointer-events:none;transition:border-radius .15s var(--ds-ease-in-out);position:absolute;inset:0}[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] [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);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]{background:color-mix(in srgb, var(--dsw-specific-sidebar-nav-item-active) 40%, transparent);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-dsh-glass-inputbar] [data-slot]{color:var(--dsw-alias-label-secondary)}[data-dsh-glass-float] [data-phase=hero]{animation:dsh-glass-in .32s var(--ds-ease-in-out)}[data-dsh-glass-float] [data-phase=active]{animation:dsh-glass-in .3s var(--ds-ease-in-out)}[data-dsh-glass-float] [data-testid^=view-]{animation:dsh-glass-in .26s var(--ds-ease-in-out)}[data-dsh-glass-float] [data-tool]{animation:dsh-glass-rise .3s var(--ds-ease-in-out) both}[data-dsh-glass-float] [role=dialog]{animation:dsh-glass-dialog-in .24s var(--ds-ease-in-out)}@keyframes dsh-glass-in{0%{opacity:0}}@keyframes dsh-glass-rise{0%{opacity:0;transform:translateY(6px)}}@keyframes 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*=panel],[data-dsh-glass-compat] [class*=popover],[data-dsh-glass-compat] [class*=dropdown]{backdrop-filter:blur(12px)}[data-dsh-glass-compat] [role=menu],[data-dsh-glass-compat] [role=tooltip],[data-dsh-glass-compat] [class*=card],[data-dsh-glass-compat] [class*=popover],[data-dsh-glass-compat] [class*=dropdown]{background:var(--dsh-glass-card-raised)}[data-dsh-glass-compat] [role=menu]:not(:focus-visible),[data-dsh-glass-compat] [role=tooltip]:not(:focus-visible),[data-dsh-glass-compat] [class*=card]:not(:focus-visible),[data-dsh-glass-compat] [class*=popover]:not(:focus-visible),[data-dsh-glass-compat] [class*=dropdown]:not(:focus-visible){outline:1px solid var(--dsh-glass-rim);outline-offset:-1px}[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}}";
1957
- /** Settings namespace both halves address: Host registers it, Client binds
1958
- * it through `ctx.settingsScope` (see `src/settings-catppuccin.ts`). */
1957
+ /** Legacy settings namespace (DSH <= 0.1.6-alpha.2): the Host registers it
1958
+ * through `settings.installSection`, the Client binds it through
1959
+ * `ctx.settingsScope` (see `src/settings-catppuccin.ts`). */
1959
1960
  const CATPPUCCIN_SETTINGS_NS = "catppuccin";
1961
+ /**
1962
+ * Profile entry id carrying the plugin (DSH >= 0.1.7-alpha.1): the settings
1963
+ * service projects our volatile `Config` into a form named by the ACTIVE
1964
+ * PROFILE ENTRY's id, so this constant is the new seam's namespace.
1965
+ *
1966
+ * It is a CONTRACT with `cordis.patch.yml`: the `insert` row whose `name` is
1967
+ * `@nonamelego/dsh-catppuccin` must keep `id: dsh-catppuccin`, because the
1968
+ * Client can only address the form by that id (asserted by
1969
+ * `tests/settings-seam.spec.ts`). A user who renames the entry loses the new
1970
+ * seam and degrades to localStorage-only — the legacy seam is unaffected,
1971
+ * since that namespace is registered by the plugin itself.
1972
+ */
1973
+ const CATPPUCCIN_ENTRY_ID = "dsh-catppuccin";
1974
+ /**
1975
+ * Whether a raw settings view carries a user layer at all.
1976
+ *
1977
+ * The two seams answer this differently and the difference is load-bearing
1978
+ * (issue #15): the NEW form's `user` is the profile patch's `override`, which
1979
+ * `configEditor.configuration()` defaults to `{}` — so "absent" arrives as an
1980
+ * EMPTY OBJECT, and `!== undefined` would be true forever. The LEGACY
1981
+ * document omits the field entirely when the document holds no such section,
1982
+ * so the old `!== undefined` test stays correct there. Callers must use the
1983
+ * predicate their channel defines (see `src/client/state-sync.ts`).
1984
+ * @param user - a snapshot's raw user layer.
1985
+ * @returns whether it holds at least one entry.
1986
+ */
1987
+ function hasUserLayerSection(user) {
1988
+ return typeof user === "object" && user !== null && !Array.isArray(user) && Object.keys(user).length > 0;
1989
+ }
1960
1990
  /** The four registered Catppuccin theme ids. MUST stay in sync with the
1961
1991
  * `themeId` of `CATPPUCCIN_FLAVORS` in `src/client/palettes.ts` — guarded by
1962
1992
  * `tests/state.spec.ts` — because the persisted flavour value IS the theme id. */
@@ -3283,14 +3313,17 @@ window.__ModuleLoader__.load({
3283
3313
  const UPDATE_ROUTE_PATH = "/catppuccin/check-update";
3284
3314
  //#endregion
3285
3315
  //#region src/client/state-sync.ts
3286
- /**
3287
- * Bind the Catppuccin namespace on this context. The scope derives from the
3288
- * shared document mirror (no wire read of its own) and its life is bound to
3289
- * the calling fiber, so binding never blocks on the settings transport.
3290
- */
3291
- function bindCatppuccinScope(ctx) {
3292
- return ctx.settingsScope.bind({ namespace: CATPPUCCIN_SETTINGS_NS });
3293
- }
3316
+ /** The snapshot an unbound scope reports: the pre-0.5.0 "transport absent"
3317
+ * degradation, where localStorage is the only store. */
3318
+ const UNBOUND_SNAPSHOT = {
3319
+ status: "unavailable",
3320
+ value: void 0,
3321
+ base: void 0,
3322
+ user: void 0,
3323
+ revision: void 0,
3324
+ writable: false,
3325
+ mode: "memory"
3326
+ };
3294
3327
  /** Whether the scope is usable for durable persistence: the document is
3295
3328
  * resolved (`ready`) and the Host persistence is active (`host`). A `memory`
3296
3329
  * scope (non-loopback pages / absent transport) or a not-yet-ready one is
@@ -3304,6 +3337,94 @@ window.__ModuleLoader__.load({
3304
3337
  if (!isScopeUsable(snapshot) || snapshot.value === void 0) return null;
3305
3338
  return stateFromSettingsSection(snapshot.value);
3306
3339
  }
3340
+ /** The NEW seam's channel (DSH >= 0.1.7-alpha.1). */
3341
+ function configFormsScope(form) {
3342
+ return {
3343
+ kind: "configForms",
3344
+ getSnapshot: () => form.getSnapshot(),
3345
+ subscribe: (listener) => form.subscribe(listener),
3346
+ hasUserLayer: (snapshot) => hasUserLayerSection(snapshot.user),
3347
+ write: async (ops, baseRevision) => {
3348
+ try {
3349
+ return await form.mutate(ops, baseRevision) ? "accepted" : "refused";
3350
+ } catch {
3351
+ return "failed";
3352
+ }
3353
+ }
3354
+ };
3355
+ }
3356
+ /** The LEGACY seam's channel (DSH <= 0.1.6-alpha.2). */
3357
+ function legacyScope(scope) {
3358
+ return {
3359
+ kind: "settingsScope",
3360
+ getSnapshot: () => scope.getSnapshot(),
3361
+ subscribe: (listener) => scope.subscribe(listener),
3362
+ hasUserLayer: (snapshot) => snapshot.user !== void 0,
3363
+ write: async (ops) => {
3364
+ try {
3365
+ await scope.mutate(ops);
3366
+ return "accepted";
3367
+ } catch {
3368
+ return "failed";
3369
+ }
3370
+ }
3371
+ };
3372
+ }
3373
+ /**
3374
+ * Bind whichever settings channel this host serves, late and without blocking.
3375
+ *
3376
+ * Both bindings ride OPTIONAL injections: a service the deployment never
3377
+ * provides simply never fires its callback (it does not hold the plugin
3378
+ * back), and the two names cannot both be live in one host — 0.1.7-alpha.1
3379
+ * removed `settingsScope` from the service set, and 0.1.6 and earlier know
3380
+ * nothing of `configForms`. Until one binds, the scope reports the unbound
3381
+ * snapshot and every write short-circuits, which is exactly the
3382
+ * "settings transport unavailable" degradation the rows already handle.
3383
+ * @param ctx - client root context.
3384
+ * @returns the scope facade, bound for the lifetime of the caller's fiber.
3385
+ */
3386
+ function createDurableScope(ctx) {
3387
+ let current;
3388
+ let offBound;
3389
+ const listeners = /* @__PURE__ */ new Set();
3390
+ const notify = () => {
3391
+ for (const listener of [...listeners]) listener();
3392
+ };
3393
+ const attach = (next) => {
3394
+ if (next === current) return;
3395
+ offBound?.();
3396
+ current = next;
3397
+ offBound = next?.subscribe(notify);
3398
+ notify();
3399
+ };
3400
+ ctx.inject(["configForms"], (formsCtx) => {
3401
+ attach(configFormsScope(formsCtx.configForms.get(CATPPUCCIN_ENTRY_ID)));
3402
+ return () => {
3403
+ attach(void 0);
3404
+ };
3405
+ });
3406
+ ctx.inject(["settingsScope"], (legacyCtx) => {
3407
+ const binder = legacyCtx.settingsScope;
3408
+ if (binder !== void 0) attach(legacyScope(binder.bind({ namespace: CATPPUCCIN_SETTINGS_NS })));
3409
+ return () => {
3410
+ attach(void 0);
3411
+ };
3412
+ });
3413
+ return {
3414
+ get kind() {
3415
+ return current?.kind ?? "none";
3416
+ },
3417
+ getSnapshot: () => current?.getSnapshot() ?? UNBOUND_SNAPSHOT,
3418
+ subscribe: (listener) => {
3419
+ listeners.add(listener);
3420
+ return () => {
3421
+ listeners.delete(listener);
3422
+ };
3423
+ },
3424
+ hasUserLayer: (snapshot) => current?.hasUserLayer(snapshot) ?? false,
3425
+ write: async (ops, baseRevision) => current?.write(ops, baseRevision) ?? "failed"
3426
+ };
3427
+ }
3307
3428
  /** One atomic mutation covering the whole local state (revision-fenced).
3308
3429
  * Writing every field keeps the section self-contained — a partial write
3309
3430
  * could otherwise leave a knob stuck on a stale override after a document
@@ -3366,11 +3487,13 @@ window.__ModuleLoader__.load({
3366
3487
  /**
3367
3488
  * Read-side consistency guard for the durable persist (items C/X).
3368
3489
  *
3369
- * The WRITE side is already revision-fenced by `scope.mutate(...)` (the
3370
- * Host rejects an outdated revision), so no optimistic locking is re-built
3371
- * here. What the fence cannot see is a STALE LOCAL VIEW: tab A reads an old
3372
- * localStorage value, the user changes flavour, and the debounced flush
3490
+ * The WRITE side is revision-fenced by the channel, so no optimistic locking
3491
+ * is re-built here. What a fence cannot see is a STALE LOCAL VIEW: tab A reads
3492
+ * an old localStorage value, the user changes flavour, and the debounced flush
3373
3493
  * writes that old-based state over tab B's already-committed newer choice.
3494
+ * This guard is also the ONLY conflict detector the legacy seam can offer —
3495
+ * that seam resolves a refused write silently, so without it a concurrent edit
3496
+ * would be lost without a trace.
3374
3497
  *
3375
3498
  * The guard compares the revision the local state was derived from
3376
3499
  * (`baseRevision`, captured at schedule time) with the snapshot revision at
@@ -3386,21 +3509,19 @@ window.__ModuleLoader__.load({
3386
3509
  * a redundant identical mutation).
3387
3510
  */
3388
3511
  async function persistStateToScope(scope, state, options = {}) {
3389
- try {
3390
- const snapshot = scope.getSnapshot();
3391
- if (!isScopeUsable(snapshot)) return "error";
3392
- const remote = durableStateFromSnapshot(snapshot);
3393
- const localSection = settingsSectionFromState(state);
3394
- if (remote !== null && settingsSectionsEqual(settingsSectionFromState(remote), localSection)) return "noop";
3395
- const { baseRevision, lastWrittenSection } = options;
3396
- if (baseRevision !== void 0 && snapshot.revision !== void 0 && snapshot.revision !== baseRevision) {
3397
- if (!(lastWrittenSection !== void 0 && remote !== null && settingsSectionsEqual(settingsSectionFromState(remote), lastWrittenSection))) return "stale";
3398
- }
3399
- await scope.mutate(stateToMutateOps(state));
3400
- return "written";
3401
- } catch {
3402
- return "error";
3512
+ const snapshot = scope.getSnapshot();
3513
+ if (!isScopeUsable(snapshot)) return "error";
3514
+ const remote = durableStateFromSnapshot(snapshot);
3515
+ const localSection = settingsSectionFromState(state);
3516
+ if (remote !== null && settingsSectionsEqual(settingsSectionFromState(remote), localSection)) return "noop";
3517
+ const { baseRevision, lastWrittenSection } = options;
3518
+ if (baseRevision !== void 0 && snapshot.revision !== void 0 && snapshot.revision !== baseRevision) {
3519
+ if (!(lastWrittenSection !== void 0 && remote !== null && settingsSectionsEqual(settingsSectionFromState(remote), lastWrittenSection))) return "stale";
3403
3520
  }
3521
+ const outcome = await scope.write(stateToMutateOps(state), baseRevision);
3522
+ if (outcome === "accepted") return "written";
3523
+ if (outcome === "refused") return "stale";
3524
+ return "error";
3404
3525
  }
3405
3526
  let pendingTimer;
3406
3527
  let pendingWrite;
@@ -3499,11 +3620,13 @@ window.__ModuleLoader__.load({
3499
3620
  /** Whether localStorage carries an EXPLICIT `off` flavour choice.
3500
3621
  *
3501
3622
  * `readFlavor()` maps both "no value" and "off" to `off`, and the two are not
3502
- * the same thing: DSH Desktop boots the GUI on a fresh random loopback port, so
3503
- * its localStorage starts empty on every launch and the settings document —
3504
- * which may legitimately hold a flavour — is what should win. Only a literal
3505
- * `off` in storage proves the user (or another window) actually turned the
3506
- * flavour off, which is the case the cross-tab restore must act on (audit F4). */
3623
+ * the same thing: an absent value only means THIS browser/origin never chose
3624
+ * (a first visit, cleared site data, or a second Desktop instance that landed
3625
+ * on the next free loopback port), while the durable store — which may
3626
+ * legitimately hold a flavour picked elsewhere — is machine-wide. Only a
3627
+ * literal `off` in storage proves the user (or another window) actually turned
3628
+ * the flavour off, which is the case the cross-tab restore must act on
3629
+ * (audit F4). */
3507
3630
  function readExplicitFlavorOff() {
3508
3631
  try {
3509
3632
  return localStorage.getItem(FLAVOR_STORAGE_KEY) === "off";
@@ -3678,13 +3801,17 @@ window.__ModuleLoader__.load({
3678
3801
  if (preference !== "light" && preference !== "dark") return false;
3679
3802
  return preference === livePick;
3680
3803
  }
3681
- /** Required services: slots + locale (settings rows), theme (register +
3682
- * switch), and the settings scope (durable persistence). */
3804
+ /** Required services: slots + locale (settings rows) and theme (register +
3805
+ * switch). The durable settings transport is NOT a hard dependency: which
3806
+ * service provides it depends on the host version (issue #15 — 0.1.7-alpha.1
3807
+ * replaced `settingsScope` with `configForms`), so `createDurableScope`
3808
+ * binds whichever one appears through its own optional injections. Listing
3809
+ * either name here would leave the plugin permanently pending on the other
3810
+ * half of the installed base. */
3683
3811
  const inject = [
3684
3812
  "slots",
3685
3813
  "locale",
3686
- "theme",
3687
- "settingsScope"
3814
+ "theme"
3688
3815
  ];
3689
3816
  /**
3690
3817
  * Register the Catppuccin dictionaries, the flavour themes (lazy — item JJ:
@@ -3762,7 +3889,7 @@ window.__ModuleLoader__.load({
3762
3889
  };
3763
3890
  ctx.effect(() => disposeThemeRegistrations, "catppuccin: flavour theme disposers");
3764
3891
  const glass = new GlassLayer(ctx);
3765
- const scope = bindCatppuccinScope(ctx);
3892
+ const scope = createDurableScope(ctx);
3766
3893
  const buildLocalState = () => ({
3767
3894
  version: 1,
3768
3895
  flavor: readFlavor(),
@@ -3876,7 +4003,7 @@ window.__ModuleLoader__.load({
3876
4003
  const state = durableStateFromSnapshot(snapshot);
3877
4004
  if (state === null) return;
3878
4005
  const local = buildLocalState();
3879
- if (snapshot.user === void 0) {
4006
+ if (!scope.hasUserLayer(snapshot)) {
3880
4007
  if (!isDefaultState(local)) queuePersist();
3881
4008
  return;
3882
4009
  }