@nonamelego/dsh-catppuccin 0.6.5-beta.0 → 0.6.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/lib/client.js CHANGED
@@ -3449,6 +3449,46 @@ window.__ModuleLoader__.load({
3449
3449
  }
3450
3450
  ];
3451
3451
  //#endregion
3452
+ //#region src/boot-palette.ts
3453
+ /** Attribute marking the injected `<style>` so the Client can find it. */
3454
+ const BOOT_PALETTE_ATTR = "data-dsh-catppuccin-boot";
3455
+ //#endregion
3456
+ //#region src/client/boot-palette.ts
3457
+ /**
3458
+ * Client half of the pre-paint palette (issue #30 补充, 2026-10-27).
3459
+ *
3460
+ * The Host injects a `<style data-dsh-catppuccin-boot>` into the index HTML so
3461
+ * the first frame is already the persisted flavour (see `src/boot-palette.ts`).
3462
+ * That sheet is only a stand-in: its selector is `html body` (0,0,2), which
3463
+ * outranks the official `body` token block, so leaving it in place would pin
3464
+ * every token and defeat later theme switches (the user picks Light or turns the
3465
+ * flavour off and nothing changes).
3466
+ *
3467
+ * So it is removed the moment the flavour theme actually takes over — which is
3468
+ * NOT the same instant as "setTheme was called":
3469
+ * - a reload where the runtime preference already equals the flavour id never
3470
+ * calls setTheme (see `applyDesired`'s early return), and
3471
+ * - a built-in pick (Light / Dark / System) must remove it too, because the
3472
+ * sheet belongs to a flavour that is no longer wanted.
3473
+ * The restore effect therefore clears it from every one of those points plus a
3474
+ * fallback timer and the effect teardown.
3475
+ */
3476
+ /**
3477
+ * Remove every injected pre-paint sheet from this document.
3478
+ *
3479
+ * Idempotent and safe when nothing was injected (hosts without the index
3480
+ * injection seam, `flavor: off`, a document that already cleared it).
3481
+ * @returns whether an element was actually removed (diagnostics and tests).
3482
+ */
3483
+ function clearBootPalette() {
3484
+ let removed = false;
3485
+ for (const element of document.querySelectorAll(`[${BOOT_PALETTE_ATTR}]`)) {
3486
+ element.remove();
3487
+ removed = true;
3488
+ }
3489
+ return removed;
3490
+ }
3491
+ //#endregion
3452
3492
  //#region src/client/shiki-tokens.ts
