rune-lab 0.3.0 → 0.4.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.
Files changed (133) hide show
  1. package/README.md +1 -1
  2. package/dist/core/design-tokens/props.d.ts +52 -0
  3. package/dist/core/design-tokens/props.d.ts.map +1 -0
  4. package/dist/core/design-tokens/props.js +34 -0
  5. package/dist/core/exchange-rate/strategies.d.ts +52 -0
  6. package/dist/core/exchange-rate/strategies.d.ts.map +1 -0
  7. package/dist/core/exchange-rate/strategies.js +72 -0
  8. package/dist/core/index.d.ts +8 -3
  9. package/dist/core/index.d.ts.map +1 -1
  10. package/dist/core/index.js +8 -3
  11. package/dist/core/internal/message-resolver.d.ts +1 -1
  12. package/dist/core/internal/message-resolver.d.ts.map +1 -1
  13. package/dist/core/layout/types.d.ts +60 -0
  14. package/dist/core/layout/types.d.ts.map +1 -0
  15. package/dist/core/layout/types.js +4 -0
  16. package/dist/core/money/index.d.ts +1 -1
  17. package/dist/core/money/index.d.ts.map +1 -1
  18. package/dist/core/money/index.js +1 -1
  19. package/dist/core/money/money-primitive.d.ts +101 -0
  20. package/dist/core/money/money-primitive.d.ts.map +1 -0
  21. package/dist/core/money/money-primitive.js +161 -0
  22. package/dist/core/money/money.d.ts +74 -2
  23. package/dist/core/money/money.d.ts.map +1 -1
  24. package/dist/core/money/money.js +120 -2
  25. package/dist/core/shortcuts/types.d.ts +60 -0
  26. package/dist/core/shortcuts/types.d.ts.map +1 -0
  27. package/dist/core/shortcuts/types.js +4 -0
  28. package/dist/index.d.ts +4 -3
  29. package/dist/index.js +6 -4
  30. package/dist/state/api.svelte.js +2 -2
  31. package/dist/state/app.svelte.js +1 -1
  32. package/dist/state/auth/index.d.ts +2 -2
  33. package/dist/state/auth/index.js +1 -1
  34. package/dist/state/auth/session.svelte.d.ts +1 -1
  35. package/dist/state/auth/session.svelte.js +7 -5
  36. package/dist/state/auth/types.d.ts +1 -1
  37. package/dist/state/cart.svelte.d.ts +1 -1
  38. package/dist/state/cart.svelte.js +1 -1
  39. package/dist/state/commands.svelte.d.ts +7 -7
  40. package/dist/state/commands.svelte.js +1 -1
  41. package/dist/state/composables/useMoney.d.ts +19 -3
  42. package/dist/state/composables/useMoney.js +70 -6
  43. package/dist/state/composables/useMoneyFilter.d.ts +20 -0
  44. package/dist/state/composables/useMoneyFilter.js +81 -0
  45. package/dist/state/composables/usePersistence.d.ts +1 -1
  46. package/dist/state/composables/usePersistence.js +1 -1
  47. package/dist/state/composables/useRuneLab.d.ts +1 -1
  48. package/dist/state/composables/useRuneLab.js +2 -2
  49. package/dist/state/composables/useShortcuts.d.ts +33 -0
  50. package/dist/state/composables/useShortcuts.js +75 -0
  51. package/dist/state/context.d.ts +1 -0
  52. package/dist/state/context.js +1 -0
  53. package/dist/state/createConfigStore.svelte.d.ts +4 -31
  54. package/dist/state/createConfigStore.svelte.js +62 -51
  55. package/dist/state/currency.svelte.d.ts +13 -9
  56. package/dist/state/currency.svelte.js +26 -10
  57. package/dist/state/currency.test.d.ts +1 -0
  58. package/dist/state/currency.test.js +35 -0
  59. package/dist/state/exchange-rate.svelte.d.ts +43 -0
  60. package/dist/state/exchange-rate.svelte.js +145 -0
  61. package/dist/state/exchange-rate.test.d.ts +1 -0
  62. package/dist/state/exchange-rate.test.js +75 -0
  63. package/dist/state/index.d.ts +26 -19
  64. package/dist/state/index.js +25 -18
  65. package/dist/state/language.svelte.d.ts +3 -10
  66. package/dist/state/language.svelte.js +4 -5
  67. package/dist/state/layout.svelte.d.ts +1 -1
  68. package/dist/state/layout.svelte.js +4 -4
  69. package/dist/state/persistence/drivers.d.ts +1 -1
  70. package/dist/state/persistence/drivers.js +9 -7
  71. package/dist/state/persistence/drivers.test.d.ts +1 -0
  72. package/dist/state/persistence/drivers.test.js +79 -0
  73. package/dist/state/persistence/provider.d.ts +23 -0
  74. package/dist/state/persistence/provider.js +43 -0
  75. package/dist/state/persistence/provider.test.d.ts +1 -0
  76. package/dist/state/persistence/provider.test.js +51 -0
  77. package/dist/state/registry/index.d.ts +44 -0
  78. package/dist/state/registry/index.js +58 -0
  79. package/dist/state/registry/registry.test.d.ts +1 -0
  80. package/dist/state/registry/registry.test.js +112 -0
  81. package/dist/state/registry/types.d.ts +20 -0
  82. package/dist/state/registry/types.js +3 -0
  83. package/dist/state/shortcuts.svelte.js +4 -4
  84. package/dist/state/theme.svelte.d.ts +3 -10
  85. package/dist/state/theme.svelte.js +8 -8
  86. package/dist/state/toast-bridge.d.ts +1 -1
  87. package/dist/state/toast.svelte.js +1 -1
  88. package/dist/ui/components/ApiMonitor.svelte +2 -2
  89. package/dist/ui/components/Icon.svelte +1 -1
  90. package/dist/ui/components/RuneProvider.svelte +28 -8
  91. package/dist/ui/components/RuneProvider.svelte.d.ts +12 -5
  92. package/dist/ui/components/Toaster.svelte +1 -1
  93. package/dist/ui/components/money/MoneyDisplay.svelte +91 -18
  94. package/dist/ui/components/money/MoneyDisplay.svelte.d.ts +15 -3
  95. package/dist/ui/components/money/MoneyDisplay.svelte.test.d.ts +1 -1
  96. package/dist/ui/components/money/MoneyDisplay.svelte.test.js +45 -2
  97. package/dist/ui/components/money/MoneyInput.svelte +123 -42
  98. package/dist/ui/components/money/MoneyInput.svelte.d.ts +14 -5
  99. package/dist/ui/features/command-palette/CommandPalette.svelte +3 -3
  100. package/dist/ui/features/config/APP_CONFIGURATIONS.d.ts +29 -0
  101. package/dist/ui/features/config/APP_CONFIGURATIONS.js +38 -0
  102. package/dist/ui/features/config/CurrencySelector.svelte +10 -36
  103. package/dist/ui/features/config/LanguageSelector.svelte +10 -33
  104. package/dist/ui/features/config/ResourceSelector.svelte +92 -0
  105. package/dist/ui/features/config/ResourceSelector.svelte.d.ts +25 -0
  106. package/dist/ui/features/config/ThemeSelector.svelte +11 -34
  107. package/dist/ui/features/shortcuts/ShortcutBinder.svelte +17 -0
  108. package/dist/ui/features/shortcuts/ShortcutBinder.svelte.d.ts +7 -0
  109. package/dist/ui/features/shortcuts/ShortcutPalette.svelte +3 -3
  110. package/dist/ui/index.d.ts +5 -1
  111. package/dist/ui/index.js +8 -3
  112. package/dist/ui/layout/ConnectedNavigationPanel.svelte +7 -8
  113. package/dist/ui/layout/ConnectedNavigationPanel.svelte.d.ts +1 -1
  114. package/dist/ui/layout/ConnectedWorkspaceStrip.svelte +5 -3
  115. package/dist/ui/layout/ConnectedWorkspaceStrip.svelte.d.ts +1 -1
  116. package/dist/ui/layout/NavigationPanel.svelte +1 -1
  117. package/dist/ui/layout/NavigationPanel.svelte.d.ts +1 -1
  118. package/dist/ui/layout/WorkspaceLayout.svelte +9 -1
  119. package/dist/ui/layout/WorkspaceLayout.svelte.d.ts +7 -0
  120. package/dist/ui/layout/WorkspaceStrip.svelte +1 -1
  121. package/dist/ui/layout/WorkspaceStrip.svelte.d.ts +1 -1
  122. package/dist/ui/layout/connection-factory.d.ts +50 -0
  123. package/dist/ui/layout/connection-factory.js +58 -0
  124. package/dist/ui/layout/index.d.ts +2 -2
  125. package/dist/ui/layout/index.js +1 -1
  126. package/dist/ui/paraglide/README.md +53 -0
  127. package/dist/ui/paraglide/runtime.d.ts +105 -124
  128. package/dist/ui/paraglide/runtime.js +162 -127
  129. package/dist/ui/paraglide/server.d.ts +6 -17
  130. package/dist/ui/paraglide/server.js +11 -20
  131. package/dist/ui/primitives/DatePicker.svelte +1 -1
  132. package/package.json +8 -8
  133. package/dist/state/daisyui.d.ts +0 -4
