rune-lab 0.4.2-beta.6 → 0.4.2-beta.8

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 (172) hide show
  1. package/.gitignore +12 -0
  2. package/deno.json +47 -0
  3. package/justfile +5 -0
  4. package/package.json +3 -31
  5. package/scripts/ci.just +25 -0
  6. package/scripts/deploy.just +20 -0
  7. package/scripts/dev.just +4 -0
  8. package/scripts/inject.just +72 -0
  9. package/src/app.d.ts +13 -0
  10. package/src/app.html +12 -0
  11. package/src/{i18n/message-resolver.test.js → lib/i18n/message-resolver.test.ts} +23 -3
  12. package/src/{i18n/message-resolver.d.ts → lib/i18n/message-resolver.ts} +47 -9
  13. package/src/{kernel/src/actions/portal.js → lib/kernel/src/actions/portal.ts} +11 -3
  14. package/src/{kernel/src/actions/shortcut-listener.js → lib/kernel/src/actions/shortcut-listener.ts} +20 -5
  15. package/src/lib/kernel/src/context/app.svelte.ts +98 -0
  16. package/src/{kernel/src/context/context.js → lib/kernel/src/context/context.ts} +17 -2
  17. package/src/lib/kernel/src/context/stores.svelte.ts +96 -0
  18. package/src/{kernel/src/context/types.d.ts → lib/kernel/src/context/types.ts} +12 -0
  19. package/src/{kernel/src/context/useRuneLab.d.ts → lib/kernel/src/context/useRuneLab.ts} +14 -1
  20. package/src/lib/kernel/src/persistence/createConfigStore.svelte.ts +120 -0
  21. package/src/{kernel/src/persistence/drivers.test.js → lib/kernel/src/persistence/drivers.test.ts} +17 -1
  22. package/src/lib/kernel/src/persistence/drivers.ts +77 -0
  23. package/src/{kernel/src/persistence/provider.test.js → lib/kernel/src/persistence/provider.test.ts} +9 -2
  24. package/src/{kernel/src/persistence/provider.js → lib/kernel/src/persistence/provider.ts} +17 -4
  25. package/src/{kernel/src/persistence/types.d.ts → lib/kernel/src/persistence/types.ts} +2 -0
  26. package/src/lib/kernel/src/persistence/usePersistence.ts +9 -0
  27. package/src/{kernel/src/registry/mod.d.ts → lib/kernel/src/registry/mod.ts} +116 -14
  28. package/src/{kernel/src/registry/registry.test.js → lib/kernel/src/registry/registry.test.ts} +58 -17
  29. package/src/{kernel/src/tokens/props.d.ts → lib/kernel/src/tokens/props.ts} +32 -5
  30. package/src/{mod.js → lib/mod.ts} +2 -0
  31. package/src/{runes/layout/src/APP_CONFIGURATIONS.d.ts → lib/runes/layout/src/APP_CONFIGURATIONS.ts} +28 -1
  32. package/src/{runes/layout/src/connection-factory.d.ts → lib/runes/layout/src/connection-factory.ts} +41 -7
  33. package/src/{runes/layout/src/language.svelte.js → lib/runes/layout/src/language.svelte.ts} +23 -9
  34. package/src/{runes/layout/src/mod.js → lib/runes/layout/src/mod.ts} +18 -14
  35. package/src/{runes/layout/src/store.svelte.js → lib/runes/layout/src/store.svelte.ts} +57 -32
  36. package/src/{runes/layout/src/theme.svelte.js → lib/runes/layout/src/theme.svelte.ts} +15 -5
  37. package/src/{runes/layout/src/types.js → lib/runes/layout/src/types.ts} +3 -0
  38. package/src/lib/runes/palettes/src/commands/store.svelte.ts +80 -0
  39. package/src/{runes/palettes/src/mod.js → lib/runes/palettes/src/mod.ts} +9 -5
  40. package/src/{runes/palettes/src/notifications/bridge.js → lib/runes/palettes/src/notifications/bridge.ts} +22 -10
  41. package/src/{runes/palettes/src/notifications/store.svelte.js → lib/runes/palettes/src/notifications/store.svelte.ts} +24 -9
  42. package/src/{runes/palettes/src/shortcuts/store.svelte.js → lib/runes/palettes/src/shortcuts/store.svelte.ts} +34 -35
  43. package/src/{runes/palettes/src/shortcuts/types.js → lib/runes/palettes/src/shortcuts/types.ts} +2 -1
  44. package/src/{runes/palettes/src/shortcuts/useShortcuts.js → lib/runes/palettes/src/shortcuts/useShortcuts.ts} +16 -7
  45. package/src/{runes/plugins/money/src/currency.svelte.js → lib/runes/plugins/money/src/currency.svelte.ts} +42 -19
  46. package/src/{runes/plugins/money/src/currency.test.js → lib/runes/plugins/money/src/currency.test.ts} +10 -2
  47. package/src/{runes/plugins/money/src/exchange-rate.svelte.js → lib/runes/plugins/money/src/exchange-rate.svelte.ts} +49 -43
  48. package/src/{runes/plugins/money/src/exchange-rate.test.js → lib/runes/plugins/money/src/exchange-rate.test.ts} +23 -2
  49. package/src/{runes/plugins/money/src/mod.js → lib/runes/plugins/money/src/mod.ts} +34 -10
  50. package/src/{runes/plugins/money/src/money-primitive.test.js → lib/runes/plugins/money/src/money-primitive.test.ts} +40 -0
  51. package/src/{runes/plugins/money/src/money-primitive.js → lib/runes/plugins/money/src/money-primitive.ts} +68 -26
  52. package/src/{runes/plugins/money/src/money.test.js → lib/runes/plugins/money/src/money.test.ts} +29 -0
  53. package/src/{runes/plugins/money/src/money.js → lib/runes/plugins/money/src/money.ts} +129 -32
  54. package/src/{runes/plugins/money/src/strategies.test.js → lib/runes/plugins/money/src/strategies.test.ts} +30 -2
  55. package/src/{runes/plugins/money/src/strategies.js → lib/runes/plugins/money/src/strategies.ts} +27 -12
  56. package/src/{runes/plugins/money/src/useMoney.js → lib/runes/plugins/money/src/useMoney.ts} +131 -12
  57. package/src/{runes/plugins/money/src/useMoneyFilter.js → lib/runes/plugins/money/src/useMoneyFilter.ts} +40 -5
  58. package/src/routes/+page.svelte +3 -0
  59. package/tsconfig.json +41 -0
  60. package/vite.config.ts +30 -0
  61. package/src/RuneProvider.svelte.d.ts +0 -32
  62. package/src/i18n/message-resolver.js +0 -49
  63. package/src/kernel/src/actions/portal.d.ts +0 -10
  64. package/src/kernel/src/actions/shortcut-listener.d.ts +0 -18
  65. package/src/kernel/src/context/app.svelte.d.ts +0 -45
  66. package/src/kernel/src/context/app.svelte.js +0 -89
  67. package/src/kernel/src/context/context.d.ts +0 -15
  68. package/src/kernel/src/context/stores.svelte.d.ts +0 -67
  69. package/src/kernel/src/context/stores.svelte.js +0 -49
  70. package/src/kernel/src/context/types.js +0 -1
  71. package/src/kernel/src/context/useRuneLab.js +0 -26
  72. package/src/kernel/src/mod.js +0 -13
  73. package/src/kernel/src/persistence/createConfigStore.svelte.d.ts +0 -30
  74. package/src/kernel/src/persistence/createConfigStore.svelte.js +0 -76
  75. package/src/kernel/src/persistence/drivers.d.ts +0 -12
  76. package/src/kernel/src/persistence/drivers.js +0 -87
  77. package/src/kernel/src/persistence/provider.d.ts +0 -30
  78. package/src/kernel/src/persistence/types.js +0 -2
  79. package/src/kernel/src/persistence/usePersistence.d.ts +0 -2
  80. package/src/kernel/src/persistence/usePersistence.js +0 -5
  81. package/src/kernel/src/registry/mod.js +0 -140
  82. package/src/kernel/src/tokens/props.js +0 -35
  83. package/src/mod.d.ts +0 -5
  84. package/src/runes/layout/src/APP_CONFIGURATIONS.js +0 -38
  85. package/src/runes/layout/src/AppSettingSelector.svelte.d.ts +0 -18
  86. package/src/runes/layout/src/ConnectedNavigationPanel.svelte.d.ts +0 -18
  87. package/src/runes/layout/src/ConnectedWorkspaceStrip.svelte.d.ts +0 -18
  88. package/src/runes/layout/src/ContentArea.svelte.d.ts +0 -18
  89. package/src/runes/layout/src/DetailPanel.svelte.d.ts +0 -18
  90. package/src/runes/layout/src/Icon.svelte.d.ts +0 -15
  91. package/src/runes/layout/src/LanguageSelector.svelte.d.ts +0 -18
  92. package/src/runes/layout/src/NavigationPanel.svelte.d.ts +0 -18
  93. package/src/runes/layout/src/ResourceSelector.svelte.d.ts +0 -21
  94. package/src/runes/layout/src/ThemeSelector.svelte.d.ts +0 -18
  95. package/src/runes/layout/src/WorkspaceLayout.svelte.d.ts +0 -47
  96. package/src/runes/layout/src/WorkspaceStrip.svelte.d.ts +0 -18
  97. package/src/runes/layout/src/connection-factory.js +0 -58
  98. package/src/runes/layout/src/language.svelte.d.ts +0 -50
  99. package/src/runes/layout/src/mod.d.ts +0 -22
  100. package/src/runes/layout/src/store.svelte.d.ts +0 -37
  101. package/src/runes/layout/src/theme.svelte.d.ts +0 -6
  102. package/src/runes/layout/src/types.d.ts +0 -6
  103. package/src/runes/palettes/src/commands/CommandPalette.svelte.d.ts +0 -18
  104. package/src/runes/palettes/src/commands/mod.js +0 -2
  105. package/src/runes/palettes/src/commands/store.svelte.d.ts +0 -33
  106. package/src/runes/palettes/src/commands/store.svelte.js +0 -63
  107. package/src/runes/palettes/src/mod.d.ts +0 -9
  108. package/src/runes/palettes/src/notifications/NotificationBell.svelte.d.ts +0 -26
  109. package/src/runes/palettes/src/notifications/Toaster.svelte.d.ts +0 -15
  110. package/src/runes/palettes/src/notifications/bridge.d.ts +0 -33
  111. package/src/runes/palettes/src/notifications/mod.js +0 -4
  112. package/src/runes/palettes/src/notifications/store.svelte.d.ts +0 -23
  113. package/src/runes/palettes/src/shortcuts/ShortcutPalette.svelte.d.ts +0 -23
  114. package/src/runes/palettes/src/shortcuts/mod.js +0 -4
  115. package/src/runes/palettes/src/shortcuts/store.svelte.d.ts +0 -37
  116. package/src/runes/palettes/src/shortcuts/types.d.ts +0 -1
  117. package/src/runes/palettes/src/shortcuts/useShortcuts.d.ts +0 -32
  118. package/src/runes/plugins/money/src/CurrencySelector.svelte.d.ts +0 -18
  119. package/src/runes/plugins/money/src/MoneyDisplay.svelte.d.ts +0 -18
  120. package/src/runes/plugins/money/src/MoneyInput.svelte.d.ts +0 -49
  121. package/src/runes/plugins/money/src/currency.svelte.d.ts +0 -29
  122. package/src/runes/plugins/money/src/exchange-rate.svelte.d.ts +0 -44
  123. package/src/runes/plugins/money/src/mod.d.ts +0 -13
  124. package/src/runes/plugins/money/src/money-primitive.d.ts +0 -106
  125. package/src/runes/plugins/money/src/money.d.ts +0 -170
  126. package/src/runes/plugins/money/src/strategies.d.ts +0 -55
  127. package/src/runes/plugins/money/src/types.js +0 -21
  128. package/src/runes/plugins/money/src/useMoney.d.ts +0 -69
  129. package/src/runes/plugins/money/src/useMoneyFilter.d.ts +0 -23
  130. /package/src/{RuneProvider.svelte → lib/RuneProvider.svelte} +0 -0
  131. /package/src/{i18n → lib/i18n}/project.inlang/settings.json +0 -0
  132. /package/src/{i18n → lib/i18n}/translations/ar.json +0 -0
  133. /package/src/{i18n → lib/i18n}/translations/de.json +0 -0
  134. /package/src/{i18n → lib/i18n}/translations/en.json +0 -0
  135. /package/src/{i18n → lib/i18n}/translations/es.json +0 -0
  136. /package/src/{i18n → lib/i18n}/translations/fr.json +0 -0
  137. /package/src/{i18n → lib/i18n}/translations/hi.json +0 -0
  138. /package/src/{i18n → lib/i18n}/translations/it.json +0 -0
  139. /package/src/{i18n → lib/i18n}/translations/ja.json +0 -0
  140. /package/src/{i18n → lib/i18n}/translations/ko.json +0 -0
  141. /package/src/{i18n → lib/i18n}/translations/pt.json +0 -0
  142. /package/src/{i18n → lib/i18n}/translations/ru.json +0 -0
  143. /package/src/{i18n → lib/i18n}/translations/vi.json +0 -0
  144. /package/src/{i18n → lib/i18n}/translations/zh.json +0 -0
  145. /package/src/{kernel → lib/kernel}/deno.json +0 -0
  146. /package/src/{kernel/src/mod.d.ts → lib/kernel/src/mod.ts} +0 -0
  147. /package/src/{runes → lib/runes}/layout/deno.json +0 -0
  148. /package/src/{runes → lib/runes}/layout/src/AppSettingSelector.svelte +0 -0
  149. /package/src/{runes → lib/runes}/layout/src/ConnectedNavigationPanel.svelte +0 -0
  150. /package/src/{runes → lib/runes}/layout/src/ConnectedWorkspaceStrip.svelte +0 -0
  151. /package/src/{runes → lib/runes}/layout/src/ContentArea.svelte +0 -0
  152. /package/src/{runes → lib/runes}/layout/src/DetailPanel.svelte +0 -0
  153. /package/src/{runes → lib/runes}/layout/src/Icon.svelte +0 -0
  154. /package/src/{runes → lib/runes}/layout/src/LanguageSelector.svelte +0 -0
  155. /package/src/{runes → lib/runes}/layout/src/NavigationPanel.svelte +0 -0
  156. /package/src/{runes → lib/runes}/layout/src/ResourceSelector.svelte +0 -0
  157. /package/src/{runes → lib/runes}/layout/src/ThemeSelector.svelte +0 -0
  158. /package/src/{runes → lib/runes}/layout/src/WorkspaceLayout.svelte +0 -0
  159. /package/src/{runes → lib/runes}/layout/src/WorkspaceStrip.svelte +0 -0
  160. /package/src/{runes → lib/runes}/palettes/deno.json +0 -0
  161. /package/src/{runes → lib/runes}/palettes/src/commands/CommandPalette.svelte +0 -0
  162. /package/src/{runes/palettes/src/commands/mod.d.ts → lib/runes/palettes/src/commands/mod.ts} +0 -0
  163. /package/src/{runes → lib/runes}/palettes/src/notifications/NotificationBell.svelte +0 -0
  164. /package/src/{runes → lib/runes}/palettes/src/notifications/Toaster.svelte +0 -0
  165. /package/src/{runes/palettes/src/notifications/mod.d.ts → lib/runes/palettes/src/notifications/mod.ts} +0 -0
  166. /package/src/{runes → lib/runes}/palettes/src/shortcuts/ShortcutPalette.svelte +0 -0
  167. /package/src/{runes/palettes/src/shortcuts/mod.d.ts → lib/runes/palettes/src/shortcuts/mod.ts} +0 -0
  168. /package/src/{runes → lib/runes}/plugins/money/deno.json +0 -0
  169. /package/src/{runes → lib/runes}/plugins/money/src/CurrencySelector.svelte +0 -0
  170. /package/src/{runes → lib/runes}/plugins/money/src/MoneyDisplay.svelte +0 -0
  171. /package/src/{runes → lib/runes}/plugins/money/src/MoneyInput.svelte +0 -0
  172. /package/src/{runes/plugins/money/src/types.d.ts → lib/runes/plugins/money/src/types.ts} +0 -0