3453
3493
  /**
3454
3494
  * Canonical Catppuccin syntax colours per flavour.
@@ -4238,32 +4278,57 @@ window.__ModuleLoader__.load({
4238
4278
  const reapplyThemePrefs = () => {
4239
4279
  const active = theme.getTheme().preference;
4240
4280
  const flavor = flavorInfo(active);
4241
- if (flavor === void 0 || !themeDisposers.has(flavor.themeId)) return;
4242
- disposeThemeRegistrations();
4281
+ if (flavor === void 0) return;
4282
+ const dispose = themeDisposers.get(flavor.themeId);
4283
+ if (dispose === void 0) return;
4284
+ themeDisposers.delete(flavor.themeId);
4285
+ dispose();
4243
4286
  registerThemeFor(flavor);
4287
+ try {
4288
+ theme.setTheme(flavor.themeId);
4289
+ } catch {}
4244
4290
  };
4245
4291
  let lastWrittenSection;
4246
4292
  let handleStaleConflict;
4293
+ let applyRestoredPreference;
4247
4294
  let pendingLocalPush = false;
4295
+ let pendingLocalSection = null;
4296
+ let maxSeenRevision;
4297
+ let revisionKind;
4298
+ let lastDocumentSection = null;
4248
4299
  let syncedSection = settingsSectionFromState(buildLocalState());
4249
4300
  const baseRevisionTracker = createBaseRevisionTracker();
4250
4301
  const queuePersist = () => {
4251
- if (!isScopeUsable(scope.getSnapshot()) && !settingsSectionsEqual(settingsSectionFromState(buildLocalState()), syncedSection)) pendingLocalPush = true;
4302
+ const localSection = settingsSectionFromState(buildLocalState());
4303
+ if ((lastDocumentSection === null || !settingsSectionsEqual(localSection, lastDocumentSection)) && !settingsSectionsEqual(localSection, syncedSection)) {
4304
+ pendingLocalSection = localSection;
4305
+ if (!isScopeUsable(scope.getSnapshot())) pendingLocalPush = true;
4306
+ }
4252
4307
  baseRevisionTracker.capture(() => scope.getSnapshot().revision);
4253
4308
  scheduleDurablePersist(persistLocal);
4254
4309
  };
4255
4310
  const persistLocal = () => {
4256
4311
  const baseRevision = baseRevisionTracker.take();
4257
- if (!isScopeUsable(scope.getSnapshot())) return;
4312
+ if (!isScopeUsable(scope.getSnapshot())) {
4313
+ pendingLocalSection = null;
4314
+ return;
4315
+ }
4258
4316
  const state = buildLocalState();
4317
+ const flushed = settingsSectionFromState(state);
4259
4318
  persistStateToScope(scope, state, {
4260
4319
  baseRevision,
4261
4320
  lastWrittenSection
4262
4321
  }).then((outcome) => {
4322
+ const settled = pendingLocalSection !== null && settingsSectionsEqual(pendingLocalSection, flushed);
4263
4323
  if (outcome === "written") {
4264
- lastWrittenSection = settingsSectionFromState(state);
4265
- syncedSection = lastWrittenSection;
4324
+ lastWrittenSection = flushed;
4325
+ syncedSection = flushed;
4326
+ if (settled) pendingLocalSection = null;
4327
+ } else if (outcome === "noop") {
4328
+ syncedSection = flushed;
4329
+ if (settled) pendingLocalSection = null;
4266
4330
  } else if (outcome === "stale") {
4331
+ if (settled) pendingLocalSection = null;
4267
4332
  emitConflict();
4268
4333
  handleStaleConflict?.();
4269
4334
  }
@@ -4271,28 +4336,49 @@ window.__ModuleLoader__.load({
4271
4336
  };
4272
4337
  const AUTO_CHECK_BOOT_DELAY_MS = 3e3;
4273
4338
  const AUTO_CHECK_INTERVAL_MS = 216e5;
4339
+ /** How long the Host's pre-paint palette may survive before it is removed
4340
+ * unconditionally (issue #30 补充). Generous on purpose: removing it EARLY
4341
+ * re-exposes the official palette for one frame, which is the bug. */
4342
+ const BOOT_PALETTE_FALLBACK_MS = 1e4;
4274
4343
  let lastAutoResult = null;
4275
4344
  let armAutoCheck;