@@ -0,0 +1,51 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { resolveDriver } from "./provider.ts";
3
+ describe("DriverProvider", () => {
4
+ describe("resolveDriver", () => {
5
+ it("should return inMemoryDriver when given undefined", () => {
6
+ const driver = resolveDriver(undefined);
7
+ expect(driver).toBeDefined();
8
+ expect(typeof driver.get).toBe("function");
9
+ expect(typeof driver.set).toBe("function");
10
+ expect(typeof driver.remove).toBe("function");
11
+ });
12
+ it("should pass through a concrete PersistenceDriver", () => {
13
+ const mockDriver = {
14
+ get: () => null,
15
+ set: () => { },
16
+ remove: () => { },
17
+ };
18
+ const driver = resolveDriver(mockDriver);
19
+ expect(driver).toBe(mockDriver);
20
+ });
21
+ it("should resolve a factory function to a concrete driver", () => {
22
+ const mockDriver = {
23
+ get: () => null,
24
+ set: () => { },
25
+ remove: () => { },
26
+ };
27
+ const driver = resolveDriver(() => mockDriver);
28
+ expect(driver).toBe(mockDriver);
29
+ });
30
+ it("should fall back to inMemoryDriver when factory returns undefined", () => {
31
+ const driver = resolveDriver(() => undefined);
32
+ expect(driver).toBeDefined();
33
+ expect(typeof driver.get).toBe("function");
34
+ });
35
+ it("inMemoryDriver fallback should be functional", () => {
36
+ const driver = resolveDriver(undefined);
37
+ driver.set("test-key", "test-value");
38
+ expect(driver.get("test-key")).toBe("test-value");
39
+ driver.remove("test-key");
40
+ expect(driver.get("test-key")).toBeNull();
41
+ });
42
+ it("should create isolated inMemoryDriver instances per call", () => {
43
+ const driverA = resolveDriver(undefined);
44
+ const driverB = resolveDriver(undefined);
45
+ driverA.set("key", "A");
46
+ driverB.set("key", "B");
47
+ expect(driverA.get("key")).toBe("A");
48
+ expect(driverB.get("key")).toBe("B");
49
+ });
50
+ });
51
+ });
@@ -0,0 +1,44 @@
1
+ import type { StoreRegistryEntry } from "./types.ts";
2
+ export type { StoreFactory, StoreRegistryEntry } from "./types.ts";
3
+ /**
4
+ * The global store registry.
5
+ * Pre-populated with built-in stores by RuneProvider.
6
+ * Third-party plugins can register additional entries before mounting.
7
+ *
8
+ * @example
9
+ * ```ts
10
+ * // Plugin registration (before RuneProvider mounts)
11
+ * registerStore({
12
+ * key: "analytics",
13
+ * factory: (config, driver) => createAnalyticsStore(config),
14
+ * optional: true,
15
+ * noPersistence: true,
16
+ * });
17
+ * ```
18
+ */
19
+ declare const STORE_REGISTRY: Map<string, StoreRegistryEntry<unknown, unknown>>;
20
+ /**
21
+ * Register a store entry in the global registry.
22
+ * Must be called before RuneProvider mounts for the store to be auto-wired.
23
+ *
24
+ * @param entry - Store registry entry with key, factory, and options.
25
+ * @throws Error if a store with the same key is already registered.
26
+ */
27
+ export declare function registerStore(entry: StoreRegistryEntry): void;
28
+ /**
29
+ * Get a registered store entry by key.
30
+ */
31
+ export declare function getRegisteredStore(key: string): StoreRegistryEntry | undefined;
32
+ /**
33
+ * Get all registered store entries.
34
+ */
35
+ export declare function getAllRegisteredStores(): Map<string, StoreRegistryEntry>;
36
+ /**
37
+ * Remove a store registration (primarily useful for testing).
38
+ */
39
+ export declare function unregisterStore(key: string): boolean;
40
+ /**
41
+ * Clear all store registrations (primarily useful for testing).
42
+ */
43
+ export declare function clearRegistry(): void;
44
+ export { STORE_REGISTRY };
@@ -0,0 +1,58 @@
1
+ // sdk/state/src/registry/index.ts
2
+ // Store Registry — declarative map of context keys to store factories.
3
+ // RuneProvider iterates this at mount time to auto-wire all stores.
4
+ /**
5
+ * The global store registry.
6
+ * Pre-populated with built-in stores by RuneProvider.
7
+ * Third-party plugins can register additional entries before mounting.
8
+ *
9
+ * @example
10
+ * ```ts
11
+ * // Plugin registration (before RuneProvider mounts)
12
+ * registerStore({
13
+ * key: "analytics",
14
+ * factory: (config, driver) => createAnalyticsStore(config),
15
+ * optional: true,
16
+ * noPersistence: true,
17
+ * });
18
+ * ```
19
+ */
20
+ const STORE_REGISTRY = new Map();
21
+ /**
22
+ * Register a store entry in the global registry.
23
+ * Must be called before RuneProvider mounts for the store to be auto-wired.
24
+ *
25
+ * @param entry - Store registry entry with key, factory, and options.
26
+ * @throws Error if a store with the same key is already registered.
27
+ */
28
+ export function registerStore(entry) {
29
+ if (STORE_REGISTRY.has(entry.key)) {
30
+ console.warn(`[StoreRegistry] Overwriting existing store registration for key "${entry.key}"`);
31
+ }
32
+ STORE_REGISTRY.set(entry.key, entry);
33
+ }
34
+ /**
35
+ * Get a registered store entry by key.
36
+ */
37
+ export function getRegisteredStore(key) {
38
+ return STORE_REGISTRY.get(key);
39
+ }
40
+ /**
41
+ * Get all registered store entries.
42
+ */
43
+ export function getAllRegisteredStores() {
44
+ return STORE_REGISTRY;
45
+ }
46
+ /**
47
+ * Remove a store registration (primarily useful for testing).
48
+ */
49
+ export function unregisterStore(key) {
50
+ return STORE_REGISTRY.delete(key);
51
+ }
52
+ /**
53
+ * Clear all store registrations (primarily useful for testing).
54
+ */
55
+ export function clearRegistry() {
56
+ STORE_REGISTRY.clear();
57
+ }
58
+ export { STORE_REGISTRY };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,112 @@
1
+ import { describe, expect, it, beforeEach } from "vitest";
2
+ import { registerStore, getRegisteredStore, getAllRegisteredStores, unregisterStore, clearRegistry, STORE_REGISTRY, } from "./index.ts";
3
+ describe("StoreRegistry", () => {
4
+ // Clean slate for each test
5
+ beforeEach(() => {
6
+ clearRegistry();
7
+ });
8
+ describe("registerStore", () => {
9
+ it("should register a store entry", () => {
10
+ registerStore({
11
+ key: "test",
12
+ factory: () => ({ value: 42 }),
13
+ });
14
+ expect(STORE_REGISTRY.size).toBe(1);
15
+ expect(STORE_REGISTRY.has("test")).toBe(true);
16
+ });
17
+ it("should allow overwriting with a warning", () => {
18
+ registerStore({ key: "test", factory: () => "v1" });
19
+ registerStore({ key: "test", factory: () => "v2" });
20
+ expect(STORE_REGISTRY.size).toBe(1);
21
+ const entry = getRegisteredStore("test");
22
+ // Factory should be the second one
23
+ expect(entry?.factory({}, {})).toBe("v2");
24
+ });
25
+ it("should register multiple entries", () => {
26
+ registerStore({ key: "a", factory: () => "A" });
27
+ registerStore({ key: "b", factory: () => "B" });
28
+ registerStore({ key: "c", factory: () => "C" });
29
+ expect(STORE_REGISTRY.size).toBe(3);
30
+ });
31
+ });
32
+ describe("getRegisteredStore", () => {
33
+ it("should return the entry for a registered key", () => {
34
+ registerStore({
35
+ key: "theme",
36
+ factory: () => "theme-store",
37
+ optional: false,
38
+ });
39
+ const entry = getRegisteredStore("theme");
40
+ expect(entry).toBeDefined();
41
+ expect(entry.key).toBe("theme");
42
+ expect(entry.optional).toBe(false);
43
+ });
44
+ it("should return undefined for unregistered keys", () => {
45
+ expect(getRegisteredStore("nonexistent")).toBeUndefined();
46
+ });
47
+ });
48
+ describe("getAllRegisteredStores", () => {
49
+ it("should return all registered stores", () => {
50
+ registerStore({ key: "a", factory: () => "A" });
51
+ registerStore({ key: "b", factory: () => "B" });
52
+ const all = getAllRegisteredStores();
53
+ expect(all.size).toBe(2);
54
+ expect(all.has("a")).toBe(true);
55
+ expect(all.has("b")).toBe(true);
56
+ });
57
+ });
58
+ describe("unregisterStore", () => {
59
+ it("should remove a registered store", () => {
60
+ registerStore({ key: "test", factory: () => "val" });
61
+ expect(STORE_REGISTRY.has("test")).toBe(true);
62
+ const result = unregisterStore("test");
63
+ expect(result).toBe(true);
64
+ expect(STORE_REGISTRY.has("test")).toBe(false);
65
+ });
66
+ it("should return false for non-existent keys", () => {
67
+ expect(unregisterStore("nope")).toBe(false);
68
+ });
69
+ });
70
+ describe("clearRegistry", () => {
71
+ it("should remove all entries", () => {
72
+ registerStore({ key: "a", factory: () => "A" });
73
+ registerStore({ key: "b", factory: () => "B" });
74
+ expect(STORE_REGISTRY.size).toBe(2);
75
+ clearRegistry();
76
+ expect(STORE_REGISTRY.size).toBe(0);
77
+ });
78
+ });
79
+ describe("StoreFactory contract", () => {
80
+ it("factory receives config and driver, returns store", () => {
81
+ const mockDriver = {
82
+ get: () => null,
83
+ set: () => { },
84
+ remove: () => { },
85
+ };
86
+ registerStore({
87
+ key: "analytics",
88
+ factory: (config, driver) => ({
89
+ type: "analytics",
90
+ hasDriver: !!driver,
91
+ }),
92
+ optional: true,
93
+ noPersistence: true,
94
+ });
95
+ const entry = getRegisteredStore("analytics");
96
+ const store = entry.factory({ apiUrl: "test" }, mockDriver);
97
+ expect(store).toEqual({
98
+ type: "analytics",
99
+ hasDriver: true,
100
+ });
101
+ });
102
+ it("optional factory can return null", () => {
103
+ registerStore({
104
+ key: "optional-feature",
105
+ factory: () => null,
106
+ optional: true,
107
+ });
108
+ const entry = getRegisteredStore("optional-feature");
109
+ expect(entry.factory({}, {})).toBeNull();
110
+ });
111
+ });
112
+ });
@@ -0,0 +1,20 @@
1
+ import type { PersistenceDriver } from "@internal/core";
2
+ /**
3
+ * Factory function signature for creating a store.
4
+ * Receives the resolved config and driver, returns a store instance or null (for optional stores).
5
+ */
6
+ export type StoreFactory<TConfig = unknown, TStore = unknown> = (config: TConfig, driver: PersistenceDriver) => TStore | null;
7
+ /**
8
+ * An entry in the Store Registry.
9
+ * Maps a context key to its factory function and metadata.
10
+ */
11
+ export interface StoreRegistryEntry<TConfig = unknown, TStore = unknown> {
12
+ /** Unique key matching the RUNE_LAB_CONTEXT key (e.g., "theme", "language") */
13
+ key: string;
14
+ /** Factory function that creates the store */
15
+ factory: StoreFactory<TConfig, TStore>;
16
+ /** If true, the store is optional — null return from factory means "skip" */
17
+ optional?: boolean;
18
+ /** If true, the store does not use persistence at all */
19
+ noPersistence?: boolean;
20
+ }
@@ -0,0 +1,3 @@
1
+ // sdk/state/src/registry/types.ts
2
+ // Types for the Store Registry — the declarative store auto-wiring engine.
3
+ export {};
@@ -1,6 +1,6 @@
1
1
  import hotkeys from "hotkeys-js";