package/.gitignore ADDED
@@ -0,0 +1,12 @@
1
+ # all that start with a dot except .gitignore
2
+ .*
3
+ !.gitignore
4
+
5
+ # lock files (from various package managers)
6
+ *.lock
7
+
8
+ # web-generated files
9
+ node_modules
10
+ dist
11
+ lab
12
+ build
package/deno.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "@yrrrrrf/rune-lab",
3
+ "version": "0.4.2-beta.8",
4
+ "description": "Modern toolkit for Svelte 5 Runes applications.",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "readme": "README.md",
8
+ "author": "Fernando Bryan Reza Campos",
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/Yrrrrrf/rune-lab.git"
12
+ },
13
+ "exports": {
14
+ ".": "./src/lib/mod.ts"
15
+ },
16
+ "nodeModulesDir": "manual",
17
+ "workspace": [
18
+ "./src/lib/kernel",
19
+ "./src/lib/runes/layout",
20
+ "./src/lib/runes/palettes",
21
+ "./src/lib/runes/plugins/money"
22
+ ],
23
+ "imports": {
24
+ "@rune-lab/core": "./src/lib/mod.ts",
25
+ "@rune-lab/i18n/": "./src/lib/i18n/",
26
+ "typescript": "npm:typescript@5.9.3",
27
+ "@tailwindcss/vite": "npm:@tailwindcss/vite",
28
+ "esm-env": "npm:esm-env",
29
+ "vite": "npm:vite",
30
+ "vite-plus": "npm:vite-plus",
31
+ "daisyui/": "npm:daisyui/",
32
+ "hotkeys-js": "npm:hotkeys-js",
33
+ "@inlang/paraglide-js": "npm:@inlang/paraglide-js",
34
+ "svelte": "npm:svelte",
35
+ "@sveltejs/kit": "npm:@sveltejs/kit",
36
+ "@sveltejs/vite-plugin-svelte": "npm:@sveltejs/vite-plugin-svelte",
37
+ "@sveltejs/adapter-static": "npm:@sveltejs/adapter-static",
38
+ "jsdom": "npm:jsdom",
39
+ "@testing-library/jest-dom": "npm:@testing-library/jest-dom"
40
+ },
41
+ "exclude": [
42
+ ".*",
43
+ "scripts/**",
44
+ "dist/**",
45
+ "**/*.test.ts"
46
+ ]
47
+ }
package/justfile ADDED
@@ -0,0 +1,5 @@
1
+ set shell := ["bash", "-cu"]
2
+
3
+ import 'scripts/dev.just'
4
+ import 'scripts/ci.just'
5
+ import 'scripts/deploy.just'
package/package.json CHANGED
@@ -1,33 +1,5 @@
1
1
  {
2
2
  "name": "rune-lab",
3
- "readme": "README.md",
4
- "version": "0.4.2-beta.6",
5
- "description": "Modern toolkit for Svelte 5 Runes applications.",
6
- "license": "MIT",
7
- "repository": {
8
- "type": "git",
9
- "url": "git+https://github.com/Yrrrrrf/rune-lab.git"
10
- },
11
- "type": "module",
12
- "exports": {
13
- ".": {
14
- "types": "./src/mod.d.ts",
15
- "default": "./src/mod.js"
16
- }
17
- },
18
- "peerDependencies": {
19
- "svelte": "^5.55.0",
20
- "@sveltejs/kit": "^2.55.0",
21
- "@inlang/paraglide-js": "^2.15.1",
22
- "hotkeys-js": "^4.0.2",
23
- "dinero.js": "^2.0.2",
24
- "esm-env": "^1.2.2"
25
- },
26
- "keywords": [
27
- "svelte",
28
- "svelte-5",
29
- "runes",
30
- "ui",
31
- "components"
32
- ]
33
- }
3
+ "version": "0.4.2-beta.8",
4
+ "packageManager": "pnpm@10.33.0"
5
+ }
@@ -0,0 +1,25 @@
1
+ [doc('Format Client code')]
2
+ [group('CI')]
3
+ fmt:
4
+ deno fmt .
5
+ @echo "✓ Client code formatted"
6
+
7
+ [doc('Lint Client code')]
8
+ [group('CI')]
9
+ lint:
10
+ deno lint .
11
+ @echo "✓ Client code linted"
12
+
13
+ [doc('Type-check Client workspace')]
14
+ [group('CI')]
15
+ typecheck:
16
+ # check ts types with strict deno.ns
17
+ deno check .
18
+ # check svelte types
19
+ deno run -A npm:svelte-check
20
+ @echo "✓ Client type-checked"
21
+
22
+ [doc('Full client quality gate')]
23
+ [group('CI')]
24
+ quality: fmt lint typecheck
25
+ @echo "✓ Client quality checks passed"
@@ -0,0 +1,20 @@
1
+ [doc('Build Client')]
2
+ [group('Build')]
3
+ build:
4
+ # * Clean up previous build
5
+ rm -rf dist/
6
+ deno run -A npm:@inlang/paraglide-js compile --project ./src/lib/i18n/project.inlang --outdir ./src/lib/i18n/paraglide
7
+ deno run -A npm:@sveltejs/package -- -i src -o dist
8
+ # * Build ESM package
9
+ # deno run -A scripts/build_pkg.ts
10
+ @echo "✓ build done"
11
+
12
+ [doc('Publish to jsr')]
13
+ [group('Deploy')]
14
+ deploy: build
15
+ # * Format build output
16
+ deno fmt --no-config dist
17
+ # * Publish to npm
18
+ (cd dist && bun publish)
19
+ # * Publish to jsr
20
+ @echo "✓ deploy done"
@@ -0,0 +1,4 @@
1
+ [doc('Test the client (runs once)')]
2
+ [group('Dev')]
3
+ test *args:
4
+ vp test
@@ -0,0 +1,72 @@
1
+ # inject.just — surgical local rune-lab injection
2
+ #
3
+ # ⚠ IMPORTANT: `just -f` changes cwd to the justfile's directory.
4
+ # Never pass `.` as dest — pass $PWD or an absolute path instead.
5
+ #
6
+ # Usage:
7
+ # just -f ~/rune-lab/scripts/inject.just inject $PWD
8
+ # just -f ~/rune-lab/scripts/inject.just inject /absolute/path/to/project
9
+ # just -f ~/rune-lab/scripts/inject.just inject-clean $PWD
10
+ #``
11
+ # Recipes:
12
+ # inject — build rune-lab, then hot-swap node_modules/rune-lab
13
+ # inject-clean — full dep reset (yog reborn --deep), then inject
14
+ # ─────────────────────────────────────────────────────────────────────────────
15
+
16
+ # Hot-swap: rebuild rune-lab and inject into <dest>/node_modules/rune-lab
17
+ inject dest: _build-for-inject
18
+ #!/usr/bin/env bash
19
+ set -euo pipefail
20
+
21
+ dest_abs="{{ dest }}"
22
+
23
+ # Reject relative paths — they resolve to rune-lab's own dir, not the caller's
24
+ if [[ "$dest_abs" != /* ]]; then
25
+ echo "❌ dest must be an absolute path."
26
+ echo " Call with: just -f ... inject \$PWD"
27
+ echo " Got: $dest_abs"
28
+ exit 1
29
+ fi
30
+
31
+ if [ ! -d "$dest_abs/node_modules" ]; then
32
+ echo "❌ No node_modules/ found in $dest_abs"
33
+ echo " Run your package manager's install first, then retry."
34
+ exit 1
35
+ fi
36
+
37
+ echo "🗑 Removing stale $dest_abs/node_modules/rune-lab..."
38
+ rm -rf "$dest_abs/node_modules/rune-lab"
39
+
40
+ echo "📦 Copying local dist into $dest_abs/node_modules/rune-lab..."
41
+ mkdir -p "$dest_abs/node_modules/rune-lab"
42
+ cp -r "{{ justfile_directory() }}/../dist" "$dest_abs/node_modules/rune-lab/dist"
43
+ cp "{{ justfile_directory() }}/../package.json" "$dest_abs/node_modules/rune-lab/package.json"
44
+
45
+ echo "✅ Done! node_modules/rune-lab → $(du -sh $dest_abs/node_modules/rune-lab | cut -f1)"
46
+ echo " Source: {{ justfile_directory() }}/../dist"
47
+ echo " Target: $dest_abs/node_modules/rune-lab"
48
+
49
+ # Full reset: nuke deps in <dest>, reinstall, then hot-swap rune-lab
50
+ inject-clean dest: _build-for-inject
51
+ #!/usr/bin/env bash
52
+ set -euo pipefail
53
+
54
+ dest_abs="{{ dest }}"
55
+
56
+ if [[ "$dest_abs" != /* ]]; then
57
+ echo "❌ dest must be an absolute path. Call with: just -f ... inject-clean \$PWD"
58
+ exit 1
59
+ fi
60
+
61
+ echo "💀 Running yog reborn --deep in $dest_abs..."
62
+ (cd "$dest_abs" && yog reborn --deep)
63
+
64
+ just -f "{{ justfile_directory() }}/inject.just" inject "$dest_abs"
65
+
66
+ # ── Internal ──────────────────────────────────────────────────────────────────
67
+
68
+ [private]
69
+ _build-for-inject:
70
+ @echo "🔨 Building rune-lab from {{ justfile_directory() }}/.."
71
+ just --justfile "{{ justfile_directory() }}/../justfile" --working-directory "{{ justfile_directory() }}/.." build
72
+ @echo "✅ rune-lab built."
package/src/app.d.ts ADDED
@@ -0,0 +1,13 @@
1
+ // See https://svelte.dev/docs/kit/types#app.d.ts
2
+ // for information about these interfaces
3
+ declare global {
4
+ namespace App {
5
+ // interface Error {}
6
+ // interface Locals {}
7
+ // interface PageData {}
8
+ // interface PageState {}
9
+ // interface Platform {}
10
+ }
11
+ }
12
+
13
+ export {};
package/src/app.html ADDED
@@ -0,0 +1,12 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <link rel="icon" href="%sveltekit.assets%/favicon.svg" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
7
+ %sveltekit.head%
8
+ </head>
9
+ <body data-sveltekit-preload-data="hover">
10
+ <div style="display: contents">%sveltekit.body%</div>
11
+ </body>
12
+ </html>
@@ -1,11 +1,14 @@
1
1
  import { describe, expect, it, vi } from "vite-plus/test";
2
+
2
3
  // Mock esm-env before importing the module
3
4
  vi.mock("esm-env", () => ({ DEV: true }));
5
+
4
6
  import {
5
7
  batchResolveMessages,
6
8
  createMessageResolver,
7
9
  hasMessage,
8
10
  } from "./message-resolver.ts";
11
+
9
12
  describe("MessageResolver", () => {
10
13
  const mockMessages = {
11
14
  usd: () => "US Dollar",
@@ -13,70 +16,87 @@ describe("MessageResolver", () => {
13
16
  mxn: () => "Mexican Peso",
14
17
  jpy: () => "Japanese Yen",
15
18
  };
19
+
16
20
  describe("createMessageResolver", () => {
17
21
  it("should resolve a key via keyExtractor", () => {
18
- const resolver = createMessageResolver(mockMessages, {
22
+ const resolver = createMessageResolver<{ code: string }>(mockMessages, {
19
23
  keyExtractor: (opt) => opt.code,
20
24
  keyTransformer: (key) => key.toLowerCase(),
21
25
  });
26
+
22
27
  expect(resolver({ code: "USD" })).toBe("US Dollar");
23
28
  expect(resolver({ code: "EUR" })).toBe("Euro");
24
29
  });
30
+
25
31
  it("should use untransformed key when no transformer provided", () => {
26
- const resolver = createMessageResolver(mockMessages, {
32
+ const resolver = createMessageResolver<{ code: string }>(mockMessages, {
27
33
  keyExtractor: (opt) => opt.code,
28
34
  });
35
+
29
36
  // Without transformer, keys must match exactly
30
37
  expect(resolver({ code: "usd" })).toBe("US Dollar");
31
38
  });
39
+
32
40
  it("should fall back to the raw key for missing translations", () => {
33
41
  const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
34
- const resolver = createMessageResolver(mockMessages, {
42
+
43
+ const resolver = createMessageResolver<{ code: string }>(mockMessages, {
35
44
  keyExtractor: (opt) => opt.code,
36
45
  keyTransformer: (key) => key.toLowerCase(),
37
46
  });
47
+
38
48
  const result = resolver({ code: "UNKNOWN" });
39
49
  expect(result).toBe("UNKNOWN"); // falls back to original key
40
50
  expect(warnSpy).toHaveBeenCalledWith(
41
51
  expect.stringContaining("Missing translation"),
42
52
  );
53
+
43
54
  warnSpy.mockRestore();
44
55
  });
45
56
  });
57
+
46
58
  describe("hasMessage", () => {
47
59
  it("should return true for existing keys", () => {
48
60
  expect(hasMessage(mockMessages, "usd")).toBe(true);
49
61
  expect(hasMessage(mockMessages, "eur")).toBe(true);
50
62
  });
63
+
51
64
  it("should return false for missing keys", () => {
52
65
  expect(hasMessage(mockMessages, "UNKNOWN")).toBe(false);
53
66
  expect(hasMessage(mockMessages, "")).toBe(false);
54
67
  });
55
68
  });
69
+
56
70
  describe("batchResolveMessages", () => {
57
71
  it("should resolve all options at once", () => {
58
72
  const options = [{ code: "USD" }, { code: "EUR" }, { code: "MXN" }];
73
+
59
74
  const result = batchResolveMessages(mockMessages, options, {
60
75
  keyExtractor: (opt) => opt.code,
61
76
  keyTransformer: (key) => key.toLowerCase(),
62
77
  });
78
+
63
79
  expect(result).toEqual({
64
80
  USD: "US Dollar",
65
81
  EUR: "Euro",
66
82
  MXN: "Mexican Peso",
67
83
  });
68
84
  });
85
+
69
86
  it("should include fallback values for missing keys", () => {
70
87
  vi.spyOn(console, "warn").mockImplementation(() => {});
88
+
71
89
  const options = [{ code: "USD" }, { code: "XYZ" }];
72
90
  const result = batchResolveMessages(mockMessages, options, {
73
91
  keyExtractor: (opt) => opt.code,
74
92
  keyTransformer: (key) => key.toLowerCase(),
75
93
  });
94
+
76
95
  expect(result).toEqual({
77
96
  USD: "US Dollar",
78
97
  XYZ: "XYZ", // fallback
79
98
  });
99
+
80
100
  vi.restoreAllMocks();
81
101
  });
82
102
  });
@@ -1,22 +1,29 @@
1
+ // sdk/devtools/src/patterns/message-resolver.ts
2
+ import { DEV } from "esm-env";
3
+
1
4
  /**
2
5
  * Dynamic i18n Message Resolver Pattern
3
6
  *
4
7
  * Enables dynamic message function calls for config selectors.
5
8
  * Creates a resolver that dynamically looks up and calls message functions.
6
9
  */
10
+
7
11
  type MessageBundle = Record<string, (...args: unknown[]) => string>;
12
+
8
13
  interface MessageResolverConfig<T> {
9
14
  /**
10
15
  * Function to extract the message key from an option
11
16
  * @example (currency) => currency.code // "USD"
12
17
  */
13
18
  keyExtractor: (option: T) => string;
19
+
14
20
  /**
15
21
  * Optional transformer for the key before message lookup
16
22
  * @example (key) => key.toLowerCase() // "USD" -> "usd"
17
23
  */
18
24
  keyTransformer?: (key: string) => string;
19
25
  }
26
+
20
27
  /**
21
28
  * Creates a message resolver function for dynamic i18n lookups
22
29
  *
@@ -29,23 +36,54 @@ interface MessageResolverConfig<T> {
29
36
  * getCurrencyLabel({ code: "EUR", symbol: "€" }); // "Euro"
30
37
  * ```
31
38
  */
32
- export declare function createMessageResolver<T>(
39
+ export function createMessageResolver<T>(
33
40
  messages: MessageBundle,
34
41
  config: MessageResolverConfig<T>,
35
- ): (option: T) => string;
42
+ ) {
43
+ return (option: T): string => {
44
+ const key = config.keyExtractor(option);
45
+ const transformedKey = config.keyTransformer
46
+ ? config.keyTransformer(key)
47
+ : key;
48
+
49
+ const messageFn = messages[transformedKey];
50
+
51
+ if (!messageFn || typeof messageFn !== "function") {
52
+ if (DEV) {
53
+ console.warn(
54
+ `[MessageResolver] Missing translation for key: "${transformedKey}"`,
55
+ );
56
+ }
57
+ return key; // Fallback to key
58
+ }
59
+
60
+ return messageFn();
61
+ };
62
+ }
63
+
36
64
  /**
37
65
  * Type guard to check if a message key exists
38
66
  */
39
- export declare function hasMessage(
40
- messages: MessageBundle,
41
- key: string,
42
- ): boolean;
67
+ export function hasMessage(messages: MessageBundle, key: string): boolean {
68
+ return key in messages && typeof messages[key] === "function";
69
+ }
70
+
43
71
  /**
44
72
  * Batch resolver for multiple options at once
45
73
  */
46
- export declare function batchResolveMessages<T>(
74
+ export function batchResolveMessages<T>(
47
75
  messages: MessageBundle,
48
76
  options: T[],
49
77
  config: MessageResolverConfig<T>,
50
- ): Record<string, string>;
51
- export {};
78
+ ): Record<string, string> {
79
+ const resolver = createMessageResolver(messages, config);
80
+
81
+ return options.reduce(
82
+ (acc, option) => {
83
+ const key = config.keyExtractor(option);
84
+ acc[key] = resolver(option);
85
+ return acc;
86
+ },
87
+ {} as Record<string, string>,
88
+ );
89
+ }
@@ -1,11 +1,16 @@
1
1
  import { DEV } from "esm-env";
2
+
2
3
  /**
3
4
  * Svelte action to teleport a DOM element to a different target (e.g. body)
4
5
  */
5
- export function portal(node, target = "body") {
6
- let targetNode;
6
+ export function portal(
7
+ node: HTMLElement,
8
+ target: string | HTMLElement = "body",
9
+ ): { update(newTarget: string | HTMLElement): void; destroy(): void } {
10
+ let targetNode: HTMLElement | null;
7
11
  let isTeleported = false;
8
- function update(newTarget) {
12
+
13
+ function update(newTarget: string | HTMLElement) {
9
14
  if (typeof newTarget === "string") {
10
15
  targetNode = document.querySelector(newTarget);
11
16
  if (!targetNode && DEV) {
@@ -16,12 +21,15 @@ export function portal(node, target = "body") {
16
21
  } else {
17
22
  targetNode = newTarget;
18
23
  }
24
+
19
25
  if (targetNode) {
20
26
  targetNode.appendChild(node);
21
27
  isTeleported = true;
22
28
  }
23
29
  }
30
+
24
31
  update(target);
32
+
25
33
  return {
26
34
  update,
27
35
  destroy() {
@@ -1,31 +1,46 @@
1
1
  import hotkeys from "hotkeys-js";
2
2
  import { untrack } from "svelte";
3
+
4
+ export interface ShortcutStoreLike {
5
+ entries: {
6
+ enabled?: boolean;
7
+ keys: string;
8
+ when?: () => boolean;
9
+ handler: (event: KeyboardEvent) => void;
10
+ }[];
11
+ }
12
+
3
13
  /**
4
14
  * Svelte Action to listen for shortcuts registered in shortcutStore.
5
15
  * Applied to the root element of the layout.
6
16
  */
7
- export function shortcutListener(_node, shortcutStore) {
17
+ export function shortcutListener(
18
+ _node: HTMLElement,
19
+ shortcutStore: ShortcutStoreLike,
20
+ ): { destroy(): void } {
8
21
  // Use $effect to reactively sync shortcuts
9
22
  const cleanup = $effect.root(() => {
10
23
  $effect(() => {
11
24
  // Unbind everything first to ensure clean state
12
25
  hotkeys.unbind();
26
+
13
27
  // We read entries here, so this effect re-runs when entries change.
14
28
  for (const entry of shortcutStore.entries) {
15
29
  // @ts-ignore: enabled might not be on the interface but we check it anyway
16
- if (entry.enabled === false) {
17
- continue;
18
- }
30
+ if (entry.enabled === false) continue;
31
+
19
32
  hotkeys(entry.keys, "all", (event, _handler) => {
20
33
  // Check "when" predicate if it exists
21
- if (entry.when && !untrack(() => entry.when())) {
34
+ if (entry.when && !untrack(() => entry.when!())) {
22
35
  return;
23
36
  }
37
+
24
38
  entry.handler(event);
25
39
  });
26
40
  }
27
41
  });
28
42
  });
43
+
29
44
  return {
30
45
  destroy() {
31
46
  cleanup();
@@ -0,0 +1,98 @@
1
+ // src/kernel/src/context/app.svelte.ts
2
+ import { getContext } from "svelte";
3
+ import { RUNE_LAB_CONTEXT } from "./context.ts";
4
+ import { DEV } from "esm-env";
5
+
6
+ /**
7
+ * Application metadata interface
8
+ */
9
+ export interface AppData {
10
+ name: string;
11
+ version: string;
12
+ description: string;
13
+ author: string;
14
+ repository?: string;
15
+ license?: string;
16
+ homepage?: string;
17
+ }
18
+
19
+ /**
20
+ * App Store
21
+ * Manages application metadata and identity
22
+ */
23
+ export class AppStore {
24
+ // State
25
+ name: string = $state("Rune Lab");
26
+ version: string = $state("0.0.1");
27
+ description: string = $state("Modern toolkit for Svelte 5 Runes");
28
+ author: string = $state("Yrrrrrf");
29
+ repository: string = $state("https://github.com/Yrrrrrf/rune-lab");
30
+ license: string = $state("MIT");
31
+ homepage: string = $state("https://jsr.io/@yrrrrrf/rune-lab");
32
+ customIcons: Record<string, string> = $state<Record<string, string>>({});
33
+
34
+ #initialized = false;
35
+
36
+ /**
37
+ * Initialize app store with metadata.
38
+ */
39
+ init(data: Partial<AppData>): void {
40
+ if (this.#initialized) {
41
+ if (DEV) {
42
+ console.warn(
43
+ "AppStore.init() called multiple times. Ignoring subsequent calls.",
44
+ "Overwritten properties would have been:",
45
+ data,
46
+ );
47
+ }
48
+ return;
49
+ }
50
+
51
+ if (data.name) this.name = data.name;
52
+ if (data.version) this.version = data.version;
53
+ if (data.description) this.description = data.description;
54
+ if (data.author) this.author = data.author;
55
+ if (data.repository) this.repository = data.repository;
56
+ if (data.license) this.license = data.license;
57
+ if (data.homepage) this.homepage = data.homepage;
58
+
59
+ this.#initialized = true;
60
+ }
61
+
62
+ /**
63
+ * @internal Test-only. Resets initialization guard.
64
+ */
65
+ __reset(): void {
66
+ this.#initialized = false;
67
+ }
68
+
69
+ /**
70
+ * Get full app information object
71
+ */
72
+ get info(): AppData {
73
+ return {
74
+ name: this.name,
75
+ version: this.version,
76
+ description: this.description,
77
+ author: this.author,
78
+ repository: this.repository,
79
+ license: this.license,
80
+ homepage: this.homepage,
81
+ };
82
+ }
83
+
84
+ /**
85
+ * Registers custom SVG icons to be available globally in the Icon component
86
+ */
87
+ registerIcons(icons: Record<string, string>) {
88
+ this.customIcons = { ...this.customIcons, ...icons };
89
+ }
90
+ }
91
+
92
+ export function createAppStore(): AppStore {
93
+ return new AppStore();
94
+ }
95
+
96
+ export function getAppStore(): AppStore {
97
+ return getContext<AppStore>(RUNE_LAB_CONTEXT.app);
98
+ }
@@ -1,5 +1,20 @@
1
1
  // src/lib/context.ts
2
- export const RUNE_LAB_CONTEXT = {
2
+
3
+ export const RUNE_LAB_CONTEXT: {
4
+ app: symbol;
5
+ api: symbol;
6
+ toast: symbol;
7
+ theme: symbol;
8
+ language: symbol;
9
+ currency: symbol;
10
+ shortcut: symbol;
11
+ layout: symbol;
12
+ commands: symbol;
13
+ persistence: symbol;
14
+ cart: symbol;
15
+ session: symbol;
16
+ exchangeRate: symbol;
17
+ } = {
3
18
  app: Symbol("rl:app"),
4
19
  api: Symbol("rl:api"),
5
20
  toast: Symbol("rl:toast"),
@@ -13,4 +28,4 @@ export const RUNE_LAB_CONTEXT = {
13
28
  cart: Symbol("rl:cart"),
14
29
  session: Symbol("rl:session"),
15
30
  exchangeRate: Symbol("rl:exchange-rate"),
16
- };
31
+ } as const;