4276
4345
  ctx.effect(() => {
4277
4346
  let liveBuiltinPick = null;
4347
+ const bootPaletteFallback = window.setTimeout(() => {
4348
+ clearBootPalette();
4349
+ }, BOOT_PALETTE_FALLBACK_MS);
4278
4350
  const originalSetTheme = theme.setTheme;
4279
- const restoreGuardSetTheme = (id) => {
4280
- liveBuiltinPick = isBuiltinPreference(id) ? id : null;
4351
+ const commitTheme = (id, record) => {
4352
+ if (record) liveBuiltinPick = isBuiltinPreference(id) ? id : null;
4281
4353
  originalSetTheme.call(theme, id);
4354
+ clearBootPalette();
4355
+ };
4356
+ const restoreGuardSetTheme = (id) => {
4357
+ commitTheme(id, true);
4282
4358
  };
4283
4359
  theme.setTheme = restoreGuardSetTheme;
4360
+ applyRestoredPreference = (id) => {
4361
+ commitTheme(id, false);
4362
+ };
4284
4363
  let restorePending = false;
4364
+ let pendingDesired;
4285
4365
  const scheduleRestore = (desired) => {
4366
+ pendingDesired = desired;
4286
4367
  if (restorePending) return;
4287
4368
  restorePending = true;
4288
4369
  queueMicrotask(() => {
4289
4370
  restorePending = false;
4371
+ const wanted = pendingDesired;
4372
+ pendingDesired = void 0;
4290
4373
  const preference = theme.getTheme().preference;
4291
- if (preference === desired) return;
4374
+ if (preference === wanted) {
4375
+ clearBootPalette();
4376
+ return;
4377
+ }
4292
4378
  if (builtinPickWins(preference, liveBuiltinPick)) return;
4293
- ensureThemeRegistered(desired);
4379
+ ensureThemeRegistered(wanted);
4294
4380
  try {
4295
- theme.setTheme(desired);
4381
+ commitTheme(wanted, false);
4296
4382
  } catch {}
4297
4383
  });
4298
4384
  };
@@ -4301,11 +4387,17 @@ window.__ModuleLoader__.load({
4301
4387
  const desired = readFlavor();
4302
4388
  if (desired === "off") {
4303
4389
  const preference = theme.getTheme().preference;
4304
- if (readExplicitFlavorOff() && flavorFromThemeId(preference) !== "off") scheduleRestore(readRestoredPreference());
4390
+ if (readExplicitFlavorOff()) {
4391
+ clearBootPalette();
4392
+ if (flavorFromThemeId(preference) !== "off") scheduleRestore(readRestoredPreference());
4393
+ }
4305
4394
  return;
4306
4395
  }
4307
4396
  const preference = theme.getTheme().preference;
4308
- if (preference === desired) return;
4397
+ if (preference === desired) {
4398
+ clearBootPalette();
4399
+ return;
4400
+ }
4309
4401
  if (builtinPickWins(preference, liveBuiltinPick)) return;
4310
4402
  scheduleRestore(desired);
4311
4403
  };
@@ -4323,7 +4415,7 @@ window.__ModuleLoader__.load({
4323
4415
  }
4324
4416
  if (flavorFromThemeId(theme.getTheme().preference) === "off") return;
4325
4417
  try {
4326
- theme.setTheme(readRestoredPreference());
4418
+ commitTheme(readRestoredPreference(), false);
4327
4419
  } catch {}
4328
4420
  };
4329
4421
  window.addEventListener("storage", onStorage);
@@ -4334,6 +4426,15 @@ window.__ModuleLoader__.load({
4334
4426
  const snapshot = scope.getSnapshot();
4335
4427
  const state = durableStateFromSnapshot(snapshot);
4336
4428
  if (state === null) return;
4429
+ const revision = snapshot.revision;
4430
+ if (revision !== void 0) {
4431
+ if (revisionKind !== scope.kind) {
4432
+ revisionKind = scope.kind;
4433
+ maxSeenRevision = void 0;
4434
+ }
4435
+ if (maxSeenRevision !== void 0 && revision < maxSeenRevision) return;
4436
+ maxSeenRevision = Math.max(maxSeenRevision ?? revision, revision);
4437
+ }
4337
4438
  const local = buildLocalState();
4338
4439
  if (pendingLocalPush) {
4339
4440
  pendingLocalPush = false;
@@ -4349,6 +4450,7 @@ window.__ModuleLoader__.load({
4349
4450
  reapplyThemePrefs();
4350
4451
  applyDesired();
4351
4452
  syncedSection = settingsSectionFromState(replayed.state);
4453
+ lastDocumentSection = settingsSectionFromState(state);
4352
4454
  queuePersist();
4353
4455
  return;
4354
4456
  }
@@ -4357,8 +4459,19 @@ window.__ModuleLoader__.load({
4357
4459
  if (!isDefaultState(local)) queuePersist();
4358
4460
  return;
4359
4461
  }
4360
- if (hasUnpersistableOverrides(snapshot.value?.overrides)) queuePersist();
4361
- if (settingsSectionsEqual(settingsSectionFromState(local), settingsSectionFromState(state))) return;
4462
+ const pushCleaned = hasUnpersistableOverrides(snapshot.value?.overrides);
4463
+ const localSection = settingsSectionFromState(local);
4464
+ const documentSection = settingsSectionFromState(state);
4465
+ if (settingsSectionsEqual(localSection, documentSection)) {
4466
+ lastDocumentSection = documentSection;
4467
+ return;
4468
+ }
4469
+ if (pendingLocalSection !== null && !settingsSectionsEqual(pendingLocalSection, documentSection)) return;
4470
+ cancelDurablePersist();
4471
+ baseRevisionTracker.take();
4472
+ pendingLocalSection = null;
4473
+ lastDocumentSection = documentSection;
4474
+ syncedSection = documentSection;
4362
4475
  writeFlavor(state.flavor);
4363
4476
  glass.applyRemote(state.glass);
4364
4477
  writeAutoCheck(state.autoCheck);
@@ -4368,7 +4481,7 @@ window.__ModuleLoader__.load({
4368
4481
  emitPrefs();
4369
4482
  reapplyThemePrefs();
4370
4483
  applyDesired();
4371
- syncedSection = settingsSectionFromState(state);
4484
+ if (pushCleaned) queuePersist();
4372
4485
  };
4373
4486
  applyScopeSnapshot();
4374
4487
  const offScope = scope.subscribe(applyScopeSnapshot);
@@ -4402,10 +4515,13 @@ window.__ModuleLoader__.load({
4402
4515
  offGlass();
4403
4516
  disposer();
4404
4517
  window.removeEventListener("storage", onStorage);
4518
+ window.clearTimeout(bootPaletteFallback);
4519
+ clearBootPalette();
4405
4520
  window.clearTimeout(bootTimer);
4406
4521
  window.clearInterval(intervalTimer);
4407
4522
  cancelDurablePersist();
4408
4523
  handleStaleConflict = void 0;
4524
+ applyRestoredPreference = void 0;
4409
4525
  armAutoCheck = void 0;
4410
4526
  if (theme.setTheme === restoreGuardSetTheme) theme.setTheme = originalSetTheme;
4411
4527
  else console.warn("[dsh-catppuccin] theme.setTheme was replaced by another extension; leaving it in place");
@@ -4426,18 +4542,32 @@ window.__ModuleLoader__.load({
4426
4542
  if (choice === "off") {
4427
4543
  writeFlavor("off");
4428
4544
  try {
4429
- theme.setTheme(readRestoredPreference());
4545
+ if (applyRestoredPreference !== void 0) applyRestoredPreference(readRestoredPreference());
4546
+ else theme.setTheme(readRestoredPreference());
4430
4547
  } catch {}
4431
4548
  queuePersist();
4432
4549
  return;
4433
4550
  }
4434
4551
  const flavor = flavorInfo(choice);
4435
4552
  if (!flavor) return;
4553
+ const previous = readFlavor();
4436
4554
  writeFlavor(flavor.themeId);
4437
4555
  ensureThemeRegistered(flavor.themeId);
4438
4556
  try {
4439
4557
  theme.setTheme(flavor.themeId);
4440
- } catch {}
4558
+ } catch {
4559
+ themeDisposers.get(flavor.themeId)?.();
4560
+ themeDisposers.delete(flavor.themeId);
4561
+ registerThemeFor(flavor);
4562
+ try {
4563
+ theme.setTheme(flavor.themeId);
4564
+ } catch {
4565
+ writeFlavor(previous);
4566
+ emitPrefs();
4567
+ emitConflict();
4568
+ return;
4569
+ }
4570
+ }
4441
4571
  queuePersist();
4442
4572
  },
4443
4573
  overrides: overridesSnapshot,