2
2
  import { getContext, untrack } from "svelte";
3
- import { RUNE_LAB_CONTEXT } from "./context";
3
+ import { RUNE_LAB_CONTEXT } from "./context.ts";
4
4
  import { DEV } from "esm-env";
5
5
  /**
6
6
  * Built-in layout shortcuts
@@ -166,7 +166,7 @@ export function shortcutListener(node, shortcutStore) {
166
166
  for (const entry of shortcutStore.entries) {
167
167
  if (entry.enabled === false)
168
168
  continue;
169
- hotkeys(entry.keys, "all", (event, handler) => {
169
+ hotkeys(entry.keys, "all", (event, _handler) => {
170
170
  // Scoping logic: check if the event target is within the required scope
171
171
  if (entry.scope === "global") {
172
172
  // Hotkeys.js already filters inputs by default
@@ -202,9 +202,9 @@ export function shortcutListener(node, shortcutStore) {
202
202
  hotkeys.setScope("global");
203
203
  }
204
204
  };
205
- window.addEventListener("focusin", handleFocusIn);
205
+ globalThis.addEventListener("focusin", handleFocusIn);
206
206
  return () => {
207
- window.removeEventListener("focusin", handleFocusIn);
207
+ globalThis.removeEventListener("focusin", handleFocusIn);
208
208
  };
209
209
  });
210
210
  return {
@@ -1,9 +1,9 @@
1
- import { type ConfigStore } from "./createConfigStore.svelte";
1
+ import { type ConfigStore } from "./createConfigStore.svelte.ts";
2
2
  export interface Theme {
3
3
  name: string;
4
4
  icon: string;
5
5
  }
6
- import type { PersistenceDriver } from "rune-lab/core";
6
+ import type { PersistenceDriver } from "@internal/core";
7
7
  export interface ThemeStoreOptions {
8
8
  driver?: PersistenceDriver | (() => PersistenceDriver | undefined);
9
9
  /** Additional custom themes to append to the built-in DaisyUI set */
