sh3-core 0.3.1 → 0.4.1

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/dist/Shell.svelte CHANGED
@@ -101,7 +101,7 @@
101
101
  height: 100%;
102
102
  width: 100%;
103
103
  position: relative;
104
- background: var(--shell-bg);
104
+ background: var(--shell-grad-bg, var(--shell-bg));
105
105
  color: var(--shell-fg);
106
106
  }
107
107
 
@@ -110,7 +110,7 @@
110
110
  align-items: center;
111
111
  gap: var(--shell-pad-md);
112
112
  padding: 0 var(--shell-pad-md);
113
- background: var(--shell-bg-elevated);
113
+ background: var(--shell-grad-bg-elevated, var(--shell-bg-elevated));
114
114
  border-bottom: 1px solid var(--shell-border);
115
115
  user-select: none;
116
116
  }
@@ -123,7 +123,7 @@
123
123
  .shell-content {
124
124
  position: relative;
125
125
  overflow: hidden;
126
- background: var(--shell-bg);
126
+ background: var(--shell-grad-bg, var(--shell-bg));
127
127
  min-width: 0;
128
128
  min-height: 0;
129
129
  }
@@ -132,7 +132,7 @@
132
132
  align-items: center;
133
133
  justify-content: space-between;
134
134
  padding: 0 var(--shell-pad-md);
135
- background: var(--shell-bg-sunken);
135
+ background: var(--shell-grad-bg-sunken, var(--shell-bg-sunken));
136
136
  border-top: 1px solid var(--shell-border);
137
137
  color: var(--shell-fg-muted);
138
138
  font-size: 11px;
package/dist/api.d.ts CHANGED
@@ -21,3 +21,4 @@ export declare const capabilities: {
21
21
  readonly hotInstall: boolean;
22
22
  };
23
23
  export type { ServerShard, ServerShardContext } from './server-shard/types';
24
+ export { setTokenOverrides, clearTokenOverrides, getTokenOverrides, } from './theme';
package/dist/api.js CHANGED
@@ -43,3 +43,5 @@ export const capabilities = {
43
43
  /** Whether this target supports hot-installing packages via dynamic import from blob URL. */
44
44
  hotInstall: typeof Blob !== 'undefined' && typeof URL.createObjectURL === 'function',
45
45
  };
46
+ // Theme token override API (shell-level theming support).
47
+ export { setTokenOverrides, clearTokenOverrides, getTokenOverrides, } from './theme';
@@ -10,6 +10,7 @@ import { mount } from 'svelte';
10
10
  import { Shell } from './index';
11
11
  import { registerShard, registerApp, bootstrap, __setBackend, setLocalOwner, } from './host';
12
12
  import { resolvePlatform } from './platform/index';
