rune-lab 0.5.0-rc.1 → 0.5.0
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 +34 -257
- package/dist/src/core/forge/define-plugin.d.ts +11 -0
- package/dist/src/core/forge/define-plugin.js +11 -1
- package/dist/src/core/kernel/wiring.d.ts +1 -0
- package/dist/src/core/kernel/wiring.js +16 -17
- package/dist/src/plugins/layout/stores/layout.svelte.d.ts +3 -1
- package/dist/src/plugins/layout/stores/layout.svelte.js +9 -3
- package/dist/src/plugins/layout/stores/theme.svelte.js +15 -7
- package/dist/src/plugins/palettes/SettingsModal.svelte +69 -45
- package/dist/src/plugins/palettes/host/PaletteHost.svelte +10 -2
- package/dist/src/plugins/palettes/palettes/shortcuts/ShortcutPalette.svelte +3 -1
- package/dist/src/plugins/palettes/plugin.d.ts +8 -3
- package/dist/src/plugins/palettes/plugin.js +4 -3
- package/dist/src/plugins/palettes/registry/registry.svelte.d.ts +15 -2
- package/dist/src/plugins/palettes/registry/registry.svelte.js +14 -3
- package/dist/src/plugins/palettes/settings-modal/AppMetadata.svelte +59 -0
- package/dist/src/plugins/palettes/settings-modal/AppMetadata.svelte.d.ts +7 -0
- package/dist/src/plugins/palettes/settings-modal/GeneralSection.svelte +27 -0
- package/dist/src/plugins/palettes/settings-modal/GeneralSection.svelte.d.ts +12 -0
- package/dist/src/plugins/palettes/settings-modal/SettingsSearchResults.svelte +60 -30
- package/dist/src/plugins/palettes/settings-modal/SettingsSearchResults.svelte.d.ts +1 -0
- package/dist/src/plugins/palettes/settings-modal/SettingsSectionContent.svelte +20 -7
- package/dist/src/plugins/palettes/settings-modal/SettingsSectionContent.svelte.d.ts +3 -2
- package/dist/src/plugins/palettes/settings-modal/SettingsSidebar.svelte +49 -35
- package/dist/src/plugins/palettes/settings-modal/SettingsSidebar.svelte.d.ts +2 -3
- package/dist/src/plugins/palettes/settings-modal/hash-routing.d.ts +2 -8
- package/dist/src/plugins/palettes/settings-modal/hash-routing.js +9 -37
- package/dist/src/plugins/palettes/settings-modal/route.svelte.d.ts +4 -0
- package/dist/src/plugins/palettes/settings-modal/route.svelte.js +54 -0
- package/dist/src/plugins/palettes/settings-modal/search.d.ts +4 -3
- package/dist/src/plugins/palettes/settings-modal/search.js +34 -6
- package/dist/src/plugins/palettes/settings-modal/sections.d.ts +17 -0
- package/dist/src/plugins/palettes/settings-modal/sections.js +28 -0
- package/dist/src/ui/RuneProvider.svelte +10 -9
- package/dist/src/ui/RuneProvider.svelte.d.ts +0 -3
- package/dist/src/ui/mod.js +1 -1
- package/dist/src/ui/reactivity/app.svelte.d.ts +3 -11
- package/dist/src/ui/reactivity/app.svelte.js +13 -46
- package/dist/src/ui/reactivity/data-store.svelte.d.ts +10 -0
- package/dist/src/ui/reactivity/data-store.svelte.js +35 -0
- package/dist/src/ui/settings/SettingsFields.svelte +7 -5
- package/package.json +1 -1
- package/dist/src/ui/static/attributions.md +0 -11
- package/dist/src/ui/static/img/rune.png +0 -0
package/README.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<h1 align="center">
|
|
2
|
-
<img src="https://raw.githubusercontent.com/Yrrrrrf/rune-lab/refs/heads/main/
|
|
2
|
+
<img src="https://raw.githubusercontent.com/Yrrrrrf/rune-lab/refs/heads/main/apps/lab/src/lib/assets/img/rune.png" alt="Rune Lab Icon" width="128" height="128" description="Icon representing the Svelte Runes system">
|
|
3
3
|
<div align="center">Rune Lab</div>
|
|
4
4
|
</h1>
|
|
5
5
|
|
|
@@ -16,293 +16,70 @@
|
|
|
16
16
|
|
|
17
17
|
**Rune Lab** is a modern, extensible **plugin-based UI shell** for **Svelte 5**
|
|
18
18
|
applications. Harnessing the power of Svelte's new **Runes** system, it provides
|
|
19
|
-
a complete application skeleton with
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
a complete application skeleton with layout management, dynamic theming, i18n,
|
|
20
|
+
keyboard shortcuts, a command palette, toast notifications, and currency
|
|
21
|
+
controls.
|
|
22
22
|
|
|
23
23
|
Everything is wired through a centralized **Provider + Registry + Context**
|
|
24
24
|
architecture, ensuring clean dependency injection and state isolation across
|
|
25
25
|
your app.
|
|
26
26
|
|
|
27
|
-
## Key Features
|
|
28
|
-
|
|
29
|
-
- **✨ Svelte 5 Runes-First:** Built from the ground up using `$state`,
|
|
30
|
-
`$derived`, and `$effect`. No legacy Svelte 4 stores.
|
|
31
|
-
- **🧩 Extensible Plugin Architecture:** Features are isolated into plugins
|
|
32
|
-
(`LayoutPlugin`, `PalettesPlugin`, `MoneyPlugin`). Only load what you need.
|
|
33
|
-
- **🎨 Dynamic Theming & i18n:** 32 DaisyUI themes and 13 pre-configured locales
|
|
34
|
-
powered by Paraglide JS, with zero-flash SSR persistence.
|
|
35
|
-
- **🔗 Declarative Callbacks:** Bridge store changes (theme, language, currency)
|
|
36
|
-
to your own system hooks via `RuneProvider` props.
|
|
37
|
-
- **💾 Abstract Persistence Layer:** Swap between cookies, localStorage,
|
|
38
|
-
sessionStorage, or in-memory state seamlessly via generic drivers.
|
|
39
|
-
- **💸 Robust Money Subsystem:** Backed by Dinero.js for floating-point-safe
|
|
40
|
-
precision arithmetic, complete with exchange rate strategies and masked
|
|
41
|
-
currency inputs.
|
|
42
|
-
- **⌨️ Developer & Power-User Friendly:** Out-of-the-box Command Palette
|
|
43
|
-
(`Ctrl+K`) and interactive Shortcuts Palette (`Ctrl+/`).
|
|
44
|
-
|
|
45
27
|
## Installation
|
|
46
28
|
|
|
47
|
-
### Using NPM / Bun
|
|
48
|
-
|
|
49
29
|
```bash
|
|
50
30
|
npm install rune-lab
|
|
51
31
|
# or
|
|
52
32
|
bun install rune-lab
|
|
53
33
|
```
|
|
54
34
|
|
|
55
|
-
##
|
|
56
|
-
|
|
57
|
-
After installing, two configuration steps are required to ensure the framework's
|
|
58
|
-
components are compiled and styled correctly in your consuming project.
|
|
59
|
-
|
|
60
|
-
### Step 1 — Vite: Process `rune-lab` through the Svelte compiler
|
|
61
|
-
|
|
62
|
-
By default, Vite externalizes `node_modules` during SSR, bypassing the Svelte
|
|
63
|
-
compiler. Add the following to your `vite.config.ts` to process `rune-lab`
|
|
64
|
-
properly:
|
|
65
|
-
|
|
66
|
-
```ts
|
|
67
|
-
// vite.config.ts
|
|
68
|
-
import { defineConfig } from "vite";
|
|
69
|
-
import { sveltekit } from "@sveltejs/kit/vite";
|
|
70
|
-
|
|
71
|
-
export default defineConfig({
|
|
72
|
-
plugins: [sveltekit()],
|
|
73
|
-
ssr: {
|
|
74
|
-
noExternal: ["rune-lab"], // 👈 CRITICAL for Svelte 5 components in node_modules
|
|
75
|
-
},
|
|
76
|
-
});
|
|
77
|
-
```
|
|
78
|
-
|
|
79
|
-
### Step 2 — Tailwind CSS v4: Scan `rune-lab` for utility classes
|
|
35
|
+
## Setup & Quick Start
|
|
80
36
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
the DaisyUI classes used internally by `rune-lab`:
|
|
84
|
-
|
|
85
|
-
```css
|
|
86
|
-
/* app.css / layout.css / global.css */
|
|
87
|
-
@import "tailwindcss";
|
|
88
|
-
@source "../node_modules/rune-lab/dist"; /* 👈 add this */
|
|
89
|
-
```
|
|
90
|
-
|
|
91
|
-
## Quick Start
|
|
92
|
-
|
|
93
|
-
Get your application shell running in less than 40 lines. Inside your
|
|
94
|
-
`+layout.svelte`:
|
|
37
|
+
Configure your application layout with the plugins. Here is what you need to set
|
|
38
|
+
up in your root `+layout.svelte`:
|
|
95
39
|
|
|
96
40
|
```svelte
|
|
97
41
|
<script lang="ts">
|
|
98
|
-
import
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
} from "rune-lab";
|
|
106
|
-
import type { NavigationSection } from "rune-lab";
|
|
107
|
-
import { setLocale } from "$lib/i18n/paraglide/runtime.js";
|
|
42
|
+
import "./layout.css";
|
|
43
|
+
import { RuneProvider, version } from "rune-lab";
|
|
44
|
+
import { I18nPlugin } from "rune-lab/i18n";
|
|
45
|
+
import { LayoutPlugin } from "rune-lab/layout";
|
|
46
|
+
import { PalettesPlugin } from "rune-lab/palettes";
|
|
47
|
+
import type { Snippet } from "svelte";
|
|
48
|
+
import AppLayout from "./AppLayout.svelte";
|
|
108
49
|
|
|
109
|
-
|
|
50
|
+
import faviconUrl from "$lib/static/img/rune.png";
|
|
110
51
|
|
|
111
|
-
|
|
112
|
-
{
|
|
113
|
-
id: "main",
|
|
114
|
-
title: "Main Menu",
|
|
115
|
-
items: [
|
|
116
|
-
{ id: "home", label: "Dashboard", icon: "🏠" },
|
|
117
|
-
{ id: "settings", label: "Settings", icon: "⚙️" },
|
|
118
|
-
],
|
|
119
|
-
},
|
|
120
|
-
];
|
|
52
|
+
let { children }: { children: Snippet } = $props();
|
|
121
53
|
</script>
|
|
122
54
|
|
|
123
|
-
<!-- Initialize the system with your required plugins and callbacks -->
|
|
124
55
|
<RuneProvider
|
|
125
56
|
config={{
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
<WorkspaceLayout>
|
|
134
|
-
{#snippet navigationPanel()}
|
|
135
|
-
<!-- Auto-wires to LayoutStore state -->
|
|
136
|
-
<ConnectedNavigationPanel {sections} />
|
|
137
|
-
{/snippet}
|
|
138
|
-
|
|
139
|
-
{#snippet content()}
|
|
140
|
-
<div class="p-8">
|
|
141
|
-
{@render children()}
|
|
142
|
-
</div>
|
|
143
|
-
{/snippet}
|
|
144
|
-
</WorkspaceLayout>
|
|
145
|
-
</RuneProvider>
|
|
146
|
-
```
|
|
147
|
-
|
|
148
|
-
You now have a fully reactive layout, a keyboard command palette, a toast
|
|
149
|
-
notification system, and theme/language switchers.
|
|
150
|
-
|
|
151
|
-
## Money & Currency Plugin
|
|
152
|
-
|
|
153
|
-
Rune Lab provides a robust money layer that handles precision arithmetic,
|
|
154
|
-
formatting, and live exchange-rate triangulation. To use it, simply register the
|
|
155
|
-
`MoneyPlugin`:
|
|
156
|
-
|
|
157
|
-
```svelte
|
|
158
|
-
<script lang="ts">
|
|
159
|
-
import { MoneyPlugin } from "rune-lab";
|
|
160
|
-
</script>
|
|
161
|
-
|
|
162
|
-
<RuneProvider
|
|
163
|
-
config={{
|
|
164
|
-
"rune-lab.money": {
|
|
165
|
-
defaultCurrency: "USD",
|
|
166
|
-
exchangeRates: {
|
|
167
|
-
base: "USD",
|
|
168
|
-
rates: { MXN: 17.23, EUR: 0.91 },
|
|
169
|
-
},
|
|
57
|
+
favicon: faviconUrl,
|
|
58
|
+
icons: "material",
|
|
59
|
+
app: {
|
|
60
|
+
name: "Rune Lab",
|
|
61
|
+
version: version(),
|
|
62
|
+
description: "Plugin test bench",
|
|
63
|
+
author: "Yrrrrrf",
|
|
170
64
|
},
|
|
65
|
+
// Namespaced per-plugin config
|
|
66
|
+
"rune-lab.layout": "dark",
|
|
171
67
|
}}
|
|
172
|
-
plugins={[LayoutPlugin, PalettesPlugin,
|
|
68
|
+
plugins={[LayoutPlugin, PalettesPlugin, I18nPlugin]}
|
|
173
69
|
>
|
|
174
|
-
|
|
70
|
+
<AppLayout>
|
|
71
|
+
{@render children()}
|
|
72
|
+
</AppLayout>
|
|
175
73
|
</RuneProvider>
|
|
176
74
|
```
|
|
177
75
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
```svelte
|
|
181
|
-
<script lang="ts">
|
|
182
|
-
import { CurrencySelector, MoneyDisplay, MoneyInput, useMoney } from "rune-lab";
|
|
183
|
-
|
|
184
|
-
let price = $state(15000); // Minor units (e.g., cents) -> $150.00
|
|
185
|
-
const { convert, format } = useMoney();
|
|
186
|
-
</script>
|
|
187
|
-
|
|
188
|
-
<!-- Select from available currencies -->
|
|
189
|
-
<CurrencySelector />
|
|
190
|
-
|
|
191
|
-
<!-- Formats safely and localizes based on the active LanguageStore -->
|
|
192
|
-
<MoneyDisplay amount={price} currency="USD" />
|
|
193
|
-
|
|
194
|
-
<!-- Compact notation ($1.5M) -->
|
|
195
|
-
<MoneyDisplay amount={150000000} currency="USD" compact />
|
|
196
|
-
|
|
197
|
-
<!-- Integer-backed masked input (prevents floating point errors) -->
|
|
198
|
-
<MoneyInput bind:amount={price} currency="USD" />
|
|
199
|
-
```
|
|
200
|
-
|
|
201
|
-
## Persistence Drivers
|
|
202
|
-
|
|
203
|
-
Rune Lab provides generic drivers to remember user preferences across reloads.
|
|
204
|
-
Pass one to `config.persistence` on `<RuneProvider>`:
|
|
205
|
-
|
|
206
|
-
- `cookieDriver`: Best for SSR applications (like SvelteKit) to prevent "theme
|
|
207
|
-
flash" on initial load.
|
|
208
|
-
- `localStorageDriver`: Best for client-only applications (SPAs).
|
|
209
|
-
- `sessionStorageDriver`: For preferences that should clear when the browser tab
|
|
210
|
-
closes.
|
|
211
|
-
|
|
212
|
-
```svelte
|
|
213
|
-
<script lang="ts">
|
|
214
|
-
import { cookieDriver } from "rune-lab";
|
|
215
|
-
</script>
|
|
216
|
-
|
|
217
|
-
<RuneProvider config={{ persistence: cookieDriver }} plugins={[...]}>
|
|
218
|
-
```
|
|
219
|
-
|
|
220
|
-
## Advanced Patterns
|
|
76
|
+
## Plugins
|
|
221
77
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
Beyond the `RuneProvider` props, you can imperatively subscribe to any
|
|
225
|
-
`ConfigStore` (theme, language, currency) from your own services or components.
|
|
226
|
-
Callbacks include `try/catch` protection so they never crash the store.
|
|
227
|
-
|
|
228
|
-
```ts
|
|
229
|
-
import { getThemeStore } from "rune-lab";
|
|
230
|
-
|
|
231
|
-
const themeStore = getThemeStore();
|
|
232
|
-
|
|
233
|
-
// Returns an unsubscribe function
|
|
234
|
-
const unsub = themeStore.onChange((newId, oldId) => {
|
|
235
|
-
console.log(`Theme swapped from ${oldId} to ${newId}`);
|
|
236
|
-
});
|
|
237
|
-
```
|
|
238
|
-
|
|
239
|
-
### Keyboard Shortcuts (Auto-Cleanup)
|
|
240
|
-
|
|
241
|
-
Any component deep in your tree can register its own keyboard shortcuts using
|
|
242
|
-
the `useShortcuts` composable. It handles Svelte's `$effect` lifecycle
|
|
243
|
-
internally, ensuring shortcuts unregister when the component unmounts:
|
|
244
|
-
|
|
245
|
-
```svelte
|
|
246
|
-
<script lang="ts">
|
|
247
|
-
import { getToastStore, useShortcuts } from "rune-lab";
|
|
78
|
+
Rune Lab is modular. Plugins are documented in their respective directories:
|
|
248
79
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
{
|
|
253
|
-
id: "feature.save",
|
|
254
|
-
keys: "ctrl+s, cmd+s", // Comma-separated alternative keys
|
|
255
|
-
label: "Save Document",
|
|
256
|
-
category: "Editor",
|
|
257
|
-
scope: "global",
|
|
258
|
-
handler: (e) => {
|
|
259
|
-
e.preventDefault();
|
|
260
|
-
toasts.success("Document Saved!");
|
|
261
|
-
},
|
|
262
|
-
},
|
|
263
|
-
]);
|
|
264
|
-
</script>
|
|
265
|
-
```
|
|
266
|
-
|
|
267
|
-
### SvelteKit Route Syncing
|
|
268
|
-
|
|
269
|
-
To keep your layout's active navigation state synchronized with the SvelteKit
|
|
270
|
-
router, use an `$effect` inside your `+layout.svelte` right after the provider:
|
|
271
|
-
|
|
272
|
-
```svelte
|
|
273
|
-
<script lang="ts">
|
|
274
|
-
import { page } from "$app/state";
|
|
275
|
-
import { getLayoutStore } from "rune-lab";
|
|
276
|
-
|
|
277
|
-
const layoutStore = getLayoutStore();
|
|
278
|
-
|
|
279
|
-
$effect(() => {
|
|
280
|
-
// Automatically open the correct nav tree branch
|
|
281
|
-
const segment = page.url.pathname.split("/")[1] || "home";
|
|
282
|
-
layoutStore.navigate(segment);
|
|
283
|
-
});
|
|
284
|
-
</script>
|
|
285
|
-
```
|
|
286
|
-
|
|
287
|
-
### Calling Toasts from Outside Svelte Components
|
|
288
|
-
|
|
289
|
-
If you need to trigger a toast from a pure `.ts` file (like a fetching utility
|
|
290
|
-
or global error handler), you can use the Toast Bridge:
|
|
291
|
-
|
|
292
|
-
```ts
|
|
293
|
-
import { createToastBridge } from "rune-lab";
|
|
294
|
-
|
|
295
|
-
const { notify } = createToastBridge();
|
|
296
|
-
|
|
297
|
-
export async function fetchUser() {
|
|
298
|
-
try {
|
|
299
|
-
// ...
|
|
300
|
-
} catch (err) {
|
|
301
|
-
// Safely queues the toast if the UI hasn't mounted yet
|
|
302
|
-
notify("Failed to fetch user data", "error");
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
```
|
|
80
|
+
- **Layout Plugin (`rune-lab/layout`)**:
|
|
81
|
+
- **Palettes Plugin (`rune-lab/palettes`)**:
|
|
82
|
+
- **i18n Plugin (`rune-lab/i18n`)**:
|
|
306
83
|
|
|
307
84
|
## License
|
|
308
85
|
|
|
@@ -1,7 +1,16 @@
|
|
|
1
|
+
import type { Schema } from "effect";
|
|
1
2
|
import type { ContributionEntry } from "./define-contribution.js";
|
|
2
3
|
import type { SettingsSchema } from "./define-settings.js";
|
|
3
4
|
import type { BaseSlotSpec } from "./define-slot.js";
|
|
4
5
|
import { type SlotDescriptor } from "./descriptors.js";
|
|
6
|
+
export type SlotConfigInput<TSpec extends BaseSlotSpec> = TSpec extends {
|
|
7
|
+
config: Schema.Schema<infer TConfig, unknown, never>;
|
|
8
|
+
}
|
|
9
|
+
? TConfig
|
|
10
|
+
: unknown;
|
|
11
|
+
export type PluginConfigInput<TSlots extends Record<string, BaseSlotSpec>> = {
|
|
12
|
+
[K in keyof TSlots]?: SlotConfigInput<TSlots[K]>;
|
|
13
|
+
};
|
|
5
14
|
export interface ForgedPlugin<
|
|
6
15
|
TId extends string = string,
|
|
7
16
|
TSlots extends Record<string, BaseSlotSpec> = Record<string, BaseSlotSpec>,
|
|
@@ -13,6 +22,8 @@ export interface ForgedPlugin<
|
|
|
13
22
|
overlays?: unknown[];
|
|
14
23
|
contributions?: ContributionEntry<unknown>[];
|
|
15
24
|
descriptors: Record<keyof TSlots, SlotDescriptor>;
|
|
25
|
+
config?: Record<string, unknown>;
|
|
26
|
+
with(config: PluginConfigInput<TSlots>): ForgedPlugin<TId, TSlots>;
|
|
16
27
|
}
|
|
17
28
|
export type PluginInput =
|
|
18
29
|
| ForgedPlugin<string, Record<string, BaseSlotSpec>>
|
|
@@ -9,7 +9,7 @@ export function definePlugin(spec) {
|
|
|
9
9
|
accessorName: getAccessorName(slotName),
|
|
10
10
|
};
|
|
11
11
|
}
|
|
12
|
-
|
|
12
|
+
const plugin = {
|
|
13
13
|
id: spec.id,
|
|
14
14
|
requires: spec.requires,
|
|
15
15
|
slots: slots,
|
|
@@ -17,5 +17,15 @@ export function definePlugin(spec) {
|
|
|
17
17
|
overlays: spec.overlays,
|
|
18
18
|
contributions: spec.contributions,
|
|
19
19
|
descriptors: descriptors,
|
|
20
|
+
with(config) {
|
|
21
|
+
return {
|
|
22
|
+
...this,
|
|
23
|
+
config: {
|
|
24
|
+
...(this.config || {}),
|
|
25
|
+
...config,
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
},
|
|
20
29
|
};
|
|
30
|
+
return plugin;
|
|
21
31
|
}
|
|
@@ -33,12 +33,6 @@ function normalizePlugins(inputs) {
|
|
|
33
33
|
return true;
|
|
34
34
|
});
|
|
35
35
|
}
|
|
36
|
-
function getPluginConfig(pluginId, config) {
|
|
37
|
-
if (config[pluginId] !== undefined) {
|
|
38
|
-
return config[pluginId];
|
|
39
|
-
}
|
|
40
|
-
return config;
|
|
41
|
-
}
|
|
42
36
|
function sortPlugins(resolved) {
|
|
43
37
|
const pluginMap = new Map(resolved.map((p) => [p.id, p]));
|
|
44
38
|
const graphNodes = resolved.map((p) => ({
|
|
@@ -97,6 +91,7 @@ export function normalizeSlots(sorted) {
|
|
|
97
91
|
expose: slotSpec.expose !== false,
|
|
98
92
|
persist: slotSpec.persist,
|
|
99
93
|
configSchema: slotSpec.config,
|
|
94
|
+
pluginConfig: plugin.config,
|
|
100
95
|
create: slotSpec.create,
|
|
101
96
|
};
|
|
102
97
|
allSlots.push(slot);
|
|
@@ -151,18 +146,22 @@ function buildLayers(slots, options) {
|
|
|
151
146
|
const storeLayer = Layer.effect(
|
|
152
147
|
storeTag,
|
|
153
148
|
Effect.gen(function* () {
|
|
154
|
-
const
|
|
155
|
-
let configSlice =
|
|
149
|
+
const rawConfig = slot.pluginConfig?.[slot.slotName];
|
|
150
|
+
let configSlice = rawConfig;
|
|
156
151
|
if (slot.configSchema) {
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
152
|
+
if (rawConfig !== undefined) {
|
|
153
|
+
try {
|
|
154
|
+
configSlice = Schema.decodeUnknownSync(slot.configSchema)(
|
|
155
|
+
rawConfig,
|
|
156
|
+
);
|
|
157
|
+
} catch (err) {
|
|
158
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
159
|
+
throw new Error(
|
|
160
|
+
`[Kernel] Config validation failed for plugin "${slot.pluginId}", slot "${slot.slotName}": ${errMsg}`,
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
} else {
|
|
164
|
+
configSlice = undefined;
|
|
166
165
|
}
|
|
167
166
|
}
|
|
168
167
|
let persistenceHandle;
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import type { SlotContext } from "rune-lab/core";
|
|
2
2
|
import type { PresetState, WorkspaceItem } from "../types.js";
|
|
3
3
|
export declare class LayoutStore {
|
|
4
|
+
#private;
|
|
4
5
|
workspaces: WorkspaceItem[];
|
|
5
6
|
activeWorkspaceId: string | null;
|
|
6
7
|
activeNavItemId: string | null;
|
|
7
8
|
collapsedSections: Set<string>;
|
|
8
|
-
preset: string;
|
|
9
9
|
zones: Record<
|
|
10
10
|
string,
|
|
11
11
|
{
|
|
@@ -14,6 +14,8 @@ export declare class LayoutStore {
|
|
|
14
14
|
}
|
|
15
15
|
>;
|
|
16
16
|
constructor(_ctx?: SlotContext<unknown>);
|
|
17
|
+
get preset(): string;
|
|
18
|
+
set preset(v: string);
|
|
17
19
|
applyPreset(presetId: string | PresetState): void;
|
|
18
20
|
toggleZone(zoneId: string): void;
|
|
19
21
|
setWorkspaces(items: WorkspaceItem[]): void;
|
|
@@ -5,7 +5,7 @@ export class LayoutStore {
|
|
|
5
5
|
activeNavItemId = $state(null);
|
|
6
6
|
collapsedSections = $state(new Set());
|
|
7
7
|
// Zone & Preset state
|
|
8
|
-
preset = $state("workspace");
|
|
8
|
+
#preset = $state("workspace");
|
|
9
9
|
zones = $state({
|
|
10
10
|
nav: { visible: true, size: 280 },
|
|
11
11
|
strip: { visible: true, size: 64 },
|
|
@@ -15,17 +15,23 @@ export class LayoutStore {
|
|
|
15
15
|
"overlay-anchor": { visible: true },
|
|
16
16
|
});
|
|
17
17
|
constructor(_ctx) {}
|
|
18
|
+
get preset() {
|
|
19
|
+
return this.#preset;
|
|
20
|
+
}
|
|
21
|
+
set preset(v) {
|
|
22
|
+
this.applyPreset(v);
|
|
23
|
+
}
|
|
18
24
|
applyPreset(presetId) {
|
|
19
25
|
if (typeof presetId === "string") {
|
|
20
26
|
const state = PRESETS[presetId];
|
|
21
27
|
if (state) {
|
|
22
|
-
this
|
|
28
|
+
this.#preset = presetId;
|
|
23
29
|
for (const [zone, val] of Object.entries(state)) {
|
|
24
30
|
this.zones[zone] = { ...val };
|
|
25
31
|
}
|
|
26
32
|
}
|
|
27
33
|
} else {
|
|
28
|
-
this
|
|
34
|
+
this.#preset = "custom";
|
|
29
35
|
for (const [zone, val] of Object.entries(presetId)) {
|
|
30
36
|
this.zones[zone] = { ...val };
|
|
31
37
|
}
|
|
@@ -51,13 +51,21 @@ export function createThemeStore(ctx) {
|
|
|
51
51
|
icon: "🎨",
|
|
52
52
|
driver: ctx.persistence,
|
|
53
53
|
});
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
const
|
|
59
|
-
if (store.get(
|
|
60
|
-
store.set(
|
|
54
|
+
const saved = ctx.persistence.get("theme");
|
|
55
|
+
const hasPersisted =
|
|
56
|
+
typeof saved === "string" ? Boolean(saved && store.get(saved)) : false;
|
|
57
|
+
if (!hasPersisted) {
|
|
58
|
+
const configTheme = typeof ctx.config === "string" ? ctx.config : undefined;
|
|
59
|
+
if (configTheme && store.get(configTheme)) {
|
|
60
|
+
store.set(configTheme);
|
|
61
|
+
} else if (BROWSER) {
|
|
62
|
+
const prefersDark = globalThis.matchMedia(
|
|
63
|
+
"(prefers-color-scheme: dark)",
|
|
64
|
+
).matches;
|
|
65
|
+
const systemDefault = prefersDark ? "dark" : "light";
|
|
66
|
+
if (store.get(systemDefault)) {
|
|
67
|
+
store.set(systemDefault);
|
|
68
|
+
}
|
|
61
69
|
}
|
|
62
70
|
}
|
|
63
71
|
return store;
|