@@ -11,12 +11,5 @@ export interface ThemeStoreOptions {
11
11
  /** Fallback theme if no persisted value exists (after system preference check) */
12
12
  defaultTheme?: string;
13
13
  }
14
- export declare function createThemeStore(driverOrOptions?: PersistenceDriver | (() => PersistenceDriver | undefined) | ThemeStoreOptions): {
15
- current: string;
16
- available: Theme[];
17
- set(id: string): void;
18
- get(id: string): Theme | undefined;
19
- getProp<K extends keyof Theme>(prop: K, id?: string | undefined): Theme[K] | undefined;
20
- addItems(newItems: Theme[]): void;
21
- };
14
+ export declare function createThemeStore(driverOrOptions?: PersistenceDriver | (() => PersistenceDriver | undefined) | ThemeStoreOptions): ConfigStore<Theme>;
22
15
  export declare function getThemeStore(): ConfigStore<Theme>;
@@ -1,7 +1,8 @@
1
+ // @ts-nocheck: legacy daisyui imports and complex ConfigStore typing
1
2
  // client/packages/ui/src/state/theme-config.svelte.ts
2
- import { createConfigStore, } from "./createConfigStore.svelte";
3
+ import { createConfigStore, } from "./createConfigStore.svelte.ts";
3
4
  import { getContext } from "svelte";
