reigncode-app 1.3.2

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 (300) hide show
  1. package/AGENTS.md +30 -0
  2. package/Dockerfile +21 -0
  3. package/README.md +51 -0
  4. package/bunfig.toml +3 -0
  5. package/create-effect-simplification-spec.md +515 -0
  6. package/e2e/AGENTS.md +226 -0
  7. package/e2e/actions.ts +1018 -0
  8. package/e2e/app/home.spec.ts +24 -0
  9. package/e2e/app/navigation.spec.ts +10 -0
  10. package/e2e/app/palette.spec.ts +20 -0
  11. package/e2e/app/server-default.spec.ts +58 -0
  12. package/e2e/app/session.spec.ts +16 -0
  13. package/e2e/app/titlebar-history.spec.ts +120 -0
  14. package/e2e/commands/input-focus.spec.ts +15 -0
  15. package/e2e/commands/panels.spec.ts +33 -0
  16. package/e2e/commands/tab-close.spec.ts +32 -0
  17. package/e2e/files/file-open.spec.ts +31 -0
  18. package/e2e/files/file-tree.spec.ts +56 -0
  19. package/e2e/files/file-viewer.spec.ts +156 -0
  20. package/e2e/fixtures.ts +154 -0
  21. package/e2e/models/model-picker.spec.ts +48 -0
  22. package/e2e/models/models-visibility.spec.ts +61 -0
  23. package/e2e/projects/project-edit.spec.ts +43 -0
  24. package/e2e/projects/projects-close.spec.ts +54 -0
  25. package/e2e/projects/projects-switch.spec.ts +116 -0
  26. package/e2e/projects/workspace-new-session.spec.ts +94 -0
  27. package/e2e/projects/workspaces.spec.ts +375 -0
  28. package/e2e/prompt/context.spec.ts +95 -0
  29. package/e2e/prompt/prompt-async.spec.ts +76 -0
  30. package/e2e/prompt/prompt-drop-file-uri.spec.ts +22 -0
  31. package/e2e/prompt/prompt-drop-file.spec.ts +30 -0
  32. package/e2e/prompt/prompt-history.spec.ts +184 -0
  33. package/e2e/prompt/prompt-mention.spec.ts +26 -0
  34. package/e2e/prompt/prompt-multiline.spec.ts +24 -0
  35. package/e2e/prompt/prompt-shell.spec.ts +62 -0
  36. package/e2e/prompt/prompt-slash-open.spec.ts +22 -0
  37. package/e2e/prompt/prompt-slash-share.spec.ts +64 -0
  38. package/e2e/prompt/prompt-slash-terminal.spec.ts +18 -0
  39. package/e2e/prompt/prompt.spec.ts +55 -0
  40. package/e2e/selectors.ts +75 -0
  41. package/e2e/session/session-child-navigation.spec.ts +37 -0
  42. package/e2e/session/session-composer-dock.spec.ts +530 -0
  43. package/e2e/session/session-model-persistence.spec.ts +359 -0
  44. package/e2e/session/session-review.spec.ts +426 -0
  45. package/e2e/session/session-undo-redo.spec.ts +233 -0
  46. package/e2e/session/session.spec.ts +174 -0
  47. package/e2e/settings/settings-keybinds.spec.ts +389 -0
  48. package/e2e/settings/settings-models.spec.ts +122 -0
  49. package/e2e/settings/settings-providers.spec.ts +136 -0
  50. package/e2e/settings/settings.spec.ts +519 -0
  51. package/e2e/sidebar/sidebar-popover-actions.spec.ts +118 -0
  52. package/e2e/sidebar/sidebar-session-links.spec.ts +30 -0
  53. package/e2e/sidebar/sidebar.spec.ts +40 -0
  54. package/e2e/status/status-popover.spec.ts +94 -0
  55. package/e2e/terminal/terminal-init.spec.ts +28 -0
  56. package/e2e/terminal/terminal-reconnect.spec.ts +46 -0
  57. package/e2e/terminal/terminal-tabs.spec.ts +168 -0
  58. package/e2e/terminal/terminal.spec.ts +18 -0
  59. package/e2e/thinking-level.spec.ts +25 -0
  60. package/e2e/tsconfig.json +9 -0
  61. package/e2e/utils.ts +63 -0
  62. package/happydom.ts +75 -0
  63. package/index.html +23 -0
  64. package/package.json +77 -0
  65. package/playwright.config.ts +45 -0
  66. package/public/_headers +17 -0
  67. package/public/oc-theme-preload.js +35 -0
  68. package/script/e2e-local.ts +180 -0
  69. package/src/addons/serialize.test.ts +319 -0
  70. package/src/addons/serialize.ts +634 -0
  71. package/src/app.tsx +308 -0
  72. package/src/components/debug-bar.tsx +443 -0
  73. package/src/components/dialog-connect-provider.tsx +617 -0
  74. package/src/components/dialog-custom-provider-form.ts +158 -0
  75. package/src/components/dialog-custom-provider.test.ts +80 -0
  76. package/src/components/dialog-custom-provider.tsx +329 -0
  77. package/src/components/dialog-edit-project.tsx +255 -0
  78. package/src/components/dialog-fork.tsx +108 -0
  79. package/src/components/dialog-manage-models.tsx +101 -0
  80. package/src/components/dialog-release-notes.tsx +144 -0
  81. package/src/components/dialog-select-directory.tsx +392 -0
  82. package/src/components/dialog-select-file.tsx +466 -0
  83. package/src/components/dialog-select-mcp.tsx +107 -0
  84. package/src/components/dialog-select-model-unpaid.tsx +137 -0
  85. package/src/components/dialog-select-model.tsx +220 -0
  86. package/src/components/dialog-select-provider.tsx +86 -0
  87. package/src/components/dialog-select-server.tsx +649 -0
  88. package/src/components/dialog-settings.tsx +73 -0
  89. package/src/components/file-tree.test.ts +78 -0
  90. package/src/components/file-tree.tsx +507 -0
  91. package/src/components/link.tsx +26 -0
  92. package/src/components/model-tooltip.tsx +91 -0
  93. package/src/components/prompt-input/attachments.test.ts +44 -0
  94. package/src/components/prompt-input/attachments.ts +201 -0
  95. package/src/components/prompt-input/build-request-parts.test.ts +312 -0
  96. package/src/components/prompt-input/build-request-parts.ts +175 -0
  97. package/src/components/prompt-input/context-items.tsx +88 -0
  98. package/src/components/prompt-input/drag-overlay.tsx +25 -0
  99. package/src/components/prompt-input/editor-dom.test.ts +99 -0
  100. package/src/components/prompt-input/editor-dom.ts +148 -0
  101. package/src/components/prompt-input/files.ts +66 -0
  102. package/src/components/prompt-input/history.test.ts +153 -0
  103. package/src/components/prompt-input/history.ts +256 -0
  104. package/src/components/prompt-input/image-attachments.tsx +58 -0
  105. package/src/components/prompt-input/paste.ts +24 -0
  106. package/src/components/prompt-input/placeholder.test.ts +48 -0
  107. package/src/components/prompt-input/placeholder.ts +15 -0
  108. package/src/components/prompt-input/slash-popover.tsx +141 -0
  109. package/src/components/prompt-input/submit.test.ts +346 -0
  110. package/src/components/prompt-input/submit.ts +579 -0
  111. package/src/components/prompt-input.tsx +1595 -0
  112. package/src/components/server/server-row.tsx +130 -0
  113. package/src/components/session/index.ts +5 -0
  114. package/src/components/session/session-context-breakdown.test.ts +61 -0
  115. package/src/components/session/session-context-breakdown.ts +132 -0
  116. package/src/components/session/session-context-format.ts +20 -0
  117. package/src/components/session/session-context-metrics.test.ts +101 -0
  118. package/src/components/session/session-context-metrics.ts +82 -0
  119. package/src/components/session/session-context-tab.tsx +339 -0
  120. package/src/components/session/session-header.tsx +486 -0
  121. package/src/components/session/session-new-view.tsx +91 -0
  122. package/src/components/session/session-sortable-tab.tsx +70 -0
  123. package/src/components/session/session-sortable-terminal-tab.tsx +193 -0
  124. package/src/components/session-context-usage.tsx +122 -0
  125. package/src/components/settings-general.tsx +585 -0
  126. package/src/components/settings-keybinds.tsx +453 -0
  127. package/src/components/settings-list.tsx +5 -0
  128. package/src/components/settings-models.tsx +137 -0
  129. package/src/components/settings-providers.tsx +251 -0
  130. package/src/components/status-popover.tsx +419 -0
  131. package/src/components/terminal.tsx +653 -0
  132. package/src/components/titlebar-history.test.ts +63 -0
  133. package/src/components/titlebar-history.ts +57 -0
  134. package/src/components/titlebar.tsx +312 -0
  135. package/src/constants/file-picker.ts +89 -0
  136. package/src/context/command-keybind.test.ts +69 -0
  137. package/src/context/command.test.ts +25 -0
  138. package/src/context/command.tsx +437 -0
  139. package/src/context/comments.test.ts +186 -0
  140. package/src/context/comments.tsx +243 -0
  141. package/src/context/file/content-cache.ts +88 -0
  142. package/src/context/file/path.test.ts +360 -0
  143. package/src/context/file/path.ts +151 -0
  144. package/src/context/file/tree-store.ts +170 -0
  145. package/src/context/file/types.ts +41 -0
  146. package/src/context/file/view-cache.ts +146 -0
  147. package/src/context/file/watcher.test.ts +149 -0
  148. package/src/context/file/watcher.ts +53 -0
  149. package/src/context/file-content-eviction-accounting.test.ts +65 -0
  150. package/src/context/file.tsx +280 -0
  151. package/src/context/global-sdk.tsx +232 -0
  152. package/src/context/global-sync/bootstrap.ts +206 -0
  153. package/src/context/global-sync/child-store.test.ts +38 -0
  154. package/src/context/global-sync/child-store.ts +281 -0
  155. package/src/context/global-sync/event-reducer.test.ts +552 -0
  156. package/src/context/global-sync/event-reducer.ts +359 -0
  157. package/src/context/global-sync/eviction.ts +28 -0
  158. package/src/context/global-sync/queue.ts +83 -0
  159. package/src/context/global-sync/session-cache.test.ts +102 -0
  160. package/src/context/global-sync/session-cache.ts +62 -0
  161. package/src/context/global-sync/session-load.ts +25 -0
  162. package/src/context/global-sync/session-prefetch.test.ts +96 -0
  163. package/src/context/global-sync/session-prefetch.ts +100 -0
  164. package/src/context/global-sync/session-trim.test.ts +59 -0
  165. package/src/context/global-sync/session-trim.ts +56 -0
  166. package/src/context/global-sync/types.ts +133 -0
  167. package/src/context/global-sync/utils.ts +25 -0
  168. package/src/context/global-sync.test.ts +122 -0
  169. package/src/context/global-sync.tsx +408 -0
  170. package/src/context/highlights.tsx +233 -0
  171. package/src/context/language.tsx +248 -0
  172. package/src/context/layout-scroll.test.ts +64 -0
  173. package/src/context/layout-scroll.ts +126 -0
  174. package/src/context/layout.test.ts +69 -0
  175. package/src/context/layout.tsx +937 -0
  176. package/src/context/local.tsx +422 -0
  177. package/src/context/model-variant.test.ts +86 -0
  178. package/src/context/model-variant.ts +52 -0
  179. package/src/context/models.tsx +163 -0
  180. package/src/context/notification.tsx +373 -0
  181. package/src/context/permission-auto-respond.test.ts +102 -0
  182. package/src/context/permission-auto-respond.ts +51 -0
  183. package/src/context/permission.tsx +277 -0
  184. package/src/context/platform.tsx +99 -0
  185. package/src/context/prompt.tsx +297 -0
  186. package/src/context/sdk.tsx +49 -0
  187. package/src/context/server.tsx +295 -0
  188. package/src/context/settings.tsx +241 -0
  189. package/src/context/sync-optimistic.test.ts +123 -0
  190. package/src/context/sync.tsx +618 -0
  191. package/src/context/terminal-title.ts +51 -0
  192. package/src/context/terminal.test.ts +82 -0
  193. package/src/context/terminal.tsx +437 -0
  194. package/src/entry.tsx +144 -0
  195. package/src/env.d.ts +18 -0
  196. package/src/hooks/use-providers.ts +44 -0
  197. package/src/i18n/ar.ts +855 -0
  198. package/src/i18n/br.ts +867 -0
  199. package/src/i18n/bs.ts +943 -0
  200. package/src/i18n/da.ts +937 -0
  201. package/src/i18n/de.ts +879 -0
  202. package/src/i18n/en.ts +948 -0
  203. package/src/i18n/es.ts +950 -0
  204. package/src/i18n/fr.ts +878 -0
  205. package/src/i18n/ja.ts +861 -0
  206. package/src/i18n/ko.ts +860 -0
  207. package/src/i18n/no.ts +944 -0
  208. package/src/i18n/parity.test.ts +32 -0
  209. package/src/i18n/pl.ts +865 -0
  210. package/src/i18n/ru.ts +946 -0
  211. package/src/i18n/th.ts +933 -0
  212. package/src/i18n/tr.ts +952 -0
  213. package/src/i18n/zh.ts +930 -0
  214. package/src/i18n/zht.ts +925 -0
  215. package/src/index.css +29 -0
  216. package/src/index.ts +6 -0
  217. package/src/pages/directory-layout.tsx +88 -0
  218. package/src/pages/error.tsx +327 -0
  219. package/src/pages/home.tsx +131 -0
  220. package/src/pages/layout/deep-links.ts +50 -0
  221. package/src/pages/layout/helpers.test.ts +211 -0
  222. package/src/pages/layout/helpers.ts +98 -0
  223. package/src/pages/layout/inline-editor.tsx +126 -0
  224. package/src/pages/layout/sidebar-items.tsx +437 -0
  225. package/src/pages/layout/sidebar-project.tsx +384 -0
  226. package/src/pages/layout/sidebar-shell.tsx +125 -0
  227. package/src/pages/layout/sidebar-workspace.tsx +504 -0
  228. package/src/pages/layout.tsx +2509 -0
  229. package/src/pages/session/composer/index.ts +2 -0
  230. package/src/pages/session/composer/session-composer-region.tsx +255 -0
  231. package/src/pages/session/composer/session-composer-state.test.ts +128 -0
  232. package/src/pages/session/composer/session-composer-state.ts +249 -0
  233. package/src/pages/session/composer/session-followup-dock.tsx +109 -0
  234. package/src/pages/session/composer/session-permission-dock.tsx +74 -0
  235. package/src/pages/session/composer/session-question-dock.tsx +449 -0
  236. package/src/pages/session/composer/session-request-tree.ts +52 -0
  237. package/src/pages/session/composer/session-revert-dock.tsx +99 -0
  238. package/src/pages/session/composer/session-todo-dock.tsx +330 -0
  239. package/src/pages/session/file-tab-scroll.test.ts +40 -0
  240. package/src/pages/session/file-tab-scroll.ts +67 -0
  241. package/src/pages/session/file-tabs.tsx +456 -0
  242. package/src/pages/session/handoff.ts +36 -0
  243. package/src/pages/session/helpers.test.ts +181 -0
  244. package/src/pages/session/helpers.ts +198 -0
  245. package/src/pages/session/message-gesture.test.ts +62 -0
  246. package/src/pages/session/message-gesture.ts +21 -0
  247. package/src/pages/session/message-id-from-hash.ts +6 -0
  248. package/src/pages/session/message-timeline.tsx +1013 -0
  249. package/src/pages/session/review-tab.tsx +170 -0
  250. package/src/pages/session/session-layout.ts +20 -0
  251. package/src/pages/session/session-model-helpers.test.ts +51 -0
  252. package/src/pages/session/session-model-helpers.ts +16 -0
  253. package/src/pages/session/session-side-panel.tsx +453 -0
  254. package/src/pages/session/terminal-label.ts +16 -0
  255. package/src/pages/session/terminal-panel.test.ts +25 -0
  256. package/src/pages/session/terminal-panel.tsx +326 -0
  257. package/src/pages/session/use-session-commands.tsx +495 -0
  258. package/src/pages/session/use-session-hash-scroll.test.ts +16 -0
  259. package/src/pages/session/use-session-hash-scroll.ts +197 -0
  260. package/src/pages/session.tsx +1841 -0
  261. package/src/sst-env.d.ts +12 -0
  262. package/src/testing/model-selection.ts +80 -0
  263. package/src/testing/prompt.ts +56 -0
  264. package/src/testing/session-composer.ts +84 -0
  265. package/src/testing/terminal.ts +118 -0
  266. package/src/theme-preload.test.ts +46 -0
  267. package/src/utils/agent.ts +23 -0
  268. package/src/utils/aim.ts +138 -0
  269. package/src/utils/base64.ts +10 -0
  270. package/src/utils/comment-note.ts +88 -0
  271. package/src/utils/id.ts +99 -0
  272. package/src/utils/notification-click.test.ts +27 -0
  273. package/src/utils/notification-click.ts +13 -0
  274. package/src/utils/persist.test.ts +115 -0
  275. package/src/utils/persist.ts +476 -0
  276. package/src/utils/prompt.test.ts +44 -0
  277. package/src/utils/prompt.ts +203 -0
  278. package/src/utils/runtime-adapters.test.ts +62 -0
  279. package/src/utils/runtime-adapters.ts +39 -0
  280. package/src/utils/same.ts +6 -0
  281. package/src/utils/scoped-cache.test.ts +69 -0
  282. package/src/utils/scoped-cache.ts +104 -0
  283. package/src/utils/server-errors.test.ts +131 -0
  284. package/src/utils/server-errors.ts +80 -0
  285. package/src/utils/server-health.test.ts +123 -0
  286. package/src/utils/server-health.ts +91 -0
  287. package/src/utils/server.ts +22 -0
  288. package/src/utils/solid-dnd.tsx +49 -0
  289. package/src/utils/sound.ts +117 -0
  290. package/src/utils/terminal-writer.test.ts +64 -0
  291. package/src/utils/terminal-writer.ts +65 -0
  292. package/src/utils/time.ts +22 -0
  293. package/src/utils/uuid.test.ts +78 -0
  294. package/src/utils/uuid.ts +12 -0
  295. package/src/utils/worktree.test.ts +46 -0
  296. package/src/utils/worktree.ts +73 -0
  297. package/sst-env.d.ts +10 -0
  298. package/tsconfig.json +26 -0
  299. package/vite.config.ts +15 -0
  300. package/vite.js +26 -0