13
+ import { hydrateTokenOverrides } from './theme';
13
14
  export async function createShell(config) {
14
15
  var _a, _b;
15
16
  // 1. Platform detection — must run before bootstrap so state zones
@@ -23,6 +24,9 @@ export async function createShell(config) {
23
24
  if (platform.localOwner) {
24
25
  setLocalOwner();
25
26
  }
27
+ // 1c. Apply persisted theme token overrides before any component mounts,
28
+ // so the first frame renders with the user's chosen theme.
29
+ hydrateTokenOverrides();
26
30
  // 1b. Load server-discovered packages (fetched by frontend from /api/packages).
27
31
  if ((_a = config === null || config === void 0 ? void 0 : config.discoveredPackages) === null || _a === void 0 ? void 0 : _a.length) {
28
32
  const { loadBundleModule } = await import('./registry/loader');
@@ -78,7 +78,7 @@
78
78
  inset: 0;
79
79
  padding: 12px 16px;
80
80
  overflow: auto;
81
- background: var(--shell-bg);
81
+ background: var(--shell-grad-bg, var(--shell-bg));
82
82
  color: var(--shell-fg);
83
83
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
84
84
  font-size: 12px;
@@ -52,7 +52,7 @@
52
52
  inset: 0;
53
53
  padding: 12px 16px;
54
54
  overflow: auto;
55
- background: var(--shell-bg);
55
+ background: var(--shell-grad-bg, var(--shell-bg));
56
56
  color: var(--shell-fg);
57
57
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
58
58
  font-size: 12px;
@@ -46,7 +46,7 @@
46
46
  align-items: center;
47
47
  gap: var(--shell-pad-sm);
48
48
  padding: var(--shell-pad-sm) var(--shell-pad-md);
49
- background: var(--shell-bg-elevated);
49
+ background: var(--shell-grad-bg-elevated, var(--shell-bg-elevated));
50
50
  color: var(--shell-fg);
51
51
  border: 1px solid var(--shell-accent);
52
52
  border-radius: 3px;
@@ -186,10 +186,12 @@
186
186
  />
187
187
  {#snippet tabBody(i: number)}
188
188
  {@const entry = tabs.tabs[i]}
189
- <div class="tab-slot-wrapper">
190
- <SlotContainer node={{ type: 'slot', slotId: entry.slotId, viewId: entry.viewId }} label={entry.label} />
191
- <SlotDropZone path={path} />
192
- </div>
189
+ {#if entry}
190
+ <div class="tab-slot-wrapper">
191
+ <SlotContainer node={{ type: 'slot', slotId: entry.slotId, viewId: entry.viewId }} label={entry.label} />
192
+ <SlotDropZone path={path} />
193
+ </div>
194
+ {/if}
193
195
  {/snippet}
194
196
  {:else if tabs?.persistent}
195
197
  <div class="empty-tabs-placeholder">
@@ -73,7 +73,7 @@
73
73
  pointer-events: auto;
74
74
  }
75
75
  .modal-box {
76
- background: var(--shell-bg-elevated);
76
+ background: var(--shell-grad-bg-elevated, var(--shell-bg-elevated));
77
77
  color: var(--shell-fg);
78
78
  border: 1px solid var(--shell-border-strong);
79
79
  border-radius: 4px;
@@ -73,7 +73,7 @@
73
73
  <style>
74
74
  .popup-frame {
75
75
  position: absolute;
76
- background: var(--shell-bg-elevated);
76
+ background: var(--shell-grad-bg-elevated, var(--shell-bg-elevated));
77
77
  color: var(--shell-fg);
78
78
  border: 1px solid var(--shell-border-strong);
79
79
  border-radius: 3px;
@@ -44,7 +44,7 @@
44
44
  align-items: center;
45
45
  gap: var(--shell-pad-md);
46
46
  padding: var(--shell-pad-sm) var(--shell-pad-md);
47
- background: var(--shell-bg-elevated);
47
+ background: var(--shell-grad-bg-elevated, var(--shell-bg-elevated));
48
48
  color: var(--shell-fg);
49
49
  border: 1px solid var(--shell-border-strong);
50
50
  border-left-width: 3px;
@@ -269,7 +269,7 @@
269
269
  display: flex;
270
270
  align-items: center;
271
271
  justify-content: center;
272
- background: var(--shell-bg-elevated);
272
+ background: var(--shell-grad-bg-elevated, var(--shell-bg-elevated));
273
273
  border: none;
274
274
  color: var(--shell-fg-muted);
275
275
  cursor: pointer;
@@ -206,7 +206,7 @@
206
206
  height: 100%;
207
207
  min-width: 0;
208
208
  min-height: 0;
209
- background: var(--shell-bg);
209
+ background: var(--shell-grad-bg, var(--shell-bg));
210
210
  }
211
211
 
212
212
  .tab-strip {
@@ -214,7 +214,7 @@
214
214
  flex: 0 0 auto;
215
215
  display: flex;
216
216
  gap: 1px;
217
- background: var(--shell-bg-sunken);
217
+ background: var(--shell-grad-bg-sunken, var(--shell-bg-sunken));
218
218
  border-bottom: 1px solid var(--shell-border);
219
219
  padding: 0 var(--shell-pad-sm);
220
220
  user-select: none;
@@ -243,11 +243,11 @@
243
243
  }
244
244
  .tab:hover {
245
245
  color: var(--shell-fg);
246
- background: var(--shell-bg-elevated);
246
+ background: var(--shell-grad-bg-elevated, var(--shell-bg-elevated));
247
247
  }
248
248
  .tab.active {
249
249
  color: var(--shell-fg);
250
- background: var(--shell-bg);
250
+ background: var(--shell-grad-bg, var(--shell-bg));
251
251
  border-top-color: var(--shell-accent);
252
252
  }
253
253
  .tab-icon { font-size: 11px; }
@@ -136,7 +136,7 @@
136
136
  justify-content: flex-start;
137
137
  padding: 48px 24px;
138
138
  overflow: auto;
139
- background: var(--shell-bg);
139
+ background: var(--shell-grad-bg, var(--shell-bg));
140
140
  color: var(--shell-fg);
141
141
  font-family: system-ui, sans-serif;
142
142
  }
@@ -196,7 +196,7 @@
196
196
  gap: 4px 16px;
197
197
  align-items: center;
198
198
  padding: 14px 18px;
199
- background: var(--shell-bg-elevated);
199
+ background: var(--shell-grad-bg-elevated, var(--shell-bg-elevated));
200
200
  border: 1px solid var(--shell-border);
201
201
  border-radius: 6px;
202
202
  }
@@ -250,7 +250,7 @@
250
250
  .shell-home-key-input {
251
251
  flex: 1;
252
252
  padding: 8px 12px;
253
- background: var(--shell-bg-elevated);
253
+ background: var(--shell-grad-bg-elevated, var(--shell-bg-elevated));
254
254
  color: var(--shell-fg);
255
255
  border: 1px solid var(--shell-border);
256
256
  border-radius: 4px;
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Apply CSS token overrides to :root and persist to User zone.
3
+ *
4
+ * Keys are token names without the `--` prefix (e.g. `'shell-accent'`).
5
+ * Values are any valid CSS value string.
6
+ *
7
+ * Calling this replaces ALL previous overrides — tokens not present in
8
+ * the new map are removed from :root.
9
+ */
10
+ export declare function setTokenOverrides(overrides: Record<string, string>): void;
11
+ /**
12
+ * Remove all token overrides from :root and clear persisted state.
13
+ * The shell reverts to the default tokens defined in tokens.css.
14
+ */
15
+ export declare function clearTokenOverrides(): void;
16
+ /**
17
+ * Read the currently persisted token overrides.
18
+ * Returns an empty object if no overrides are stored.
19
+ */
20
+ export declare function getTokenOverrides(): Record<string, string>;
21
+ /**
22
+ * Apply persisted token overrides to :root. Called once during shell boot
23
+ * before any component mounts, so the themed colors are visible from the
24
+ * first frame.
25
+ *
26
+ * This is framework-internal — not part of the shard-facing API.
27
+ */
28
+ export declare function hydrateTokenOverrides(): void;
package/dist/theme.js ADDED
@@ -0,0 +1,92 @@
1
+ /*
2
+ * Token override API — allows external packages (like sh3-style) to
3
+ * dynamically change shell CSS tokens and persist the selection to
4
+ * the User zone.
5
+ *
6
+ * The shell reads persisted overrides at boot (see createShell.ts) so
7
+ * themes survive page reloads. This module provides the write path.
8
+ */
9
+ const STORAGE_KEY = 'sh3:user:__shell__:theme';
10
+ /** Keys currently set on :root by setTokenOverrides, tracked for clearTokenOverrides. */
11
+ let appliedKeys = [];
12
+ /**
13
+ * Apply CSS token overrides to :root and persist to User zone.
14
+ *
15
+ * Keys are token names without the `--` prefix (e.g. `'shell-accent'`).
16
+ * Values are any valid CSS value string.
17
+ *
18
+ * Calling this replaces ALL previous overrides — tokens not present in
19
+ * the new map are removed from :root.
20
+ */
21
+ export function setTokenOverrides(overrides) {
22
+ // Remove previously applied keys that are not in the new set
23
+ const newKeys = Object.keys(overrides);
24
+ for (const key of appliedKeys) {
25
+ if (!newKeys.includes(key)) {
26
+ document.documentElement.style.removeProperty(`--${key}`);
27
+ }
28
+ }
29
+ // Apply new overrides
30
+ for (const [key, value] of Object.entries(overrides)) {
31
+ document.documentElement.style.setProperty(`--${key}`, value);
32
+ }
33
+ appliedKeys = newKeys;
34
+ // Persist
35
+ try {
36
+ localStorage.setItem(STORAGE_KEY, JSON.stringify(overrides));
37
+ }
38
+ catch (_a) {
39
+ // Storage full or unavailable — theme applies for this session only
40
+ }
41
+ }
42
+ /**
43
+ * Remove all token overrides from :root and clear persisted state.
44
+ * The shell reverts to the default tokens defined in tokens.css.
45
+ */
46
+ export function clearTokenOverrides() {
47
+ for (const key of appliedKeys) {
48
+ document.documentElement.style.removeProperty(`--${key}`);
49
+ }
50
+ appliedKeys = [];
51
+ try {
52
+ localStorage.removeItem(STORAGE_KEY);
53
+ }
54
+ catch (_a) {
55
+ // Ignore
56
+ }
57
+ }
58
+ /**
59
+ * Read the currently persisted token overrides.
60
+ * Returns an empty object if no overrides are stored.
61
+ */
62
+ export function getTokenOverrides() {
63
+ try {
64
+ const raw = localStorage.getItem(STORAGE_KEY);
65
+ if (!raw)
66
+ return {};
67
+ const parsed = JSON.parse(raw);
68
+ if (typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed)) {
69
+ return parsed;
70
+ }
71
+ return {};
72
+ }
73
+ catch (_a) {
74
+ return {};
75
+ }
76
+ }
77
+ /**
78
+ * Apply persisted token overrides to :root. Called once during shell boot
79
+ * before any component mounts, so the themed colors are visible from the
80
+ * first frame.
81
+ *
82
+ * This is framework-internal — not part of the shard-facing API.
83
+ */
84
+ export function hydrateTokenOverrides() {
85
+ const overrides = getTokenOverrides();
86
+ if (Object.keys(overrides).length === 0)
87
+ return;
88
+ for (const [key, value] of Object.entries(overrides)) {
89
+ document.documentElement.style.setProperty(`--${key}`, value);
90
+ }
91
+ appliedKeys = Object.keys(overrides);
92
+ }
package/dist/tokens.css CHANGED
@@ -11,6 +11,15 @@
11
11
  --shell-bg: #1a1b1e;
12
12
  --shell-bg-elevated: #22232a;
13
13
  --shell-bg-sunken: #141518;
14
+
15
+ /*
16
+ * Gradient layer — undefined by default. When set (e.g. by sh3-style),
17
+ * surfaces use the gradient instead of the flat color via the fallback
18
+ * pattern: background: var(--shell-grad-bg, var(--shell-bg)).
19
+ * Do NOT declare these here — they must remain undefined for the
20
+ * CSS fallback to work.
21
+ */
22
+
14
23
  --shell-border: #2e3038;
15
24
  --shell-border-strong: #3c3f4a;
16
25
 
@@ -66,7 +75,7 @@ body {
66
75
  padding: 0;
67
76
  height: 100%;
68
77
  overflow: hidden; /* SH3 is a shell, not a scrollable document */
69
- background: var(--shell-bg);
78
+ background: var(--shell-grad-bg, var(--shell-bg));
70
79
  color: var(--shell-fg);
71
80
  font-family: var(--shell-font-ui);
72
81
  font-size: var(--shell-font-size);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sh3-core",
3
- "version": "0.3.1",
3
+ "version": "0.4.1",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "dist"