@philomelcc/dsh-workbench 0.1.3 → 0.1.4

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.
Files changed (2) hide show
  1. package/client.js +109 -56
  2. package/package.json +1 -1
package/client.js CHANGED
@@ -435,84 +435,147 @@ window.__ModuleLoader__.load({
435
435
  }
436
436
 
437
437
  // ---------- theme switcher ----------
438
- // `paper` builds on the light palette and overrides the alias layer only.
439
- const PAPER_THEME = {
440
- id: 'paper',
441
- colorScheme: 'light',
442
- tokens: {
443
- '--dsw-alias-bg-base': '#f4efe3',
444
- '--dsw-alias-bg-layer-1': '#efe9db',
445
- '--dsw-alias-bg-layer-2': '#e7dfcd',
446
- '--dsw-alias-bg-overlay': '#fbf7ee',
447
- '--dsw-alias-border-l1': '#d8cfba',
448
- '--dsw-alias-border-l2': '#c6bba2',
449
- '--dsw-alias-brand-primary': '#8a6a45',
450
- '--dsw-alias-label-primary': '#3b342a',
451
- '--dsw-alias-label-secondary': '#6f6557',
452
- '--dsw-alias-state-error-primary': '#a34a3c',
453
- '--dsw-alias-state-warn-primary': '#a8792c',
454
- '--dsw-alias-state-success-primary': '#4f7a5a',
455
- '--dsw-alias-state-idle-primary': '#a49a89',
456
- '--dsw-specific-sidebar-fill': '#efe9db',
457
- },
438
+ // `paper` cannot be a persisted preference: DSH only accepts light / dark /
439
+ // system there, and anything else is dropped when settings resync (that is
440
+ // why switching models used to fall back to dark). So the paper palette is
441
+ // an override layer on top of the legal `light` preference, remembered in
442
+ // localStorage by us.
443
+ const PAPER_KEY = 'dsh-workbench:paper';
444
+ const PAPER_TOKENS = {
445
+ '--dsw-alias-bg-base': '#f4efe3',
446
+ '--dsw-alias-bg-layer-1': '#efe9db',
447
+ '--dsw-alias-bg-layer-2': '#e7dfcd',
448
+ '--dsw-alias-bg-overlay': '#fbf7ee',
449
+ '--dsw-alias-border-l1': '#d8cfba',
450
+ '--dsw-alias-border-l2': '#c6bba2',
451
+ '--dsw-alias-brand-primary': '#8a6a45',
452
+ '--dsw-alias-label-primary': '#3b342a',
453
+ '--dsw-alias-label-secondary': '#6f6557',
454
+ '--dsw-alias-state-error-primary': '#a34a3c',
455
+ '--dsw-alias-state-warn-primary': '#a8792c',
456
+ '--dsw-alias-state-success-primary': '#4f7a5a',
457
+ '--dsw-alias-state-idle-primary': '#a49a89',
458
+ '--dsw-specific-sidebar-fill': '#efe9db',
458
459
  };
459
460
 
460
461
  // Swatches double as both the current-state indicator and the toggle.
461
462
  const THEME_SWATCHES = [
462
- { id: 'dark', label: '深色', color: '#15171a' },
463
- { id: 'light', label: '浅色', color: '#f7f8fa' },
464
- { id: 'paper', label: '纸质', color: '#f4efe3' },
463
+ { id: 'dark', label: '深色', color: '#15171a', ring: 'rgba(255,255,255,0.28)' },
464
+ { id: 'light', label: '浅色', color: '#f7f8fa', ring: 'rgba(0,0,0,0.35)' },
465
+ { id: 'paper', label: '纸质', color: '#efe4cd', ring: 'rgba(0,0,0,0.28)' },
465
466
  ];
466
467
 
467
468
  function createThemeSwitcher(ctx) {
468
- function ThemeSwitcher() {
469
+ const readPreference = () => {
470
+ try {
471
+ const theme = typeof ctx.get === 'function' ? ctx.get('theme') : ctx.theme;
472
+ const snapshot = theme && typeof theme.getTheme === 'function' ? theme.getTheme() : null;
473
+ return snapshot ? snapshot.preference : null;
474
+ } catch (_error) {
475
+ return null;
476
+ }
477
+ };
478
+ const readPaper = () => {
479
+ try { return window.localStorage.getItem(PAPER_KEY) === '1'; } catch (_error) { return false; }
480
+ };
481
+ const writePaper = (on) => {
482
+ try { window.localStorage.setItem(PAPER_KEY, on ? '1' : '0'); } catch (_error) { /* storage unavailable */ }
483
+ };
484
+
485
+ let paperDispose = null;
486
+ function setPaper(on) {
469
487
  const theme = typeof ctx.get === 'function' ? ctx.get('theme') : ctx.theme;
470
- const read = () => {
488
+ if (!theme) return;
489
+ if (on) {
490
+ if (paperDispose) return;
491
+ // Legal persisted preference first, then paint the paper layer on top.
492
+ try { theme.setTheme('light'); } catch (error) { console.warn('[workbench] setTheme light failed', error); }
493
+ const modes = {};
494
+ Object.keys(PAPER_TOKENS).forEach((name) => {
495
+ modes[name] = { light: PAPER_TOKENS[name], dark: PAPER_TOKENS[name] };
496
+ });
471
497
  try {
472
- const snapshot = theme && typeof theme.getTheme === 'function' ? theme.getTheme() : null;
473
- return snapshot ? snapshot.preference : null;
474
- } catch (_error) {
475
- return null;
498
+ paperDispose = typeof theme.overrideTokens === 'function' ? theme.overrideTokens('workbench:paper', modes) : null;
499
+ } catch (error) {
500
+ console.warn('[workbench] paper override failed', error);
501
+ paperDispose = null;
476
502
  }
477
- };
478
- const [preference, setPreference] = React.useState(read);
503
+ } else if (paperDispose) {
504
+ try { paperDispose(); } catch (_error) { /* already gone */ }
505
+ paperDispose = null;
506
+ }
507
+ }
479
508
 
509
+ function ThemeSwitcher() {
510
+ const [paper, setPaperState] = React.useState(readPaper);
511
+ const [preference, setPreference] = React.useState(readPreference);
512
+
513
+ // Re-apply the saved paper layer after a reload / remount.
480
514
  React.useEffect(() => {
481
- if (!theme || typeof theme.getTheme !== 'function' || typeof ctx.on !== 'function') return undefined;
515
+ if (readPaper()) setPaper(true);
516
+ }, []);
517
+
518
+ // Swatches only re-render on real preference changes; the paper layer is
519
+ // local state because the theme service does not know about it.
520
+ React.useEffect(() => {
521
+ if (typeof ctx.on !== 'function') return undefined;
482
522
  let off = null;
483
523
  try {
484
- off = ctx.on('theme/change', () => setPreference(read()));
524
+ off = ctx.on('theme/change', () => {
525
+ const next = readPreference();
526
+ setPreference(next);
527
+ // Somebody picked a concrete palette in DSH's own settings: drop the layer.
528
+ if (next && next !== 'light' && paper) {
529
+ setPaper(false);
530
+ writePaper(false);
531
+ setPaperState(false);
532
+ }
533
+ });
485
534
  } catch (_error) {
486
535
  off = null;
487
536
  }
488
537
  return () => { if (typeof off === 'function') off(); };
489
- }, []);
538
+ }, [paper]);
539
+
540
+ const activeId = paper ? 'paper' : (preference === 'dark' ? 'dark' : 'light');
541
+
542
+ const choose = (id) => {
543
+ const theme = typeof ctx.get === 'function' ? ctx.get('theme') : ctx.theme;
544
+ if (!theme) return;
545
+ if (id === 'paper') {
546
+ setPaper(true);
547
+ writePaper(true);
548
+ setPaperState(true);
549
+ return;
550
+ }
551
+ setPaper(false);
552
+ writePaper(false);
553
+ setPaperState(false);
554
+ try { theme.setTheme(id); } catch (error) { console.warn('[workbench] setTheme failed', error); }
555
+ };
490
556
 
491
557
  return h('div', {
492
- style: { display: 'inline-flex', alignItems: 'center', gap: 7, flex: '0 0 auto' },
558
+ style: { display: 'inline-flex', alignItems: 'center', gap: 8, flex: '0 0 auto' },
493
559
  title: '主题',
494
560
  },
495
561
  THEME_SWATCHES.map((swatch) => {
496
- const active = preference === swatch.id;
562
+ const active = activeId === swatch.id;
497
563
  return h('button', {
498
564
  key: swatch.id,
499
565
  type: 'button',
500
566
  title: swatch.label,
501
567
  'aria-label': swatch.label,
502
568
  'aria-pressed': active ? 'true' : 'false',
503
- onClick: () => {
504
- if (!theme || typeof theme.setTheme !== 'function') return;
505
- try { theme.setTheme(swatch.id); } catch (error) { console.warn('[workbench] setTheme failed', error); }
506
- },
569
+ onClick: () => choose(swatch.id),
507
570
  style: {
508
- width: 15, height: 15, padding: 0, borderRadius: '50%', flex: '0 0 auto',
571
+ width: 17, height: 17, padding: 0, borderRadius: '50%', flex: '0 0 auto',
509
572
  background: swatch.color,
510
- border: '1px solid ' + (active ? 'var(--dsw-alias-brand-primary)' : 'var(--dsw-alias-border-l2)'),
511
- boxShadow: active
512
- ? '0 0 0 2px var(--dsw-alias-bg-base), 0 0 0 3px var(--dsw-alias-brand-primary)'
513
- : 'none',
573
+ // A hairline ring keeps the dark swatch readable on a dark bar;
574
+ // the selected one only thickens and takes the brand color.
575
+ border: (active ? '1.5px' : '1px') + ' solid ' + (active ? 'var(--dsw-alias-brand-primary)' : swatch.ring),
576
+ boxSizing: 'border-box',
514
577
  cursor: 'pointer',
515
- transition: 'box-shadow .12s, border-color .12s',
578
+ transition: 'border-color .12s, border-width .12s',
516
579
  },
517
580
  });
518
581
  }),
@@ -1329,16 +1392,6 @@ div[data-row-key^="session:"]:has(svg[data-state="ongoing"]) svg[data-state="ong
1329
1392
  layoutService = ctx.get('layout') || null;
1330
1393
  layoutGetter = () => ctx.get('layout') || null;
1331
1394
 
1332
- // Extra palette on top of the built-in light/dark pair.
1333
- try {
1334
- const themeService = typeof ctx.get === 'function' ? ctx.get('theme') : null;
1335
- if (themeService && typeof themeService.register === 'function') {
1336
- ctx.effect(() => themeService.register(PAPER_THEME), 'workbench: paper theme');
1337
- }
1338
- } catch (error) {
1339
- console.warn('[workbench] paper theme registration failed', error);
1340
- }
1341
-
1342
1395
  // The shell's own overview lives above the tab row: it is reachable from
1343
1396
  // the top-bar "工作台" icon, so it registers a page without a tab.
1344
1397
  ctx.effect(() => api.registerView({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@philomelcc/dsh-workbench",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "type": "module",
5
5
  "description": "Workbench shell for DeepSeek Harness: a view/page module API, shared UI primitives and a top-bar switcher.",
6
6
  "license": "MIT",