4
- import { RUNE_LAB_CONTEXT } from "./context";
5
+ import { RUNE_LAB_CONTEXT } from "./context.ts";
5
6
  // Icon map for known themes - unknown ones fall back to 🎨
6
7
  const THEME_ICONS = {
7
8
  light: "🌞",
@@ -40,13 +41,14 @@ const THEME_ICONS = {
40
41
  abyss: "🌌",
41
42
  silk: "🎀",
42
43
  };
44
+ import { resolveDriver } from "./persistence/provider.ts";
45
+ import themeOrder from "daisyui/functions/themeOrder.js";
46
+ import { BROWSER } from "esm-env";
43
47
  // Derived from daisyUI directly — stays in sync automatically
44
48
  const THEMES = themeOrder.map((name) => ({
45
49
  name,
46
50
  icon: THEME_ICONS[name] ?? "🎨",
47
51
  }));
48
- import themeOrder from "daisyui/functions/themeOrder.js";
49
- import { BROWSER } from "esm-env";
50
52
  export function createThemeStore(driverOrOptions) {
51
53
  // Normalize overloaded argument
52
54
  const opts = driverOrOptions && typeof driverOrOptions === "object" &&
@@ -55,9 +57,7 @@ export function createThemeStore(driverOrOptions) {
55
57
  : {
56
58
  driver: driverOrOptions,
57
59
  };
58
- const resolvedDriver = typeof opts.driver === "function"
59
- ? opts.driver()
60
- : opts.driver;
60
+ const resolvedDriver = resolveDriver(opts.driver);
61
61
  const store = createConfigStore({
62
62
  items: THEMES,
63
63
  storageKey: "theme",
@@ -72,7 +72,7 @@ export function createThemeStore(driverOrOptions) {
72
72
  }
73
73
  // System preference detection — only if no persisted value was loaded
74
74
  if (!resolvedDriver?.get("theme") && BROWSER) {
75
- const prefersDark = window.matchMedia("(prefers-color-scheme: dark)").matches;
75
+ const prefersDark = globalThis.matchMedia("(prefers-color-scheme: dark)").matches;
76
76
  const systemDefault = prefersDark ? "dark" : "light";
77
77
  const chosen = opts.defaultTheme ?? systemDefault;
78
78
  if (store.get(chosen)) {
@@ -1,4 +1,4 @@
1
- import type { ToastStore, ToastType } from "./toast.svelte";
1
+ import type { ToastStore, ToastType } from "./toast.svelte.ts";
2
2
  /**
3
3
  * Notifies the global toast system.
4
4
  * If called before the ToastStore is initialized (e.g., in a module-level store),
@@ -1,5 +1,5 @@
1
1
  import { getContext } from "svelte";
2
- import { RUNE_LAB_CONTEXT } from "./context";
2
+ import { RUNE_LAB_CONTEXT } from "./context.ts";
3
3
  export class ToastStore {
4
4
  toasts = $state([]);
5
5
  /**
@@ -1,6 +1,6 @@
1
1
  <script lang="ts">
2
- import { getApiStore } from "rune-lab/state";
3
- import { getToastStore } from "rune-lab/state";
2
+ import { getApiStore } from "@internal/state";
3
+ import { getToastStore } from "@internal/state";
4
4
 
5
5
  const apiStore = getApiStore();
6
6
  const toastStore = getToastStore();
@@ -1,5 +1,5 @@
1
1
  <script lang="ts">
2
- import { getAppStore } from "rune-lab/state";
2
+ import { getAppStore } from "@internal/state";
3
3
  import { DEV } from "esm-env";
4
4
 
5
5
  /**
@@ -12,15 +12,17 @@
12
12
  createShortcutStore,
13
13
  createCartStore,
14
14
  createSessionStore,
15
+ createExchangeRateStore,
15
16
  type CartStoreConfig,
16
- } from "rune-lab/state";
17
- import type { PersistenceDriver } from "rune-lab/core";
18
- import { localStorageDriver } from "rune-lab/state";
19
- import { RUNE_LAB_CONTEXT } from "rune-lab/state";
20
- import type { AppData } from "rune-lab/state";
21
- import type { Theme } from "rune-lab/state";
22
- import type { Currency } from "rune-lab/state";
23
- import { CommandPalette, ShortcutPalette, Toaster } from "rune-lab/ui";
17
+ type ExchangeRateStore,
18
+ } from "@internal/state";
19
+ import type { PersistenceDriver } from "@internal/core";
20
+ import { localStorageDriver } from "@internal/state";
21
+ import { RUNE_LAB_CONTEXT } from "@internal/state";
22
+ import type { AppData } from "@internal/state";
23
+ import type { Theme } from "@internal/state";
24
+ import type { Currency } from "@internal/state";
25
+ import { CommandPalette, ShortcutPalette, Toaster } from "../../mod";
24
26
 
25
27
  export interface RuneLabConfig {
26
28
  persistence?: PersistenceDriver;
@@ -46,6 +48,13 @@
46
48
  currencies?: Currency[];
47
49
  /** Default currency code when no persisted value exists */
48
50
  defaultCurrency?: string;
51
+ /** Bootstrap-time exchange rates */
52
+ exchangeRates?: {
53
+ base: string;
54
+ rates: Record<string, number>;
55
+ };
56
+ /** Callback when rates are initialized/updated */
57
+ onRatesUpdate?: (store: ExchangeRateStore) => void;
49
58
 
50
59
  // Cart (opt-in)
51
60
  /** CartStore configuration — when provided, a CartStore is created and registered in context */
@@ -80,9 +89,15 @@
80
89
  const initialDefaultTheme = untrack(() => config.defaultTheme);
81
90
  const initialCustomCurrencies = untrack(() => config.currencies);
82
91
  const initialDefaultCurrency = untrack(() => config.defaultCurrency);
92
+ const initialExchangeRates = untrack(() => config.exchangeRates);
83
93
  const initialCartConfig = untrack(() => config.cart);
84
94
  const initialAuthConfig = untrack(() => config.auth);
85
95
 
96
+ const exchangeRateStore = createExchangeRateStore();
97
+ if (initialExchangeRates) {
98
+ exchangeRateStore.setRates(initialExchangeRates.base, initialExchangeRates.rates);
99
+ }
100
+
86
101
  const themeStore = createThemeStore({
87
102
  driver: initialPersistence,
88
103
  customThemes: initialCustomThemes,
@@ -96,6 +111,7 @@
96
111
  driver: initialPersistence,
97
112
  customCurrencies: initialCustomCurrencies,
98
113
  defaultCurrency: initialDefaultCurrency,
114
+ exchangeRateStore,
99
115
  });
100
116
  const shortcutStore = createShortcutStore();
101
117
 
@@ -117,6 +133,7 @@
117
133
  setContext(RUNE_LAB_CONTEXT.theme, themeStore);
118
134
  setContext(RUNE_LAB_CONTEXT.language, languageStore);
119
135
  setContext(RUNE_LAB_CONTEXT.currency, currencyStore);
136
+ setContext(RUNE_LAB_CONTEXT.exchangeRate, exchangeRateStore);
120
137
  setContext(RUNE_LAB_CONTEXT.shortcut, shortcutStore);
121
138
  setContext(RUNE_LAB_CONTEXT.layout, layoutStore);
122
139
  setContext(RUNE_LAB_CONTEXT.commands, commandStore);
@@ -165,6 +182,9 @@
165
182
 
166
183
  onMount(() => {
167
184
  layoutStore.init();
185
+ if (config.onRatesUpdate) {
186
+ config.onRatesUpdate(exchangeRateStore);
187
+ }
168
188
  });
169
189
 
170
190
  // Meta tags derived from app store state
@@ -1,9 +1,9 @@
1
1
  import { type Snippet } from "svelte";
2
- import { type CartStoreConfig } from "rune-lab/state";
3
- import type { PersistenceDriver } from "rune-lab/core";
4
- import type { AppData } from "rune-lab/state";
5
- import type { Theme } from "rune-lab/state";
6
- import type { Currency } from "rune-lab/state";
2
+ import { type CartStoreConfig, type ExchangeRateStore } from "@internal/state";
3
+ import type { PersistenceDriver } from "@internal/core";
4
+ import type { AppData } from "@internal/state";
5
+ import type { Theme } from "@internal/state";
6
+ import type { Currency } from "@internal/state";
7
7
  export interface RuneLabConfig {
8
8
  persistence?: PersistenceDriver;
9
9
  app?: Partial<AppData>;
@@ -24,6 +24,13 @@ export interface RuneLabConfig {
24
24
  currencies?: Currency[];
25
25
  /** Default currency code when no persisted value exists */
26
26
  defaultCurrency?: string;
27
+ /** Bootstrap-time exchange rates */
28
+ exchangeRates?: {
29
+ base: string;
30
+ rates: Record<string, number>;
31
+ };
32
+ /** Callback when rates are initialized/updated */
33
+ onRatesUpdate?: (store: ExchangeRateStore) => void;
27
34
  /** CartStore configuration — when provided, a CartStore is created and registered in context */
28
35
  cart?: CartStoreConfig<any>;
29
36
  /** When true, creates and registers a SessionStore in context */
@@ -1,5 +1,5 @@
1
1
  <script lang="ts">
2
- import { getToastStore } from "rune-lab/state";
2
+ import { getToastStore } from "@internal/state";
3
3
  import { portal } from "../actions/portal";
4
4
  import { flip } from "svelte/animate";
5
5
  import { fade, fly } from "svelte/transition";