@@ -0,0 +1,49 @@
1
+ import type { Event } from "@reign-labs/sdk/v2/client"
2
+ import { createSimpleContext } from "@reign-labs/ui/context"
3
+ import { createGlobalEmitter } from "@solid-primitives/event-bus"
4
+ import { type Accessor, createEffect, createMemo, onCleanup } from "solid-js"
5
+ import { useGlobalSDK } from "./global-sdk"
6
+
7
+ type SDKEventMap = {
8
+ [key in Event["type"]]: Extract<Event, { type: key }>
9
+ }
10
+
11
+ export const { use: useSDK, provider: SDKProvider } = createSimpleContext({
12
+ name: "SDK",
13
+ init: (props: { directory: Accessor<string> }) => {
14
+ const globalSDK = useGlobalSDK()
15
+
16
+ const directory = createMemo(props.directory)
17
+ const client = createMemo(() =>
18
+ globalSDK.createClient({
19
+ directory: directory(),
20
+ throwOnError: true,
21
+ }),
22
+ )
23
+
24
+ const emitter = createGlobalEmitter<SDKEventMap>()
25
+
26
+ createEffect(() => {
27
+ const unsub = globalSDK.event.on(directory(), (event) => {
28
+ emitter.emit(event.type, event)
29
+ })
30
+ onCleanup(unsub)
31
+ })
32
+
33
+ return {
34
+ get directory() {
35
+ return directory()
36
+ },
37
+ get client() {
38
+ return client()
39
+ },
40
+ event: emitter,
41
+ get url() {
42
+ return globalSDK.url
43
+ },
44
+ createClient(opts: Parameters<typeof globalSDK.createClient>[0]) {
45
+ return globalSDK.createClient(opts)
46
+ },
47
+ }
48
+ },
49
+ })
@@ -0,0 +1,295 @@
1
+ import { createSimpleContext } from "@reign-labs/ui/context"
2
+ import { type Accessor, batch, createEffect, createMemo, onCleanup } from "solid-js"
3
+ import { createStore } from "solid-js/store"
4
+ import { Persist, persisted } from "@/utils/persist"
5
+ import { useCheckServerHealth } from "@/utils/server-health"
6
+
7
+ type StoredProject = { worktree: string; expanded: boolean }
8
+ type StoredServer = string | ServerConnection.HttpBase | ServerConnection.Http
9
+ const HEALTH_POLL_INTERVAL_MS = 10_000
10
+
11
+ export function normalizeServerUrl(input: string) {
12
+ const trimmed = input.trim()
13
+ if (!trimmed) return
14
+ const withProtocol = /^https?:\/\//.test(trimmed) ? trimmed : `http://${trimmed}`
15
+ return withProtocol.replace(/\/+$/, "")
16
+ }
17
+
18
+ export function serverName(conn?: ServerConnection.Any, ignoreDisplayName = false) {
19
+ if (!conn) return ""
20
+ if (conn.displayName && !ignoreDisplayName) return conn.displayName
21
+ return conn.http.url.replace(/^https?:\/\//, "").replace(/\/+$/, "")
22
+ }
23
+
24
+ function projectsKey(key: ServerConnection.Key) {
25
+ if (!key) return ""
26
+ if (key === "sidecar") return "local"
27
+ if (isLocalHost(key)) return "local"
28
+ return key
29
+ }
30
+
31
+ function isLocalHost(url: string) {
32
+ const host = url.replace(/^https?:\/\//, "").split(":")[0]
33
+ if (host === "localhost" || host === "127.0.0.1") return "local"
34
+ }
35
+
36
+ export namespace ServerConnection {
37
+ type Base = { displayName?: string }
38
+
39
+ export type HttpBase = {
40
+ url: string
41
+ username?: string
42
+ password?: string
43
+ }
44
+
45
+ // Regular web connections
46
+ export type Http = {
47
+ type: "http"
48
+ http: HttpBase
49
+ } & Base
50
+
51
+ export type Sidecar = {
52
+ type: "sidecar"
53
+ http: HttpBase
54
+ } & (
55
+ | // Regular desktop server
56
+ { variant: "base" }
57
+ // WSL server (windows only)
58
+ | {
59
+ variant: "wsl"
60
+ distro: string
61
+ }
62
+ ) &
63
+ Base
64
+
65
+ // Remote server desktop can SSH into
66
+ export type Ssh = {
67
+ type: "ssh"
68
+ host: string
69
+ // SSH client exposes an HTTP server for the app to use as a proxy
70
+ http: HttpBase
71
+ } & Base
72
+
73
+ export type Any =
74
+ | Http
75
+ // All these are desktop-only
76
+ | (Sidecar | Ssh)
77
+
78
+ export const key = (conn: Any): Key => {
79
+ switch (conn.type) {
80
+ case "http":
81
+ return Key.make(conn.http.url)
82
+ case "sidecar": {
83
+ if (conn.variant === "wsl") return Key.make(`wsl:${conn.distro}`)
84
+ return Key.make("sidecar")
85
+ }
86
+ case "ssh":
87
+ return Key.make(`ssh:${conn.host}`)
88
+ }
89
+ }
90
+
91
+ export type Key = string & { _brand: "Key" }
92
+ export const Key = { make: (v: string) => v as Key }
93
+ }
94
+
95
+ export const { use: useServer, provider: ServerProvider } = createSimpleContext({
96
+ name: "Server",
97
+ init: (props: { defaultServer: ServerConnection.Key; servers?: Array<ServerConnection.Any> }) => {
98
+ const checkServerHealth = useCheckServerHealth()
99
+
100
+ const [store, setStore, _, ready] = persisted(
101
+ Persist.global("server", ["server.v3"]),
102
+ createStore({
103
+ list: [] as StoredServer[],
104
+ projects: {} as Record<string, StoredProject[]>,
105
+ lastProject: {} as Record<string, string>,
106
+ }),
107
+ )
108
+
109
+ const url = (x: StoredServer) => (typeof x === "string" ? x : "type" in x ? x.http.url : x.url)
110
+
111
+ const allServers = createMemo((): Array<ServerConnection.Any> => {
112
+ const servers = [
113
+ ...(props.servers ?? []),
114
+ ...store.list.map((value) =>
115
+ typeof value === "string"
116
+ ? {
117
+ type: "http" as const,
118
+ http: { url: value },
119
+ }
120
+ : value,
121
+ ),
122
+ ]
123
+
124
+ const deduped = new Map(
125
+ servers.map((value) => {
126
+ const conn: ServerConnection.Any = "type" in value ? value : { type: "http", http: value }
127
+ return [ServerConnection.key(conn), conn]
128
+ }),
129
+ )
130
+
131
+ return [...deduped.values()]
132
+ })
133
+
134
+ const [state, setState] = createStore({
135
+ active: props.defaultServer,
136
+ healthy: undefined as boolean | undefined,
137
+ })
138
+
139
+ const healthy = () => state.healthy
140
+
141
+ function startHealthPolling(conn: ServerConnection.Any) {
142
+ let alive = true
143
+ let busy = false
144
+
145
+ const run = () => {
146
+ if (busy) return
147
+ busy = true
148
+ void check(conn)
149
+ .then((next) => {
150
+ if (!alive) return
151
+ setState("healthy", next)
152
+ })
153
+ .finally(() => {
154
+ busy = false
155
+ })
156
+ }
157
+
158
+ run()
159
+ const interval = setInterval(run, HEALTH_POLL_INTERVAL_MS)
160
+ return () => {
161
+ alive = false
162
+ clearInterval(interval)
163
+ }
164
+ }
165
+
166
+ function setActive(input: ServerConnection.Key) {
167
+ if (state.active !== input) setState("active", input)
168
+ }
169
+
170
+ function add(input: ServerConnection.Http) {
171
+ const url_ = normalizeServerUrl(input.http.url)
172
+ if (!url_) return
173
+ const conn = { ...input, http: { ...input.http, url: url_ } }
174
+ return batch(() => {
175
+ const existing = store.list.findIndex((x) => url(x) === url_)
176
+ if (existing !== -1) {
177
+ setStore("list", existing, conn)
178
+ } else {
179
+ setStore("list", store.list.length, conn)
180
+ }
181
+ setState("active", ServerConnection.key(conn))
182
+ return conn
183
+ })
184
+ }
185
+
186
+ function remove(key: ServerConnection.Key) {
187
+ const list = store.list.filter((x) => url(x) !== key)
188
+ batch(() => {
189
+ setStore("list", list)
190
+ if (state.active === key) {
191
+ const next = list[0]
192
+ setState("active", next ? ServerConnection.Key.make(url(next)) : props.defaultServer)
193
+ }
194
+ })
195
+ }
196
+
197
+ const isReady = createMemo(() => ready() && !!state.active)
198
+
199
+ const check = (conn: ServerConnection.Any) => checkServerHealth(conn.http).then((x) => x.healthy)
200
+
201
+ createEffect(() => {
202
+ const current_ = current()
203
+ if (!current_) return
204
+
205
+ setState("healthy", undefined)
206
+ onCleanup(startHealthPolling(current_))
207
+ })
208
+
209
+ const origin = createMemo(() => projectsKey(state.active))
210
+ const projectsList = createMemo(() => store.projects[origin()] ?? [])
211
+ const current: Accessor<ServerConnection.Any | undefined> = createMemo(
212
+ () => allServers().find((s) => ServerConnection.key(s) === state.active) ?? allServers()[0],
213
+ )
214
+ const isLocal = createMemo(() => {
215
+ const c = current()
216
+ return (c?.type === "sidecar" && c.variant === "base") || (c?.type === "http" && isLocalHost(c.http.url))
217
+ })
218
+
219
+ return {
220
+ ready: isReady,
221
+ healthy,
222
+ isLocal,
223
+ get key() {
224
+ return state.active
225
+ },
226
+ get name() {
227
+ return serverName(current())
228
+ },
229
+ get list() {
230
+ return allServers()
231
+ },
232
+ get current() {
233
+ return current()
234
+ },
235
+ setActive,
236
+ add,
237
+ remove,
238
+ projects: {
239
+ list: projectsList,
240
+ open(directory: string) {
241
+ const key = origin()
242
+ if (!key) return
243
+ const current = store.projects[key] ?? []
244
+ if (current.find((x) => x.worktree === directory)) return
245
+ setStore("projects", key, [{ worktree: directory, expanded: true }, ...current])
246
+ },
247
+ close(directory: string) {
248
+ const key = origin()
249
+ if (!key) return
250
+ const current = store.projects[key] ?? []
251
+ setStore(
252
+ "projects",
253
+ key,
254
+ current.filter((x) => x.worktree !== directory),
255
+ )
256
+ },
257
+ expand(directory: string) {
258
+ const key = origin()
259
+ if (!key) return
260
+ const current = store.projects[key] ?? []
261
+ const index = current.findIndex((x) => x.worktree === directory)
262
+ if (index !== -1) setStore("projects", key, index, "expanded", true)
263
+ },
264
+ collapse(directory: string) {
265
+ const key = origin()
266
+ if (!key) return
267
+ const current = store.projects[key] ?? []
268
+ const index = current.findIndex((x) => x.worktree === directory)
269
+ if (index !== -1) setStore("projects", key, index, "expanded", false)
270
+ },
271
+ move(directory: string, toIndex: number) {
272
+ const key = origin()
273
+ if (!key) return
274
+ const current = store.projects[key] ?? []
275
+ const fromIndex = current.findIndex((x) => x.worktree === directory)
276
+ if (fromIndex === -1 || fromIndex === toIndex) return
277
+ const result = [...current]
278
+ const [item] = result.splice(fromIndex, 1)
279
+ result.splice(toIndex, 0, item)
280
+ setStore("projects", key, result)
281
+ },
282
+ last() {
283
+ const key = origin()
284
+ if (!key) return
285
+ return store.lastProject[key]
286
+ },
287
+ touch(directory: string) {
288
+ const key = origin()
289
+ if (!key) return
290
+ setStore("lastProject", key, directory)
291
+ },
292
+ },
293
+ }
294
+ },
295
+ })
@@ -0,0 +1,241 @@
1
+ import { createStore, reconcile } from "solid-js/store"
2
+ import { createEffect, createMemo } from "solid-js"
3
+ import { createSimpleContext } from "@reign-labs/ui/context"
4
+ import { persisted } from "@/utils/persist"
5
+
6
+ export interface NotificationSettings {
7
+ agent: boolean
8
+ permissions: boolean
9
+ errors: boolean
10
+ }
11
+
12
+ export interface SoundSettings {
13
+ agentEnabled: boolean
14
+ agent: string
15
+ permissionsEnabled: boolean
16
+ permissions: string
17
+ errorsEnabled: boolean
18
+ errors: string
19
+ }
20
+
21
+ export interface Settings {
22
+ general: {
23
+ autoSave: boolean
24
+ releaseNotes: boolean
25
+ followup: "queue" | "steer"
26
+ showReasoningSummaries: boolean
27
+ shellToolPartsExpanded: boolean
28
+ editToolPartsExpanded: boolean
29
+ }
30
+ updates: {
31
+ startup: boolean
32
+ }
33
+ appearance: {
34
+ fontSize: number
35
+ font: string
36
+ }
37
+ keybinds: Record<string, string>
38
+ permissions: {
39
+ autoApprove: boolean
40
+ }
41
+ notifications: NotificationSettings
42
+ sounds: SoundSettings
43
+ }
44
+
45
+ const defaultSettings: Settings = {
46
+ general: {
47
+ autoSave: true,
48
+ releaseNotes: true,
49
+ followup: "steer",
50
+ showReasoningSummaries: false,
51
+ shellToolPartsExpanded: true,
52
+ editToolPartsExpanded: false,
53
+ },
54
+ updates: {
55
+ startup: true,
56
+ },
57
+ appearance: {
58
+ fontSize: 14,
59
+ font: "ibm-plex-mono",
60
+ },
61
+ keybinds: {},
62
+ permissions: {
63
+ autoApprove: false,
64
+ },
65
+ notifications: {
66
+ agent: true,
67
+ permissions: true,
68
+ errors: false,
69
+ },
70
+ sounds: {
71
+ agentEnabled: true,
72
+ agent: "staplebops-01",
73
+ permissionsEnabled: true,
74
+ permissions: "staplebops-02",
75
+ errorsEnabled: true,
76
+ errors: "nope-03",
77
+ },
78
+ }
79
+
80
+ const monoFallback =
81
+ 'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace'
82
+
83
+ const monoFonts: Record<string, string> = {
84
+ "ibm-plex-mono": `"IBM Plex Mono", "IBM Plex Mono Fallback", ${monoFallback}`,
85
+ "cascadia-code": `"Cascadia Code Nerd Font", "Cascadia Code NF", "Cascadia Mono NF", "IBM Plex Mono", "IBM Plex Mono Fallback", ${monoFallback}`,
86
+ "fira-code": `"Fira Code Nerd Font", "FiraMono Nerd Font", "FiraMono Nerd Font Mono", "IBM Plex Mono", "IBM Plex Mono Fallback", ${monoFallback}`,
87
+ hack: `"Hack Nerd Font", "Hack Nerd Font Mono", "IBM Plex Mono", "IBM Plex Mono Fallback", ${monoFallback}`,
88
+ inconsolata: `"Inconsolata Nerd Font", "Inconsolata Nerd Font Mono","IBM Plex Mono", "IBM Plex Mono Fallback", ${monoFallback}`,
89
+ "intel-one-mono": `"Intel One Mono Nerd Font", "IntoneMono Nerd Font", "IntoneMono Nerd Font Mono", "IBM Plex Mono", "IBM Plex Mono Fallback", ${monoFallback}`,
90
+ iosevka: `"Iosevka Nerd Font", "Iosevka Nerd Font Mono", "IBM Plex Mono", "IBM Plex Mono Fallback", ${monoFallback}`,
91
+ "jetbrains-mono": `"JetBrains Mono Nerd Font", "JetBrainsMono Nerd Font Mono", "JetBrainsMonoNL Nerd Font", "JetBrainsMonoNL Nerd Font Mono", "IBM Plex Mono", "IBM Plex Mono Fallback", ${monoFallback}`,
92
+ "meslo-lgs": `"Meslo LGS Nerd Font", "MesloLGS Nerd Font", "MesloLGM Nerd Font", "IBM Plex Mono", "IBM Plex Mono Fallback", ${monoFallback}`,
93
+ "roboto-mono": `"Roboto Mono Nerd Font", "RobotoMono Nerd Font", "RobotoMono Nerd Font Mono", "IBM Plex Mono", "IBM Plex Mono Fallback", ${monoFallback}`,
94
+ "source-code-pro": `"Source Code Pro Nerd Font", "SauceCodePro Nerd Font", "SauceCodePro Nerd Font Mono", "IBM Plex Mono", "IBM Plex Mono Fallback", ${monoFallback}`,
95
+ "ubuntu-mono": `"Ubuntu Mono Nerd Font", "UbuntuMono Nerd Font", "UbuntuMono Nerd Font Mono", "IBM Plex Mono", "IBM Plex Mono Fallback", ${monoFallback}`,
96
+ "geist-mono": `"GeistMono Nerd Font", "GeistMono Nerd Font Mono", "IBM Plex Mono", "IBM Plex Mono Fallback", ${monoFallback}`,
97
+ }
98
+
99
+ export function monoFontFamily(font: string | undefined) {
100
+ return monoFonts[font ?? defaultSettings.appearance.font] ?? monoFonts[defaultSettings.appearance.font]
101
+ }
102
+
103
+ function withFallback<T>(read: () => T | undefined, fallback: T) {
104
+ return createMemo(() => read() ?? fallback)
105
+ }
106
+
107
+ export const { use: useSettings, provider: SettingsProvider } = createSimpleContext({
108
+ name: "Settings",
109
+ init: () => {
110
+ const [store, setStore, _, ready] = persisted("settings.v3", createStore<Settings>(defaultSettings))
111
+
112
+ createEffect(() => {
113
+ if (typeof document === "undefined") return
114
+ document.documentElement.style.setProperty("--font-family-mono", monoFontFamily(store.appearance?.font))
115
+ })
116
+
117
+ return {
118
+ ready,
119
+ get current() {
120
+ return store
121
+ },
122
+ general: {
123
+ autoSave: withFallback(() => store.general?.autoSave, defaultSettings.general.autoSave),
124
+ setAutoSave(value: boolean) {
125
+ setStore("general", "autoSave", value)
126
+ },
127
+ releaseNotes: withFallback(() => store.general?.releaseNotes, defaultSettings.general.releaseNotes),
128
+ setReleaseNotes(value: boolean) {
129
+ setStore("general", "releaseNotes", value)
130
+ },
131
+ followup: withFallback(() => store.general?.followup, defaultSettings.general.followup),
132
+ setFollowup(value: "queue" | "steer") {
133
+ setStore("general", "followup", value)
134
+ },
135
+ showReasoningSummaries: withFallback(
136
+ () => store.general?.showReasoningSummaries,
137
+ defaultSettings.general.showReasoningSummaries,
138
+ ),
139
+ setShowReasoningSummaries(value: boolean) {
140
+ setStore("general", "showReasoningSummaries", value)
141
+ },
142
+ shellToolPartsExpanded: withFallback(
143
+ () => store.general?.shellToolPartsExpanded,
144
+ defaultSettings.general.shellToolPartsExpanded,
145
+ ),
146
+ setShellToolPartsExpanded(value: boolean) {
147
+ setStore("general", "shellToolPartsExpanded", value)
148
+ },
149
+ editToolPartsExpanded: withFallback(
150
+ () => store.general?.editToolPartsExpanded,
151
+ defaultSettings.general.editToolPartsExpanded,
152
+ ),
153
+ setEditToolPartsExpanded(value: boolean) {
154
+ setStore("general", "editToolPartsExpanded", value)
155
+ },
156
+ },
157
+ updates: {
158
+ startup: withFallback(() => store.updates?.startup, defaultSettings.updates.startup),
159
+ setStartup(value: boolean) {
160
+ setStore("updates", "startup", value)
161
+ },
162
+ },
163
+ appearance: {
164
+ fontSize: withFallback(() => store.appearance?.fontSize, defaultSettings.appearance.fontSize),
165
+ setFontSize(value: number) {
166
+ setStore("appearance", "fontSize", value)
167
+ },
168
+ font: withFallback(() => store.appearance?.font, defaultSettings.appearance.font),
169
+ setFont(value: string) {
170
+ setStore("appearance", "font", value)
171
+ },
172
+ },
173
+ keybinds: {
174
+ get: (action: string) => store.keybinds?.[action],
175
+ set(action: string, keybind: string) {
176
+ setStore("keybinds", action, keybind)
177
+ },
178
+ reset(action: string) {
179
+ setStore("keybinds", (current) => {
180
+ if (!Object.prototype.hasOwnProperty.call(current, action)) return current
181
+ const next = { ...current }
182
+ delete next[action]
183
+ return next
184
+ })
185
+ },
186
+ resetAll() {
187
+ setStore("keybinds", reconcile({}))
188
+ },
189
+ },
190
+ permissions: {
191
+ autoApprove: withFallback(() => store.permissions?.autoApprove, defaultSettings.permissions.autoApprove),
192
+ setAutoApprove(value: boolean) {
193
+ setStore("permissions", "autoApprove", value)
194
+ },
195
+ },
196
+ notifications: {
197
+ agent: withFallback(() => store.notifications?.agent, defaultSettings.notifications.agent),
198
+ setAgent(value: boolean) {
199
+ setStore("notifications", "agent", value)
200
+ },
201
+ permissions: withFallback(() => store.notifications?.permissions, defaultSettings.notifications.permissions),
202
+ setPermissions(value: boolean) {
203
+ setStore("notifications", "permissions", value)
204
+ },
205
+ errors: withFallback(() => store.notifications?.errors, defaultSettings.notifications.errors),
206
+ setErrors(value: boolean) {
207
+ setStore("notifications", "errors", value)
208
+ },
209
+ },
210
+ sounds: {
211
+ agentEnabled: withFallback(() => store.sounds?.agentEnabled, defaultSettings.sounds.agentEnabled),
212
+ setAgentEnabled(value: boolean) {
213
+ setStore("sounds", "agentEnabled", value)
214
+ },
215
+ agent: withFallback(() => store.sounds?.agent, defaultSettings.sounds.agent),
216
+ setAgent(value: string) {
217
+ setStore("sounds", "agent", value)
218
+ },
219
+ permissionsEnabled: withFallback(
220
+ () => store.sounds?.permissionsEnabled,
221
+ defaultSettings.sounds.permissionsEnabled,
222
+ ),
223
+ setPermissionsEnabled(value: boolean) {
224
+ setStore("sounds", "permissionsEnabled", value)
225
+ },
226
+ permissions: withFallback(() => store.sounds?.permissions, defaultSettings.sounds.permissions),
227
+ setPermissions(value: string) {
228
+ setStore("sounds", "permissions", value)
229
+ },
230
+ errorsEnabled: withFallback(() => store.sounds?.errorsEnabled, defaultSettings.sounds.errorsEnabled),
231
+ setErrorsEnabled(value: boolean) {
232
+ setStore("sounds", "errorsEnabled", value)
233
+ },
234
+ errors: withFallback(() => store.sounds?.errors, defaultSettings.sounds.errors),
235
+ setErrors(value: string) {
236
+ setStore("sounds", "errors", value)
237
+ },
238
+ },
239
+ }
240
+ },
241
+ })