@zerwiz/ymir 0.1.33 → 0.1.35

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 (930) hide show
  1. package/CHANGELOG.md +56 -0
  2. package/apps/README.md +4 -0
  3. package/apps/hlidskjalf/README.md +68 -0
  4. package/apps/hlidskjalf/android/app/build.gradle +73 -0
  5. package/apps/hlidskjalf/android/app/capacitor.build.gradle +19 -0
  6. package/apps/hlidskjalf/android/app/proguard-rules.pro +21 -0
  7. package/apps/hlidskjalf/android/app/src/androidTest/java/com/getcapacitor/myapp/ExampleInstrumentedTest.java +26 -0
  8. package/apps/hlidskjalf/android/app/src/main/AndroidManifest.xml +41 -0
  9. package/apps/hlidskjalf/android/app/src/main/java/org/zerwiz/ymir/MainActivity.java +5 -0
  10. package/apps/hlidskjalf/android/app/src/main/res/drawable/ic_launcher_background.xml +170 -0
  11. package/apps/hlidskjalf/android/app/src/main/res/drawable/splash.png +0 -0
  12. package/apps/hlidskjalf/android/app/src/main/res/drawable-land-hdpi/splash.png +0 -0
  13. package/apps/hlidskjalf/android/app/src/main/res/drawable-land-mdpi/splash.png +0 -0
  14. package/apps/hlidskjalf/android/app/src/main/res/drawable-land-xhdpi/splash.png +0 -0
  15. package/apps/hlidskjalf/android/app/src/main/res/drawable-land-xxhdpi/splash.png +0 -0
  16. package/apps/hlidskjalf/android/app/src/main/res/drawable-land-xxxhdpi/splash.png +0 -0
  17. package/apps/hlidskjalf/android/app/src/main/res/drawable-port-hdpi/splash.png +0 -0
  18. package/apps/hlidskjalf/android/app/src/main/res/drawable-port-mdpi/splash.png +0 -0
  19. package/apps/hlidskjalf/android/app/src/main/res/drawable-port-xhdpi/splash.png +0 -0
  20. package/apps/hlidskjalf/android/app/src/main/res/drawable-port-xxhdpi/splash.png +0 -0
  21. package/apps/hlidskjalf/android/app/src/main/res/drawable-port-xxxhdpi/splash.png +0 -0
  22. package/apps/hlidskjalf/android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml +34 -0
  23. package/apps/hlidskjalf/android/app/src/main/res/layout/activity_main.xml +12 -0
  24. package/apps/hlidskjalf/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +5 -0
  25. package/apps/hlidskjalf/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml +5 -0
  26. package/apps/hlidskjalf/android/app/src/main/res/mipmap-hdpi/ic_launcher.png +0 -0
  27. package/apps/hlidskjalf/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png +0 -0
  28. package/apps/hlidskjalf/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png +0 -0
  29. package/apps/hlidskjalf/android/app/src/main/res/mipmap-mdpi/ic_launcher.png +0 -0
  30. package/apps/hlidskjalf/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png +0 -0
  31. package/apps/hlidskjalf/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png +0 -0
  32. package/apps/hlidskjalf/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png +0 -0
  33. package/apps/hlidskjalf/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png +0 -0
  34. package/apps/hlidskjalf/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png +0 -0
  35. package/apps/hlidskjalf/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png +0 -0
  36. package/apps/hlidskjalf/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png +0 -0
  37. package/apps/hlidskjalf/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png +0 -0
  38. package/apps/hlidskjalf/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png +0 -0
  39. package/apps/hlidskjalf/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png +0 -0
  40. package/apps/hlidskjalf/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png +0 -0
  41. package/apps/hlidskjalf/android/app/src/main/res/values/ic_launcher_background.xml +4 -0
  42. package/apps/hlidskjalf/android/app/src/main/res/values/strings.xml +7 -0
  43. package/apps/hlidskjalf/android/app/src/main/res/values/styles.xml +22 -0
  44. package/apps/hlidskjalf/android/app/src/main/res/xml/file_paths.xml +5 -0
  45. package/apps/hlidskjalf/android/app/src/test/java/com/getcapacitor/myapp/ExampleUnitTest.java +18 -0
  46. package/apps/hlidskjalf/android/build.gradle +29 -0
  47. package/apps/hlidskjalf/android/capacitor.settings.gradle +3 -0
  48. package/apps/hlidskjalf/android/gradle/wrapper/gradle-wrapper.jar +0 -0
  49. package/apps/hlidskjalf/android/gradle/wrapper/gradle-wrapper.properties +7 -0
  50. package/apps/hlidskjalf/android/gradle.properties +22 -0
  51. package/apps/hlidskjalf/android/gradlew +248 -0
  52. package/apps/hlidskjalf/android/gradlew.bat +92 -0
  53. package/apps/hlidskjalf/android/settings.gradle +5 -0
  54. package/apps/hlidskjalf/android/variables.gradle +16 -0
  55. package/apps/hlidskjalf/capacitor.config.ts +26 -0
  56. package/apps/hlidskjalf/dist/apple-touch-icon.png +0 -0
  57. package/apps/hlidskjalf/dist/assets/index-BWmNIyaG.css +1 -0
  58. package/apps/hlidskjalf/dist/assets/index-k7QEfNto.js +88 -0
  59. package/apps/hlidskjalf/dist/assets/index-k7QEfNto.js.map +1 -0
  60. package/apps/hlidskjalf/dist/icon.svg +7 -0
  61. package/apps/hlidskjalf/dist/index.html +64 -0
  62. package/apps/hlidskjalf/dist/manifest.webmanifest +31 -0
  63. package/apps/hlidskjalf/dist/og.jpg +0 -0
  64. package/apps/hlidskjalf/dist/og.png +0 -0
  65. package/apps/hlidskjalf/dist/og.svg +64 -0
  66. package/apps/hlidskjalf/dist/sw.js +39 -0
  67. package/apps/hlidskjalf/dist/ymir-icon.png +0 -0
  68. package/apps/hlidskjalf/dist/ymir-mark.svg +62 -0
  69. package/apps/hlidskjalf/electron/icon.png +0 -0
  70. package/apps/hlidskjalf/electron/main.cjs +236 -0
  71. package/apps/hlidskjalf/electron/preload.cjs +8 -0
  72. package/apps/hlidskjalf/electron/smidja-icon.png +0 -0
  73. package/apps/hlidskjalf/electron/ymir-hlidskjalf.desktop.in +12 -0
  74. package/apps/hlidskjalf/electron/ymir-smidja.desktop.in +12 -0
  75. package/apps/hlidskjalf/index.html +63 -0
  76. package/apps/hlidskjalf/package-lock.json +5398 -0
  77. package/apps/hlidskjalf/package.json +55 -0
  78. package/apps/hlidskjalf/public/apple-touch-icon.png +0 -0
  79. package/apps/hlidskjalf/public/icon.svg +7 -0
  80. package/apps/hlidskjalf/public/manifest.webmanifest +31 -0
  81. package/apps/hlidskjalf/public/og.jpg +0 -0
  82. package/apps/hlidskjalf/public/og.png +0 -0
  83. package/apps/hlidskjalf/public/og.svg +64 -0
  84. package/apps/hlidskjalf/public/sw.js +39 -0
  85. package/apps/hlidskjalf/public/ymir-icon.png +0 -0
  86. package/apps/hlidskjalf/public/ymir-mark.svg +62 -0
  87. package/apps/hlidskjalf/server/accounts.ts +159 -0
  88. package/apps/hlidskjalf/server/index.ts +2045 -0
  89. package/apps/hlidskjalf/src/app/App.tsx +116 -0
  90. package/apps/hlidskjalf/src/app/BottomStream.tsx +128 -0
  91. package/apps/hlidskjalf/src/app/MobileNav.tsx +87 -0
  92. package/apps/hlidskjalf/src/app/Rail.tsx +106 -0
  93. package/apps/hlidskjalf/src/app/Shell.tsx +135 -0
  94. package/apps/hlidskjalf/src/app/Topbar.tsx +130 -0
  95. package/apps/hlidskjalf/src/components/AccentPicker.tsx +207 -0
  96. package/apps/hlidskjalf/src/components/AccountMenu.tsx +115 -0
  97. package/apps/hlidskjalf/src/components/AgentCard.tsx +75 -0
  98. package/apps/hlidskjalf/src/components/EmberBackground.tsx +36 -0
  99. package/apps/hlidskjalf/src/components/Emblem.tsx +48 -0
  100. package/apps/hlidskjalf/src/components/Halls.tsx +155 -0
  101. package/apps/hlidskjalf/src/components/LoginModal.tsx +329 -0
  102. package/apps/hlidskjalf/src/components/Markdown.tsx +10 -0
  103. package/apps/hlidskjalf/src/components/MetricTile.tsx +40 -0
  104. package/apps/hlidskjalf/src/components/ModelPicker.tsx +100 -0
  105. package/apps/hlidskjalf/src/components/Overlay.tsx +134 -0
  106. package/apps/hlidskjalf/src/components/PRCard.tsx +154 -0
  107. package/apps/hlidskjalf/src/components/RecallPanel.tsx +116 -0
  108. package/apps/hlidskjalf/src/components/RuneTag.tsx +23 -0
  109. package/apps/hlidskjalf/src/components/Status.tsx +35 -0
  110. package/apps/hlidskjalf/src/components/TaskChip.tsx +23 -0
  111. package/apps/hlidskjalf/src/components/TraceRow.tsx +28 -0
  112. package/apps/hlidskjalf/src/data/lore.ts +127 -0
  113. package/apps/hlidskjalf/src/data/metadata.ts +147 -0
  114. package/apps/hlidskjalf/src/data/mythology.ts +95 -0
  115. package/apps/hlidskjalf/src/data/realms.ts +167 -0
  116. package/apps/hlidskjalf/src/gates/Cron.tsx +77 -0
  117. package/apps/hlidskjalf/src/gates/Decisions.tsx +67 -0
  118. package/apps/hlidskjalf/src/gates/Files.tsx +138 -0
  119. package/apps/hlidskjalf/src/gates/Fleet.tsx +184 -0
  120. package/apps/hlidskjalf/src/gates/Forge.tsx +486 -0
  121. package/apps/hlidskjalf/src/gates/OmniChat.tsx +217 -0
  122. package/apps/hlidskjalf/src/gates/Processes.tsx +162 -0
  123. package/apps/hlidskjalf/src/gates/Profile.tsx +225 -0
  124. package/apps/hlidskjalf/src/gates/Reviews.tsx +33 -0
  125. package/apps/hlidskjalf/src/gates/Runes.tsx +116 -0
  126. package/apps/hlidskjalf/src/gates/Runtime.tsx +106 -0
  127. package/apps/hlidskjalf/src/gates/Sessions.tsx +109 -0
  128. package/apps/hlidskjalf/src/gates/Stats.tsx +268 -0
  129. package/apps/hlidskjalf/src/gates/Tasks.tsx +173 -0
  130. package/apps/hlidskjalf/src/gates/Trace.tsx +155 -0
  131. package/apps/hlidskjalf/src/gates/Well.tsx +154 -0
  132. package/apps/hlidskjalf/src/gates/Worktrees.tsx +48 -0
  133. package/apps/hlidskjalf/src/hooks/useTenantDef.ts +13 -0
  134. package/apps/hlidskjalf/src/main.tsx +31 -0
  135. package/apps/hlidskjalf/src/services/api.ts +404 -0
  136. package/apps/hlidskjalf/src/services/auth.ts +107 -0
  137. package/apps/hlidskjalf/src/services/stream.ts +35 -0
  138. package/apps/hlidskjalf/src/state/store.ts +648 -0
  139. package/apps/hlidskjalf/src/state/ui.ts +62 -0
  140. package/apps/hlidskjalf/src/styles/auth.css +485 -0
  141. package/apps/hlidskjalf/src/styles/components.css +1259 -0
  142. package/apps/hlidskjalf/src/styles/forge.css +115 -0
  143. package/apps/hlidskjalf/src/styles/global.css +167 -0
  144. package/apps/hlidskjalf/src/styles/menus.css +175 -0
  145. package/apps/hlidskjalf/src/styles/mobile.css +298 -0
  146. package/apps/hlidskjalf/src/styles/overlays.css +205 -0
  147. package/apps/hlidskjalf/src/styles/shell.css +641 -0
  148. package/apps/hlidskjalf/src/types.ts +251 -0
  149. package/apps/hlidskjalf/src/vite-env.d.ts +9 -0
  150. package/apps/hlidskjalf/static/landing.md +6 -0
  151. package/apps/hlidskjalf/tsconfig.json +25 -0
  152. package/apps/hlidskjalf/vite.config.ts +49 -0
  153. package/apps/hlidskjalf-mobile/App.tsx +93 -0
  154. package/apps/hlidskjalf-mobile/app.json +13 -0
  155. package/apps/hlidskjalf-mobile/index.ts +4 -0
  156. package/apps/hlidskjalf-mobile/package.json +35 -0
  157. package/apps/hlidskjalf-mobile/public/icon.svg +7 -0
  158. package/apps/hlidskjalf-mobile/tsconfig.json +4 -0
  159. package/apps/odrerir/.astro/content.d.ts +159 -0
  160. package/apps/odrerir/.astro/dev.json +24 -0
  161. package/apps/odrerir/.astro/settings.json +5 -0
  162. package/apps/odrerir/.astro/types.d.ts +2 -0
  163. package/apps/odrerir/astro.config.mjs +79 -0
  164. package/apps/odrerir/dist/apple-touch-icon.png +0 -0
  165. package/apps/odrerir/dist/favicon-192.png +0 -0
  166. package/apps/odrerir/dist/favicon-32.png +0 -0
  167. package/apps/odrerir/dist/favicon-512.png +0 -0
  168. package/apps/odrerir/dist/favicon.svg +53 -0
  169. package/apps/odrerir/dist/icon.svg +7 -0
  170. package/apps/odrerir/dist/index.html +837 -0
  171. package/apps/odrerir/dist/og-square.png +0 -0
  172. package/apps/odrerir/dist/og-story.png +0 -0
  173. package/apps/odrerir/dist/og.png +0 -0
  174. package/apps/odrerir/dist/safari-pinned-tab.svg +7 -0
  175. package/apps/odrerir/dist/site.webmanifest +16 -0
  176. package/apps/odrerir/dist/styles/cloth.css +112 -0
  177. package/apps/odrerir/dist/styles/livehall.css +278 -0
  178. package/apps/odrerir/electron/icon.png +0 -0
  179. package/apps/odrerir/electron/main.cjs +183 -0
  180. package/apps/odrerir/electron/ymir-odrerir.desktop.in +12 -0
  181. package/apps/odrerir/package-lock.json +4195 -0
  182. package/apps/odrerir/package.json +30 -0
  183. package/apps/odrerir/public/apple-touch-icon.png +0 -0
  184. package/apps/odrerir/public/favicon-192.png +0 -0
  185. package/apps/odrerir/public/favicon-32.png +0 -0
  186. package/apps/odrerir/public/favicon-512.png +0 -0
  187. package/apps/odrerir/public/favicon.svg +53 -0
  188. package/apps/odrerir/public/icon.svg +7 -0
  189. package/apps/odrerir/public/og-square.png +0 -0
  190. package/apps/odrerir/public/og-story.png +0 -0
  191. package/apps/odrerir/public/og.png +0 -0
  192. package/apps/odrerir/public/safari-pinned-tab.svg +7 -0
  193. package/apps/odrerir/public/site.webmanifest +16 -0
  194. package/apps/odrerir/scripts/extract-cloth.mjs +101 -0
  195. package/apps/odrerir/src/index.html +837 -0
  196. package/apps/odrerir/src/styles/cloth.css +112 -0
  197. package/apps/odrerir/src/styles/livehall.css +278 -0
  198. package/apps/sessrumnir/.github/ISSUE_TEMPLATE/bug_report.yml +99 -0
  199. package/apps/sessrumnir/.github/ISSUE_TEMPLATE/config.yml +8 -0
  200. package/apps/sessrumnir/.github/ISSUE_TEMPLATE/feature_request.yml +46 -0
  201. package/apps/sessrumnir/.github/PULL_REQUEST_TEMPLATE.md +56 -0
  202. package/apps/sessrumnir/.github/SUPPORT.md +18 -0
  203. package/apps/sessrumnir/.github/workflows/build.yml +133 -0
  204. package/apps/sessrumnir/AGENTS.md +540 -0
  205. package/apps/sessrumnir/CLA.md +63 -0
  206. package/apps/sessrumnir/CONTRIBUTING.md +233 -0
  207. package/apps/sessrumnir/LICENSE +200 -0
  208. package/apps/sessrumnir/NOTICE +33 -0
  209. package/apps/sessrumnir/README.md +329 -0
  210. package/apps/sessrumnir/bin/sessrumnir-web.sh +117 -0
  211. package/apps/sessrumnir/bin/sessrumnir.js +169 -0
  212. package/apps/sessrumnir/docs/CNAME +1 -0
  213. package/apps/sessrumnir/docs/favicon.ico +0 -0
  214. package/apps/sessrumnir/docs/icon-512.png +0 -0
  215. package/apps/sessrumnir/docs/index.html +441 -0
  216. package/apps/sessrumnir/docs/robots.txt +4 -0
  217. package/apps/sessrumnir/docs/screenshots/Screenshot_20260824_181929.png +0 -0
  218. package/apps/sessrumnir/docs/screenshots/Screenshot_20260824_182005.png +0 -0
  219. package/apps/sessrumnir/docs/screenshots/Screenshot_20260824_182020.png +0 -0
  220. package/apps/sessrumnir/docs/screenshots/Screenshot_20260824_182039.png +0 -0
  221. package/apps/sessrumnir/docs/screenshots/Screenshot_20260824_182117.png +0 -0
  222. package/apps/sessrumnir/docs/screenshots/Screenshot_20260824_182225.png +0 -0
  223. package/apps/sessrumnir/docs/screenshots/Screenshot_20260824_182248.png +0 -0
  224. package/apps/sessrumnir/docs/screenshots/Screenshot_20260824_182322.png +0 -0
  225. package/apps/sessrumnir/docs/screenshots/Screenshot_20260824_182340.png +0 -0
  226. package/apps/sessrumnir/docs/sitemap.xml +9 -0
  227. package/apps/sessrumnir/electron.vite.config.ts +49 -0
  228. package/apps/sessrumnir/eslint.config.mjs +75 -0
  229. package/apps/sessrumnir/i18next.config.ts +33 -0
  230. package/apps/sessrumnir/install.sh +144 -0
  231. package/apps/sessrumnir/out/main/index.js +10881 -0
  232. package/apps/sessrumnir/out/preload/index.js +722 -0
  233. package/apps/sessrumnir/out/renderer/assets/cormorant-cyrillic-400-normal-C8QS47vb.woff2 +0 -0
  234. package/apps/sessrumnir/out/renderer/assets/cormorant-cyrillic-400-normal-D3EsxgFc.woff +0 -0
  235. package/apps/sessrumnir/out/renderer/assets/cormorant-cyrillic-500-normal-B7dJQtg-.woff +0 -0
  236. package/apps/sessrumnir/out/renderer/assets/cormorant-cyrillic-500-normal-BLlg2W5x.woff2 +0 -0
  237. package/apps/sessrumnir/out/renderer/assets/cormorant-cyrillic-600-normal-BcIsDXzC.woff2 +0 -0
  238. package/apps/sessrumnir/out/renderer/assets/cormorant-cyrillic-600-normal-CBymlcNF.woff +0 -0
  239. package/apps/sessrumnir/out/renderer/assets/cormorant-cyrillic-ext-400-normal-Bgrpe4p1.woff +0 -0
  240. package/apps/sessrumnir/out/renderer/assets/cormorant-cyrillic-ext-400-normal-BlcaxZtM.woff2 +0 -0
  241. package/apps/sessrumnir/out/renderer/assets/cormorant-cyrillic-ext-500-normal-CdQuyvtc.woff +0 -0
  242. package/apps/sessrumnir/out/renderer/assets/cormorant-cyrillic-ext-500-normal-pZw22qtS.woff2 +0 -0
  243. package/apps/sessrumnir/out/renderer/assets/cormorant-cyrillic-ext-600-normal-Dr-wLyOm.woff +0 -0
  244. package/apps/sessrumnir/out/renderer/assets/cormorant-cyrillic-ext-600-normal-dLR2-a_v.woff2 +0 -0
  245. package/apps/sessrumnir/out/renderer/assets/cormorant-latin-400-normal-BGH8Vunh.woff2 +0 -0
  246. package/apps/sessrumnir/out/renderer/assets/cormorant-latin-400-normal-C3_-2Ua-.woff +0 -0
  247. package/apps/sessrumnir/out/renderer/assets/cormorant-latin-500-normal-Dj3SQ6fR.woff +0 -0
  248. package/apps/sessrumnir/out/renderer/assets/cormorant-latin-500-normal-EBdSCOD3.woff2 +0 -0
  249. package/apps/sessrumnir/out/renderer/assets/cormorant-latin-600-normal-B254Xisw.woff2 +0 -0
  250. package/apps/sessrumnir/out/renderer/assets/cormorant-latin-600-normal-DUW-TI76.woff +0 -0
  251. package/apps/sessrumnir/out/renderer/assets/cormorant-latin-ext-400-normal-DuQ88yz3.woff2 +0 -0
  252. package/apps/sessrumnir/out/renderer/assets/cormorant-latin-ext-400-normal-DuXFa1Dr.woff +0 -0
  253. package/apps/sessrumnir/out/renderer/assets/cormorant-latin-ext-500-normal-AH9qog1s.woff2 +0 -0
  254. package/apps/sessrumnir/out/renderer/assets/cormorant-latin-ext-500-normal-DAuUCO41.woff +0 -0
  255. package/apps/sessrumnir/out/renderer/assets/cormorant-latin-ext-600-normal-CD-dlDpD.woff +0 -0
  256. package/apps/sessrumnir/out/renderer/assets/cormorant-latin-ext-600-normal-D3T6BBXG.woff2 +0 -0
  257. package/apps/sessrumnir/out/renderer/assets/cormorant-vietnamese-400-normal-C-RiYxEf.woff2 +0 -0
  258. package/apps/sessrumnir/out/renderer/assets/cormorant-vietnamese-400-normal-DmUuA7Y2.woff +0 -0
  259. package/apps/sessrumnir/out/renderer/assets/cormorant-vietnamese-500-normal-DsPuwQHi.woff2 +0 -0
  260. package/apps/sessrumnir/out/renderer/assets/cormorant-vietnamese-500-normal-tGBW_mI7.woff +0 -0
  261. package/apps/sessrumnir/out/renderer/assets/cormorant-vietnamese-600-normal-Dar_cCT6.woff2 +0 -0
  262. package/apps/sessrumnir/out/renderer/assets/cormorant-vietnamese-600-normal-GIs8CF2x.woff +0 -0
  263. package/apps/sessrumnir/out/renderer/assets/ibm-plex-mono-cyrillic-300-normal-Ba-HN6uq.woff +0 -0
  264. package/apps/sessrumnir/out/renderer/assets/ibm-plex-mono-cyrillic-300-normal-ByB2hQUF.woff2 +0 -0
  265. package/apps/sessrumnir/out/renderer/assets/ibm-plex-mono-cyrillic-400-normal-BSMlKf0J.woff2 +0 -0
  266. package/apps/sessrumnir/out/renderer/assets/ibm-plex-mono-cyrillic-400-normal-CEL4l2ZJ.woff +0 -0
  267. package/apps/sessrumnir/out/renderer/assets/ibm-plex-mono-cyrillic-500-normal-Ael50iVv.woff +0 -0
  268. package/apps/sessrumnir/out/renderer/assets/ibm-plex-mono-cyrillic-500-normal-Bq9vWWag.woff2 +0 -0
  269. package/apps/sessrumnir/out/renderer/assets/ibm-plex-mono-cyrillic-ext-300-normal-B8O3uHbr.woff +0 -0
  270. package/apps/sessrumnir/out/renderer/assets/ibm-plex-mono-cyrillic-ext-300-normal-BbtBtHoB.woff2 +0 -0
  271. package/apps/sessrumnir/out/renderer/assets/ibm-plex-mono-cyrillic-ext-400-normal-DMdlQ8Kv.woff +0 -0
  272. package/apps/sessrumnir/out/renderer/assets/ibm-plex-mono-cyrillic-ext-400-normal-xuaO2J-f.woff2 +0 -0
  273. package/apps/sessrumnir/out/renderer/assets/ibm-plex-mono-cyrillic-ext-500-normal-BIfNGwUT.woff +0 -0
  274. package/apps/sessrumnir/out/renderer/assets/ibm-plex-mono-cyrillic-ext-500-normal-BqneJy0T.woff2 +0 -0
  275. package/apps/sessrumnir/out/renderer/assets/ibm-plex-mono-latin-300-normal-BPU54Tw3.woff +0 -0
  276. package/apps/sessrumnir/out/renderer/assets/ibm-plex-mono-latin-300-normal-BRS-C4KA.woff2 +0 -0
  277. package/apps/sessrumnir/out/renderer/assets/ibm-plex-mono-latin-400-normal-CvHOgSBP.woff +0 -0
  278. package/apps/sessrumnir/out/renderer/assets/ibm-plex-mono-latin-400-normal-DMJ8VG8y.woff2 +0 -0
  279. package/apps/sessrumnir/out/renderer/assets/ibm-plex-mono-latin-500-normal-CB9ihrfo.woff +0 -0
  280. package/apps/sessrumnir/out/renderer/assets/ibm-plex-mono-latin-500-normal-DSY6xOcd.woff2 +0 -0
  281. package/apps/sessrumnir/out/renderer/assets/ibm-plex-mono-latin-ext-300-normal-BEtQ2wm2.woff2 +0 -0
  282. package/apps/sessrumnir/out/renderer/assets/ibm-plex-mono-latin-ext-300-normal-CQbCaAQ4.woff +0 -0
  283. package/apps/sessrumnir/out/renderer/assets/ibm-plex-mono-latin-ext-400-normal-BmRBH3aV.woff2 +0 -0
  284. package/apps/sessrumnir/out/renderer/assets/ibm-plex-mono-latin-ext-400-normal-D3D2R8hC.woff +0 -0
  285. package/apps/sessrumnir/out/renderer/assets/ibm-plex-mono-latin-ext-500-normal-CAhNIIs5.woff2 +0 -0
  286. package/apps/sessrumnir/out/renderer/assets/ibm-plex-mono-latin-ext-500-normal-CZ70TYgx.woff +0 -0
  287. package/apps/sessrumnir/out/renderer/assets/ibm-plex-mono-vietnamese-300-normal-B6fSpYYV.woff +0 -0
  288. package/apps/sessrumnir/out/renderer/assets/ibm-plex-mono-vietnamese-300-normal-CpdbWO_2.woff2 +0 -0
  289. package/apps/sessrumnir/out/renderer/assets/ibm-plex-mono-vietnamese-400-normal-BulugwFq.woff2 +0 -0
  290. package/apps/sessrumnir/out/renderer/assets/ibm-plex-mono-vietnamese-400-normal-DDuiU_S-.woff +0 -0
  291. package/apps/sessrumnir/out/renderer/assets/ibm-plex-mono-vietnamese-500-normal-C8zxqsMH.woff +0 -0
  292. package/apps/sessrumnir/out/renderer/assets/ibm-plex-mono-vietnamese-500-normal-DZ4AoWbu.woff2 +0 -0
  293. package/apps/sessrumnir/out/renderer/assets/index-BxqsRq2x.css +4858 -0
  294. package/apps/sessrumnir/out/renderer/assets/index-TaD69rFk.js +99435 -0
  295. package/apps/sessrumnir/out/renderer/assets/inter-cyrillic-ext-wght-normal-BOeWTOD4.woff2 +0 -0
  296. package/apps/sessrumnir/out/renderer/assets/inter-cyrillic-wght-normal-DqGufNeO.woff2 +0 -0
  297. package/apps/sessrumnir/out/renderer/assets/inter-greek-ext-wght-normal-DlzME5K_.woff2 +0 -0
  298. package/apps/sessrumnir/out/renderer/assets/inter-greek-wght-normal-CkhJZR-_.woff2 +0 -0
  299. package/apps/sessrumnir/out/renderer/assets/inter-latin-ext-wght-normal-DO1Apj_S.woff2 +0 -0
  300. package/apps/sessrumnir/out/renderer/assets/inter-latin-wght-normal-Dx4kXJAl.woff2 +0 -0
  301. package/apps/sessrumnir/out/renderer/assets/inter-vietnamese-wght-normal-CBcvBZtf.woff2 +0 -0
  302. package/apps/sessrumnir/out/renderer/assets/jetbrains-mono-cyrillic-wght-normal-D73BlboJ.woff2 +0 -0
  303. package/apps/sessrumnir/out/renderer/assets/jetbrains-mono-greek-wght-normal-Bw9x6K1M.woff2 +0 -0
  304. package/apps/sessrumnir/out/renderer/assets/jetbrains-mono-latin-ext-wght-normal-DBQx-q_a.woff2 +0 -0
  305. package/apps/sessrumnir/out/renderer/assets/jetbrains-mono-latin-wght-normal-B9CIFXIH.woff2 +0 -0
  306. package/apps/sessrumnir/out/renderer/assets/jetbrains-mono-vietnamese-wght-normal-Bt-aOZkq.woff2 +0 -0
  307. package/apps/sessrumnir/out/renderer/assets/newsreader-latin-ext-wght-italic-DFS986qL.woff2 +0 -0
  308. package/apps/sessrumnir/out/renderer/assets/newsreader-latin-ext-wght-normal-C-3rgBeH.woff2 +0 -0
  309. package/apps/sessrumnir/out/renderer/assets/newsreader-latin-wght-italic-Bxi8ein9.woff2 +0 -0
  310. package/apps/sessrumnir/out/renderer/assets/newsreader-latin-wght-normal-CCVVNp6i.woff2 +0 -0
  311. package/apps/sessrumnir/out/renderer/assets/newsreader-vietnamese-wght-italic-xAqemagA.woff2 +0 -0
  312. package/apps/sessrumnir/out/renderer/assets/newsreader-vietnamese-wght-normal-Czsa-EzN.woff2 +0 -0
  313. package/apps/sessrumnir/out/renderer/assets/openmoji-color-9qo_2DLG.woff2 +0 -0
  314. package/apps/sessrumnir/out/renderer/assets/pi-logo-B6SVnhuS.svg +7 -0
  315. package/apps/sessrumnir/out/renderer/index.html +19 -0
  316. package/apps/sessrumnir/out/renderer/theme-boot.js +30 -0
  317. package/apps/sessrumnir/package-lock.json +10636 -0
  318. package/apps/sessrumnir/package.json +183 -0
  319. package/apps/sessrumnir/public/apple-touch-icon.png +0 -0
  320. package/apps/sessrumnir/public/icon.svg +7 -0
  321. package/apps/sessrumnir/resources/icons/generate_icons.py +38 -0
  322. package/apps/sessrumnir/resources/icons/icon-128.png +0 -0
  323. package/apps/sessrumnir/resources/icons/icon-16.png +0 -0
  324. package/apps/sessrumnir/resources/icons/icon-256.png +0 -0
  325. package/apps/sessrumnir/resources/icons/icon-32.png +0 -0
  326. package/apps/sessrumnir/resources/icons/icon-48.png +0 -0
  327. package/apps/sessrumnir/resources/icons/icon-512.png +0 -0
  328. package/apps/sessrumnir/resources/icons/icon-64.png +0 -0
  329. package/apps/sessrumnir/resources/icons/icon.icns +0 -0
  330. package/apps/sessrumnir/resources/icons/icon.ico +0 -0
  331. package/apps/sessrumnir/resources/icons/icon.png +0 -0
  332. package/apps/sessrumnir/resources/icons/icon.svg +7 -0
  333. package/apps/sessrumnir/resources/linux/after-install.sh +23 -0
  334. package/apps/sessrumnir/resources/linux/after-remove.sh +18 -0
  335. package/apps/sessrumnir/resources/locales/en/translation.json +1735 -0
  336. package/apps/sessrumnir/resources/permission-prompt-text.test.ts +111 -0
  337. package/apps/sessrumnir/resources/permission-prompt-text.ts +84 -0
  338. package/apps/sessrumnir/resources/permission-rules.test.ts +351 -0
  339. package/apps/sessrumnir/resources/permission-rules.ts +293 -0
  340. package/apps/sessrumnir/resources/pi-desktop-permissions.ts +62 -0
  341. package/apps/sessrumnir/resources/ymir-sessrumnir.desktop.in +12 -0
  342. package/apps/sessrumnir/scripts/check-semantic-colors.mjs +57 -0
  343. package/apps/sessrumnir/src/main/activity-stats.test.ts +240 -0
  344. package/apps/sessrumnir/src/main/activity-stats.ts +596 -0
  345. package/apps/sessrumnir/src/main/agent-detection.test.ts +25 -0
  346. package/apps/sessrumnir/src/main/agent-detection.ts +114 -0
  347. package/apps/sessrumnir/src/main/app-data-paths.test.ts +90 -0
  348. package/apps/sessrumnir/src/main/app-data-paths.ts +95 -0
  349. package/apps/sessrumnir/src/main/app-log.test.ts +114 -0
  350. package/apps/sessrumnir/src/main/app-log.ts +210 -0
  351. package/apps/sessrumnir/src/main/archived-sessions.test.ts +50 -0
  352. package/apps/sessrumnir/src/main/archived-sessions.ts +95 -0
  353. package/apps/sessrumnir/src/main/attachment-reader.test.ts +56 -0
  354. package/apps/sessrumnir/src/main/attachment-reader.ts +52 -0
  355. package/apps/sessrumnir/src/main/auto-tag.test.ts +38 -0
  356. package/apps/sessrumnir/src/main/auto-tag.ts +75 -0
  357. package/apps/sessrumnir/src/main/autostart-linux.test.ts +56 -0
  358. package/apps/sessrumnir/src/main/autostart-linux.ts +58 -0
  359. package/apps/sessrumnir/src/main/cmd-escape.test.ts +424 -0
  360. package/apps/sessrumnir/src/main/cmd-escape.ts +186 -0
  361. package/apps/sessrumnir/src/main/council-manager.test.ts +167 -0
  362. package/apps/sessrumnir/src/main/council-manager.ts +265 -0
  363. package/apps/sessrumnir/src/main/diagnostics-report.test.ts +140 -0
  364. package/apps/sessrumnir/src/main/diagnostics-report.ts +91 -0
  365. package/apps/sessrumnir/src/main/diagnostics.ts +111 -0
  366. package/apps/sessrumnir/src/main/editor-guard.test.ts +59 -0
  367. package/apps/sessrumnir/src/main/editor-guard.ts +50 -0
  368. package/apps/sessrumnir/src/main/extension-ui-ipc.test.ts +299 -0
  369. package/apps/sessrumnir/src/main/extension-ui-ipc.ts +117 -0
  370. package/apps/sessrumnir/src/main/file-service.test.ts +279 -0
  371. package/apps/sessrumnir/src/main/file-service.ts +652 -0
  372. package/apps/sessrumnir/src/main/fs-errors.ts +42 -0
  373. package/apps/sessrumnir/src/main/get-messages-trim.test.ts +77 -0
  374. package/apps/sessrumnir/src/main/get-messages-trim.ts +97 -0
  375. package/apps/sessrumnir/src/main/git-conveyor.test.ts +305 -0
  376. package/apps/sessrumnir/src/main/git-conveyor.ts +338 -0
  377. package/apps/sessrumnir/src/main/git-worktree.test.ts +101 -0
  378. package/apps/sessrumnir/src/main/git-worktree.ts +201 -0
  379. package/apps/sessrumnir/src/main/i18n.ts +31 -0
  380. package/apps/sessrumnir/src/main/index.ts +619 -0
  381. package/apps/sessrumnir/src/main/ipc/active-engine.ts +15 -0
  382. package/apps/sessrumnir/src/main/ipc/context.ts +58 -0
  383. package/apps/sessrumnir/src/main/ipc/council-handlers.ts +126 -0
  384. package/apps/sessrumnir/src/main/ipc/diagnostics-handlers.ts +8 -0
  385. package/apps/sessrumnir/src/main/ipc/file-handlers.ts +96 -0
  386. package/apps/sessrumnir/src/main/ipc/git-conveyor-handlers.ts +55 -0
  387. package/apps/sessrumnir/src/main/ipc/model-handlers.ts +33 -0
  388. package/apps/sessrumnir/src/main/ipc/models-config-handlers.ts +80 -0
  389. package/apps/sessrumnir/src/main/ipc/notes-handlers.ts +65 -0
  390. package/apps/sessrumnir/src/main/ipc/package-handlers.ts +330 -0
  391. package/apps/sessrumnir/src/main/ipc/permission-rules-handlers.ts +223 -0
  392. package/apps/sessrumnir/src/main/ipc/pi-handlers.ts +136 -0
  393. package/apps/sessrumnir/src/main/ipc/pi-start-options.ts +150 -0
  394. package/apps/sessrumnir/src/main/ipc/run-pi-cli.ts +53 -0
  395. package/apps/sessrumnir/src/main/ipc/session-handlers.ts +491 -0
  396. package/apps/sessrumnir/src/main/ipc/settings.ts +120 -0
  397. package/apps/sessrumnir/src/main/ipc/skills-mcp-handlers.ts +192 -0
  398. package/apps/sessrumnir/src/main/ipc/system-handlers.ts +131 -0
  399. package/apps/sessrumnir/src/main/ipc/tag-handlers.ts +69 -0
  400. package/apps/sessrumnir/src/main/ipc/terminal-handlers.ts +41 -0
  401. package/apps/sessrumnir/src/main/ipc/theme-handlers.ts +131 -0
  402. package/apps/sessrumnir/src/main/ipc/update-handlers.ts +61 -0
  403. package/apps/sessrumnir/src/main/ipc/validation.ts +45 -0
  404. package/apps/sessrumnir/src/main/ipc/workflow-handlers.ts +108 -0
  405. package/apps/sessrumnir/src/main/ipc/workspace-activity-wiring.ts +179 -0
  406. package/apps/sessrumnir/src/main/ipc/workspace-handlers.ts +133 -0
  407. package/apps/sessrumnir/src/main/ipc-handlers.ts +98 -0
  408. package/apps/sessrumnir/src/main/map-concurrent.test.ts +80 -0
  409. package/apps/sessrumnir/src/main/map-concurrent.ts +30 -0
  410. package/apps/sessrumnir/src/main/models-file.test.ts +114 -0
  411. package/apps/sessrumnir/src/main/models-file.ts +89 -0
  412. package/apps/sessrumnir/src/main/notes-manager.test.ts +64 -0
  413. package/apps/sessrumnir/src/main/notes-manager.ts +183 -0
  414. package/apps/sessrumnir/src/main/notify-decision.test.ts +39 -0
  415. package/apps/sessrumnir/src/main/notify-decision.ts +25 -0
  416. package/apps/sessrumnir/src/main/omp-fork-points.test.ts +119 -0
  417. package/apps/sessrumnir/src/main/omp-fork-points.ts +105 -0
  418. package/apps/sessrumnir/src/main/omp-plugin-list.test.ts +94 -0
  419. package/apps/sessrumnir/src/main/omp-plugin-list.ts +103 -0
  420. package/apps/sessrumnir/src/main/package-catalog.test.ts +157 -0
  421. package/apps/sessrumnir/src/main/package-catalog.ts +166 -0
  422. package/apps/sessrumnir/src/main/package-updates.test.ts +149 -0
  423. package/apps/sessrumnir/src/main/package-updates.ts +185 -0
  424. package/apps/sessrumnir/src/main/path-authorization.test.ts +64 -0
  425. package/apps/sessrumnir/src/main/path-authorization.ts +31 -0
  426. package/apps/sessrumnir/src/main/pi-binary-resolution.test.ts +582 -0
  427. package/apps/sessrumnir/src/main/pi-binary-resolution.ts +649 -0
  428. package/apps/sessrumnir/src/main/pi-event-router.test.ts +388 -0
  429. package/apps/sessrumnir/src/main/pi-event-router.ts +290 -0
  430. package/apps/sessrumnir/src/main/pi-paths.test.ts +134 -0
  431. package/apps/sessrumnir/src/main/pi-paths.ts +74 -0
  432. package/apps/sessrumnir/src/main/pi-rpc-manager.test.ts +467 -0
  433. package/apps/sessrumnir/src/main/pi-rpc-manager.ts +1319 -0
  434. package/apps/sessrumnir/src/main/process-tree.test.ts +94 -0
  435. package/apps/sessrumnir/src/main/renderer-origin.test.ts +33 -0
  436. package/apps/sessrumnir/src/main/renderer-origin.ts +35 -0
  437. package/apps/sessrumnir/src/main/session-lineage-reader.test.ts +317 -0
  438. package/apps/sessrumnir/src/main/session-lineage-reader.ts +111 -0
  439. package/apps/sessrumnir/src/main/session-metadata.test.ts +508 -0
  440. package/apps/sessrumnir/src/main/session-metadata.ts +526 -0
  441. package/apps/sessrumnir/src/main/session-name.test.ts +71 -0
  442. package/apps/sessrumnir/src/main/session-name.ts +69 -0
  443. package/apps/sessrumnir/src/main/session-paths.test.ts +110 -0
  444. package/apps/sessrumnir/src/main/session-paths.ts +88 -0
  445. package/apps/sessrumnir/src/main/session-tags.test.ts +61 -0
  446. package/apps/sessrumnir/src/main/session-tags.ts +253 -0
  447. package/apps/sessrumnir/src/main/session-trash.test.ts +66 -0
  448. package/apps/sessrumnir/src/main/session-trash.ts +65 -0
  449. package/apps/sessrumnir/src/main/settings-logic.ts +66 -0
  450. package/apps/sessrumnir/src/main/skills-discovery.test.ts +149 -0
  451. package/apps/sessrumnir/src/main/skills-discovery.ts +179 -0
  452. package/apps/sessrumnir/src/main/startup-launch.ts +63 -0
  453. package/apps/sessrumnir/src/main/terminal-service.ts +96 -0
  454. package/apps/sessrumnir/src/main/theme-store.test.ts +440 -0
  455. package/apps/sessrumnir/src/main/theme-store.ts +495 -0
  456. package/apps/sessrumnir/src/main/tray-decision.test.ts +46 -0
  457. package/apps/sessrumnir/src/main/tray-decision.ts +49 -0
  458. package/apps/sessrumnir/src/main/tray-manager.ts +220 -0
  459. package/apps/sessrumnir/src/main/tsconfig.json +20 -0
  460. package/apps/sessrumnir/src/main/window-background.test.ts +29 -0
  461. package/apps/sessrumnir/src/main/window-background.ts +16 -0
  462. package/apps/sessrumnir/src/main/workflow-discovery.test.ts +252 -0
  463. package/apps/sessrumnir/src/main/workflow-monitor.test.ts +371 -0
  464. package/apps/sessrumnir/src/main/workflow-monitor.ts +1020 -0
  465. package/apps/sessrumnir/src/main/workspace-activity.test.ts +227 -0
  466. package/apps/sessrumnir/src/main/workspace-activity.ts +215 -0
  467. package/apps/sessrumnir/src/main/workspace-manager.test.ts +680 -0
  468. package/apps/sessrumnir/src/main/workspace-manager.ts +1199 -0
  469. package/apps/sessrumnir/src/main/workspace-trust.test.ts +78 -0
  470. package/apps/sessrumnir/src/main/workspace-trust.ts +84 -0
  471. package/apps/sessrumnir/src/preload/index.ts +358 -0
  472. package/apps/sessrumnir/src/preload/tsconfig.json +20 -0
  473. package/apps/sessrumnir/src/renderer/index.html +18 -0
  474. package/apps/sessrumnir/src/renderer/public/theme-boot.js +30 -0
  475. package/apps/sessrumnir/src/renderer/src/app.tsx +194 -0
  476. package/apps/sessrumnir/src/renderer/src/assets/openmoji-color.woff2 +0 -0
  477. package/apps/sessrumnir/src/renderer/src/assets/pi-logo.svg +7 -0
  478. package/apps/sessrumnir/src/renderer/src/assets/ymir-mark.svg +55 -0
  479. package/apps/sessrumnir/src/renderer/src/assets.d.ts +7 -0
  480. package/apps/sessrumnir/src/renderer/src/components/chat-code-highlight.ts +239 -0
  481. package/apps/sessrumnir/src/renderer/src/components/chat-file-link.ts +140 -0
  482. package/apps/sessrumnir/src/renderer/src/components/chat-input.tsx +729 -0
  483. package/apps/sessrumnir/src/renderer/src/components/chat-panel-widths.test.ts +175 -0
  484. package/apps/sessrumnir/src/renderer/src/components/chat-panel-widths.ts +100 -0
  485. package/apps/sessrumnir/src/renderer/src/components/chat-panel.tsx +523 -0
  486. package/apps/sessrumnir/src/renderer/src/components/chat-project-picker.tsx +193 -0
  487. package/apps/sessrumnir/src/renderer/src/components/chat-search.tsx +251 -0
  488. package/apps/sessrumnir/src/renderer/src/components/code-editor-highlight.ts +85 -0
  489. package/apps/sessrumnir/src/renderer/src/components/code-editor-language.test.ts +19 -0
  490. package/apps/sessrumnir/src/renderer/src/components/code-editor-language.ts +88 -0
  491. package/apps/sessrumnir/src/renderer/src/components/code-editor.tsx +128 -0
  492. package/apps/sessrumnir/src/renderer/src/components/command-palette.tsx +315 -0
  493. package/apps/sessrumnir/src/renderer/src/components/command-results.tsx +80 -0
  494. package/apps/sessrumnir/src/renderer/src/components/composer-permission-menu.tsx +91 -0
  495. package/apps/sessrumnir/src/renderer/src/components/context-menu.tsx +457 -0
  496. package/apps/sessrumnir/src/renderer/src/components/copy-button.tsx +36 -0
  497. package/apps/sessrumnir/src/renderer/src/components/council-panels.tsx +193 -0
  498. package/apps/sessrumnir/src/renderer/src/components/custom-models-editor.tsx +330 -0
  499. package/apps/sessrumnir/src/renderer/src/components/diagnostics-panel.tsx +380 -0
  500. package/apps/sessrumnir/src/renderer/src/components/diff-viewer.tsx +318 -0
  501. package/apps/sessrumnir/src/renderer/src/components/ember-background.tsx +113 -0
  502. package/apps/sessrumnir/src/renderer/src/components/error-boundary.tsx +38 -0
  503. package/apps/sessrumnir/src/renderer/src/components/extension-ui-dialog-helpers.test.ts +43 -0
  504. package/apps/sessrumnir/src/renderer/src/components/extension-ui-dialog-helpers.ts +51 -0
  505. package/apps/sessrumnir/src/renderer/src/components/extension-ui-dialog.tsx +486 -0
  506. package/apps/sessrumnir/src/renderer/src/components/file-tree.tsx +784 -0
  507. package/apps/sessrumnir/src/renderer/src/components/git-conveyor-actions.tsx +246 -0
  508. package/apps/sessrumnir/src/renderer/src/components/home-screen.tsx +396 -0
  509. package/apps/sessrumnir/src/renderer/src/components/image-viewer.tsx +100 -0
  510. package/apps/sessrumnir/src/renderer/src/components/line-numbered-code.tsx +58 -0
  511. package/apps/sessrumnir/src/renderer/src/components/markdown-renderer.tsx +237 -0
  512. package/apps/sessrumnir/src/renderer/src/components/message-bubble.tsx +1006 -0
  513. package/apps/sessrumnir/src/renderer/src/components/mission-control.tsx +341 -0
  514. package/apps/sessrumnir/src/renderer/src/components/model-selector.tsx +219 -0
  515. package/apps/sessrumnir/src/renderer/src/components/note-picker.tsx +155 -0
  516. package/apps/sessrumnir/src/renderer/src/components/notes-panel.tsx +437 -0
  517. package/apps/sessrumnir/src/renderer/src/components/package-browser.tsx +604 -0
  518. package/apps/sessrumnir/src/renderer/src/components/permission-mode.test.ts +22 -0
  519. package/apps/sessrumnir/src/renderer/src/components/permission-mode.ts +49 -0
  520. package/apps/sessrumnir/src/renderer/src/components/permission-rules-editor-helpers.test.ts +44 -0
  521. package/apps/sessrumnir/src/renderer/src/components/permission-rules-editor-helpers.ts +35 -0
  522. package/apps/sessrumnir/src/renderer/src/components/permission-rules-editor.tsx +226 -0
  523. package/apps/sessrumnir/src/renderer/src/components/permission-selector.tsx +104 -0
  524. package/apps/sessrumnir/src/renderer/src/components/resize-handle.tsx +51 -0
  525. package/apps/sessrumnir/src/renderer/src/components/review-rail.tsx +194 -0
  526. package/apps/sessrumnir/src/renderer/src/components/session-menu-position.test.ts +22 -0
  527. package/apps/sessrumnir/src/renderer/src/components/session-menu-position.ts +34 -0
  528. package/apps/sessrumnir/src/renderer/src/components/session-panel.tsx +711 -0
  529. package/apps/sessrumnir/src/renderer/src/components/session-runtime-indicator.tsx +43 -0
  530. package/apps/sessrumnir/src/renderer/src/components/settings-panel.tsx +1360 -0
  531. package/apps/sessrumnir/src/renderer/src/components/sidebar-activity.test.ts +47 -0
  532. package/apps/sessrumnir/src/renderer/src/components/sidebar-activity.ts +76 -0
  533. package/apps/sessrumnir/src/renderer/src/components/sidebar-session-labels.test.ts +73 -0
  534. package/apps/sessrumnir/src/renderer/src/components/sidebar-session-labels.ts +47 -0
  535. package/apps/sessrumnir/src/renderer/src/components/sidebar.tsx +1045 -0
  536. package/apps/sessrumnir/src/renderer/src/components/skills-panel.tsx +159 -0
  537. package/apps/sessrumnir/src/renderer/src/components/stats-panel.tsx +368 -0
  538. package/apps/sessrumnir/src/renderer/src/components/status-bar.tsx +274 -0
  539. package/apps/sessrumnir/src/renderer/src/components/status-popover.tsx +481 -0
  540. package/apps/sessrumnir/src/renderer/src/components/streaming-bubble.tsx +128 -0
  541. package/apps/sessrumnir/src/renderer/src/components/subagent-progress.tsx +254 -0
  542. package/apps/sessrumnir/src/renderer/src/components/task-launcher.tsx +168 -0
  543. package/apps/sessrumnir/src/renderer/src/components/terminal.tsx +203 -0
  544. package/apps/sessrumnir/src/renderer/src/components/theme-editor-helpers.test.ts +35 -0
  545. package/apps/sessrumnir/src/renderer/src/components/theme-editor-helpers.ts +35 -0
  546. package/apps/sessrumnir/src/renderer/src/components/theme-editor.tsx +440 -0
  547. package/apps/sessrumnir/src/renderer/src/components/theme-gallery.tsx +373 -0
  548. package/apps/sessrumnir/src/renderer/src/components/thinking-level-selector.tsx +85 -0
  549. package/apps/sessrumnir/src/renderer/src/components/timeline.tsx +289 -0
  550. package/apps/sessrumnir/src/renderer/src/components/tool-call-icon.ts +32 -0
  551. package/apps/sessrumnir/src/renderer/src/components/workflow-navigator.tsx +901 -0
  552. package/apps/sessrumnir/src/renderer/src/components/workspace-tabs.tsx +241 -0
  553. package/apps/sessrumnir/src/renderer/src/global.d.ts +8 -0
  554. package/apps/sessrumnir/src/renderer/src/hooks/use-folder-drop.ts +101 -0
  555. package/apps/sessrumnir/src/renderer/src/hooks.test.ts +88 -0
  556. package/apps/sessrumnir/src/renderer/src/hooks.ts +574 -0
  557. package/apps/sessrumnir/src/renderer/src/i18n.test.ts +54 -0
  558. package/apps/sessrumnir/src/renderer/src/i18n.ts +89 -0
  559. package/apps/sessrumnir/src/renderer/src/index.css +425 -0
  560. package/apps/sessrumnir/src/renderer/src/main.tsx +42 -0
  561. package/apps/sessrumnir/src/renderer/src/message-grouping.test.ts +422 -0
  562. package/apps/sessrumnir/src/renderer/src/message-grouping.ts +410 -0
  563. package/apps/sessrumnir/src/renderer/src/message-parsing.test.ts +86 -0
  564. package/apps/sessrumnir/src/renderer/src/message-parsing.ts +166 -0
  565. package/apps/sessrumnir/src/renderer/src/store-external-prompts.test.ts +161 -0
  566. package/apps/sessrumnir/src/renderer/src/store-session-transitions.test.ts +2090 -0
  567. package/apps/sessrumnir/src/renderer/src/store-timeline.test.ts +52 -0
  568. package/apps/sessrumnir/src/renderer/src/store-turn-error.test.ts +136 -0
  569. package/apps/sessrumnir/src/renderer/src/store.ts +3726 -0
  570. package/apps/sessrumnir/src/renderer/src/subagent-detection.test.ts +55 -0
  571. package/apps/sessrumnir/src/renderer/src/theme/engine.test.ts +29 -0
  572. package/apps/sessrumnir/src/renderer/src/theme/engine.ts +20 -0
  573. package/apps/sessrumnir/src/renderer/src/themes/breeze-claudius.json +69 -0
  574. package/apps/sessrumnir/src/renderer/src/themes/breeze-dark.json +69 -0
  575. package/apps/sessrumnir/src/renderer/src/themes/breeze-light.json +69 -0
  576. package/apps/sessrumnir/src/renderer/src/themes/builtin-themes.test.ts +95 -0
  577. package/apps/sessrumnir/src/renderer/src/themes/dark.json +46 -0
  578. package/apps/sessrumnir/src/renderer/src/themes/fensalir.json +82 -0
  579. package/apps/sessrumnir/src/renderer/src/themes/gruvbox.json +69 -0
  580. package/apps/sessrumnir/src/renderer/src/themes/index.ts +60 -0
  581. package/apps/sessrumnir/src/renderer/src/themes/light.json +46 -0
  582. package/apps/sessrumnir/src/renderer/src/themes/nord.json +69 -0
  583. package/apps/sessrumnir/src/renderer/src/themes/sessrumnir.json +82 -0
  584. package/apps/sessrumnir/src/renderer/src/utils/debounced-buffer.test.ts +81 -0
  585. package/apps/sessrumnir/src/renderer/src/utils/debounced-buffer.ts +57 -0
  586. package/apps/sessrumnir/src/renderer/src/utils/format-relative-time.test.ts +62 -0
  587. package/apps/sessrumnir/src/renderer/src/utils/format-relative-time.ts +28 -0
  588. package/apps/sessrumnir/src/renderer/src/utils/heatmap-grid.test.ts +57 -0
  589. package/apps/sessrumnir/src/renderer/src/utils/heatmap-grid.ts +41 -0
  590. package/apps/sessrumnir/src/renderer/src/utils/ipc-error.test.ts +22 -0
  591. package/apps/sessrumnir/src/renderer/src/utils/ipc-error.ts +12 -0
  592. package/apps/sessrumnir/src/renderer/src/utils/model-search.test.ts +63 -0
  593. package/apps/sessrumnir/src/renderer/src/utils/model-search.ts +20 -0
  594. package/apps/sessrumnir/src/renderer/src/utils/planning-prompt.test.ts +31 -0
  595. package/apps/sessrumnir/src/renderer/src/utils/planning-prompt.ts +27 -0
  596. package/apps/sessrumnir/src/renderer/src/utils/preview-load-error.test.ts +15 -0
  597. package/apps/sessrumnir/src/renderer/src/utils/preview-load-error.ts +22 -0
  598. package/apps/sessrumnir/src/renderer/src/utils/process-status-label.test.ts +13 -0
  599. package/apps/sessrumnir/src/renderer/src/utils/process-status-label.ts +19 -0
  600. package/apps/sessrumnir/src/renderer/src/utils/quick-switcher.test.ts +68 -0
  601. package/apps/sessrumnir/src/renderer/src/utils/quick-switcher.ts +52 -0
  602. package/apps/sessrumnir/src/renderer/src/utils/rank-file-results.test.ts +46 -0
  603. package/apps/sessrumnir/src/renderer/src/utils/rank-file-results.ts +23 -0
  604. package/apps/sessrumnir/src/renderer/src/utils/relative-time.tsx +25 -0
  605. package/apps/sessrumnir/src/renderer/src/utils/session-title.test.ts +98 -0
  606. package/apps/sessrumnir/src/renderer/src/utils/session-title.ts +58 -0
  607. package/apps/sessrumnir/src/renderer/src/utils/stale-guard.test.ts +29 -0
  608. package/apps/sessrumnir/src/renderer/src/utils/stale-guard.ts +20 -0
  609. package/apps/sessrumnir/src/renderer/src/utils/theme.test.ts +174 -0
  610. package/apps/sessrumnir/src/renderer/src/utils/theme.ts +159 -0
  611. package/apps/sessrumnir/src/renderer/src/utils/workflow-runs.test.ts +130 -0
  612. package/apps/sessrumnir/src/renderer/src/utils/workflow-runs.ts +84 -0
  613. package/apps/sessrumnir/src/renderer/tsconfig.json +24 -0
  614. package/apps/sessrumnir/src/shared/agent-engine-label.test.ts +45 -0
  615. package/apps/sessrumnir/src/shared/agent-engine-label.ts +53 -0
  616. package/apps/sessrumnir/src/shared/app-settings.test.ts +25 -0
  617. package/apps/sessrumnir/src/shared/app-settings.ts +23 -0
  618. package/apps/sessrumnir/src/shared/bridge-http.ts +341 -0
  619. package/apps/sessrumnir/src/shared/bridge.ts +337 -0
  620. package/apps/sessrumnir/src/shared/council-config.test.ts +300 -0
  621. package/apps/sessrumnir/src/shared/council-config.ts +356 -0
  622. package/apps/sessrumnir/src/shared/default-settings.ts +39 -0
  623. package/apps/sessrumnir/src/shared/folder-drop.test.ts +118 -0
  624. package/apps/sessrumnir/src/shared/folder-drop.ts +74 -0
  625. package/apps/sessrumnir/src/shared/fork-point.test.ts +30 -0
  626. package/apps/sessrumnir/src/shared/fork-point.ts +24 -0
  627. package/apps/sessrumnir/src/shared/i18n/i18next.d.ts +10 -0
  628. package/apps/sessrumnir/src/shared/i18n/index.test.ts +51 -0
  629. package/apps/sessrumnir/src/shared/i18n/index.ts +50 -0
  630. package/apps/sessrumnir/src/shared/i18n/languages.ts +12 -0
  631. package/apps/sessrumnir/src/shared/i18n/locale-checks.ts +98 -0
  632. package/apps/sessrumnir/src/shared/i18n/locales.test.ts +92 -0
  633. package/apps/sessrumnir/src/shared/i18n/pseudo.test.ts +39 -0
  634. package/apps/sessrumnir/src/shared/i18n/pseudo.ts +44 -0
  635. package/apps/sessrumnir/src/shared/i18n/resolve.test.ts +43 -0
  636. package/apps/sessrumnir/src/shared/i18n/resolve.ts +41 -0
  637. package/apps/sessrumnir/src/shared/i18n/resources.ts +11 -0
  638. package/apps/sessrumnir/src/shared/ipc-contracts.ts +1539 -0
  639. package/apps/sessrumnir/src/shared/models-config.test.ts +124 -0
  640. package/apps/sessrumnir/src/shared/models-config.ts +167 -0
  641. package/apps/sessrumnir/src/shared/package-filter.ts +19 -0
  642. package/apps/sessrumnir/src/shared/package-spec.test.ts +51 -0
  643. package/apps/sessrumnir/src/shared/package-spec.ts +25 -0
  644. package/apps/sessrumnir/src/shared/path-compare.test.ts +38 -0
  645. package/apps/sessrumnir/src/shared/path-compare.ts +76 -0
  646. package/apps/sessrumnir/src/shared/permission-mode.ts +8 -0
  647. package/apps/sessrumnir/src/shared/pi-command.test.ts +165 -0
  648. package/apps/sessrumnir/src/shared/pi-command.ts +139 -0
  649. package/apps/sessrumnir/src/shared/product-name.ts +7 -0
  650. package/apps/sessrumnir/src/shared/session-lineage.test.ts +103 -0
  651. package/apps/sessrumnir/src/shared/session-lineage.ts +50 -0
  652. package/apps/sessrumnir/src/shared/session-preview.test.ts +93 -0
  653. package/apps/sessrumnir/src/shared/session-preview.ts +56 -0
  654. package/apps/sessrumnir/src/shared/sidebar-width.test.ts +58 -0
  655. package/apps/sessrumnir/src/shared/sidebar-width.ts +37 -0
  656. package/apps/sessrumnir/src/shared/theme/builtin-ids.ts +16 -0
  657. package/apps/sessrumnir/src/shared/theme/resolve.test.ts +66 -0
  658. package/apps/sessrumnir/src/shared/theme/resolve.ts +29 -0
  659. package/apps/sessrumnir/src/shared/theme/syntax-defaults.ts +24 -0
  660. package/apps/sessrumnir/src/shared/theme/theme-file.test.ts +88 -0
  661. package/apps/sessrumnir/src/shared/theme/theme-file.ts +132 -0
  662. package/apps/sessrumnir/src/shared/theme/tokens.test.ts +35 -0
  663. package/apps/sessrumnir/src/shared/theme/tokens.ts +86 -0
  664. package/apps/sessrumnir/src/shared/tsconfig.json +17 -0
  665. package/apps/sessrumnir/src/shared/untrusted-data.test.ts +32 -0
  666. package/apps/sessrumnir/src/shared/untrusted-data.ts +22 -0
  667. package/apps/sessrumnir/src/shared/version-compare.test.ts +33 -0
  668. package/apps/sessrumnir/src/shared/version-compare.ts +50 -0
  669. package/apps/sessrumnir/src/shared/workflow-control.ts +33 -0
  670. package/apps/sessrumnir/src/web-server.ts +315 -0
  671. package/apps/sessrumnir/tsconfig.json +26 -0
  672. package/apps/smidja/smidja_build.py +45 -0
  673. package/apps/smidja/smidja_build_review.py +76 -0
  674. package/apps/smidja/smidja_build_test.py +79 -0
  675. package/apps/smidja/smidja_document.py +76 -0
  676. package/apps/smidja/smidja_modules/agent_cc.py +15 -0
  677. package/apps/smidja/smidja_modules/agent_opencode.py +194 -0
  678. package/apps/smidja/smidja_modules/agent_pi.py +392 -0
  679. package/apps/smidja/smidja_modules/agents.py +993 -0
  680. package/apps/smidja/smidja_modules/changes.py +103 -0
  681. package/apps/smidja/smidja_modules/console.py +131 -0
  682. package/apps/smidja/smidja_modules/data_types.py +447 -0
  683. package/apps/smidja/smidja_modules/gates.py +108 -0
  684. package/apps/smidja/smidja_modules/git_helper.py +120 -0
  685. package/apps/smidja/smidja_modules/lm_local.py +144 -0
  686. package/apps/smidja/smidja_modules/permissions.py +194 -0
  687. package/apps/smidja/smidja_modules/prompts.py +21 -0
  688. package/apps/smidja/smidja_modules/quality.py +240 -0
  689. package/apps/smidja/smidja_modules/runner.py +155 -0
  690. package/apps/smidja/smidja_modules/session.py +50 -0
  691. package/apps/smidja/smidja_modules/stall_watch.py +149 -0
  692. package/apps/smidja/smidja_modules/tracer.py +271 -0
  693. package/apps/smidja/smidja_modules/utils.py +77 -0
  694. package/apps/smidja/smidja_orchestrate.py +162 -0
  695. package/apps/smidja/smidja_plan.py +45 -0
  696. package/apps/smidja/smidja_plan_build.py +55 -0
  697. package/apps/smidja/smidja_plan_build_test.py +86 -0
  698. package/apps/smidja/smidja_plan_build_test_quality.py +98 -0
  699. package/apps/smidja/smidja_prompt.py +44 -0
  700. package/apps/smidja/smidja_quality.py +49 -0
  701. package/apps/smidja/smidja_recon_iv.py +202 -0
  702. package/apps/smidja/smidja_scout.py +45 -0
  703. package/apps/smidja/smidja_simple_sdlc.py +183 -0
  704. package/apps/smidja/smidja_smidja_config/smidja.config.yaml +143 -0
  705. package/apps/smidja-factory/AGENTS.md +165 -0
  706. package/apps/smidja-factory/README.md +3 -0
  707. package/apps/smidja-factory/SKILL.md +18 -0
  708. package/apps/smidja-factory/apps/visualizer/.oxlintrc.json +18 -0
  709. package/apps/smidja-factory/apps/visualizer/bun.lock +269 -0
  710. package/apps/smidja-factory/apps/visualizer/desktop/icon.png +0 -0
  711. package/apps/smidja-factory/apps/visualizer/desktop/main.js +140 -0
  712. package/apps/smidja-factory/apps/visualizer/desktop/package-lock.json +871 -0
  713. package/apps/smidja-factory/apps/visualizer/desktop/package.json +18 -0
  714. package/apps/smidja-factory/apps/visualizer/desktop/preload.js +19 -0
  715. package/apps/smidja-factory/apps/visualizer/index.html +28 -0
  716. package/apps/smidja-factory/apps/visualizer/package.json +31 -0
  717. package/apps/smidja-factory/apps/visualizer/public/icon.svg +7 -0
  718. package/apps/smidja-factory/apps/visualizer/public/models/claude.png +0 -0
  719. package/apps/smidja-factory/apps/visualizer/public/models/gemini.png +0 -0
  720. package/apps/smidja-factory/apps/visualizer/public/models/kimi.png +0 -0
  721. package/apps/smidja-factory/apps/visualizer/public/models/openai.png +0 -0
  722. package/apps/smidja-factory/apps/visualizer/public/models/zai.png +0 -0
  723. package/apps/smidja-factory/apps/visualizer/server/chat.ts +425 -0
  724. package/apps/smidja-factory/apps/visualizer/server/db.ts +1050 -0
  725. package/apps/smidja-factory/apps/visualizer/server/index.ts +648 -0
  726. package/apps/smidja-factory/apps/visualizer/server/model-catalog.ts +90 -0
  727. package/apps/smidja-factory/apps/visualizer/server/roster-api.ts +75 -0
  728. package/apps/smidja-factory/apps/visualizer/server/roster_resolve.py +170 -0
  729. package/apps/smidja-factory/apps/visualizer/server/settings.ts +106 -0
  730. package/apps/smidja-factory/apps/visualizer/shared/types.ts +630 -0
  731. package/apps/smidja-factory/apps/visualizer/src/App.vue +379 -0
  732. package/apps/smidja-factory/apps/visualizer/src/components/ChatInput.vue +155 -0
  733. package/apps/smidja-factory/apps/visualizer/src/components/ChatThread.vue +219 -0
  734. package/apps/smidja-factory/apps/visualizer/src/components/ChatView.vue +335 -0
  735. package/apps/smidja-factory/apps/visualizer/src/components/DecisionsView.vue +177 -0
  736. package/apps/smidja-factory/apps/visualizer/src/components/DetailSection.vue +76 -0
  737. package/apps/smidja-factory/apps/visualizer/src/components/MemoryView.vue +397 -0
  738. package/apps/smidja-factory/apps/visualizer/src/components/PhaseDetail.vue +1354 -0
  739. package/apps/smidja-factory/apps/visualizer/src/components/PhaseDots.vue +55 -0
  740. package/apps/smidja-factory/apps/visualizer/src/components/SessionCard.vue +583 -0
  741. package/apps/smidja-factory/apps/visualizer/src/components/SessionMiniCard.vue +58 -0
  742. package/apps/smidja-factory/apps/visualizer/src/components/SessionPanel.vue +272 -0
  743. package/apps/smidja-factory/apps/visualizer/src/components/SessionStartForm.vue +152 -0
  744. package/apps/smidja-factory/apps/visualizer/src/components/SessionTrace.vue +1157 -0
  745. package/apps/smidja-factory/apps/visualizer/src/components/SessionsList.vue +96 -0
  746. package/apps/smidja-factory/apps/visualizer/src/components/SettingsView.vue +152 -0
  747. package/apps/smidja-factory/apps/visualizer/src/components/StatChip.vue +98 -0
  748. package/apps/smidja-factory/apps/visualizer/src/components/StatsView.vue +382 -0
  749. package/apps/smidja-factory/apps/visualizer/src/components/StatusChip.vue +73 -0
  750. package/apps/smidja-factory/apps/visualizer/src/components/ToolCallCard.vue +156 -0
  751. package/apps/smidja-factory/apps/visualizer/src/lib/api.ts +177 -0
  752. package/apps/smidja-factory/apps/visualizer/src/lib/chat-api.ts +95 -0
  753. package/apps/smidja-factory/apps/visualizer/src/lib/chat-store.ts +351 -0
  754. package/apps/smidja-factory/apps/visualizer/src/lib/events.ts +131 -0
  755. package/apps/smidja-factory/apps/visualizer/src/lib/format.ts +92 -0
  756. package/apps/smidja-factory/apps/visualizer/src/lib/highlight.ts +54 -0
  757. package/apps/smidja-factory/apps/visualizer/src/lib/markdown.ts +120 -0
  758. package/apps/smidja-factory/apps/visualizer/src/lib/models.ts +42 -0
  759. package/apps/smidja-factory/apps/visualizer/src/lib/router.ts +67 -0
  760. package/apps/smidja-factory/apps/visualizer/src/lib/types.ts +61 -0
  761. package/apps/smidja-factory/apps/visualizer/src/main.ts +13 -0
  762. package/apps/smidja-factory/apps/visualizer/src/style.css +592 -0
  763. package/apps/smidja-factory/apps/visualizer/tsconfig.json +30 -0
  764. package/apps/smidja-factory/apps/visualizer/vite.config.js +32 -0
  765. package/apps/smidja-factory/apps/visualizer/vite.config.ts +31 -0
  766. package/apps/smidja-factory/cookbooks/create_config.md +61 -0
  767. package/apps/smidja-factory/cookbooks/create_smidja.md +120 -0
  768. package/apps/smidja-factory/cookbooks/how_to_prompt_for_the_eng.md +110 -0
  769. package/apps/smidja-factory/cookbooks/install.md +56 -0
  770. package/apps/smidja-factory/cookbooks/run_smidja.md +122 -0
  771. package/apps/smidja-factory/cookbooks/smidja_overview.md +88 -0
  772. package/apps/smidja-factory/cookbooks/update_config.md +103 -0
  773. package/apps/smidja-factory/cookbooks/update_modules.md +100 -0
  774. package/apps/smidja-factory/cookbooks/update_smidja.md +90 -0
  775. package/apps/smidja-factory/docs/enhancement-plan.md +420 -0
  776. package/apps/smidja-factory/references/config.md +202 -0
  777. package/apps/smidja-factory/references/handoff.md +161 -0
  778. package/apps/smidja-factory/references/observability.md +166 -0
  779. package/apps/smidja-factory/scripts/install.py +113 -0
  780. package/apps/smidja-factory/scripts/make_config.py +35 -0
  781. package/apps/smidja-factory/scripts/make_smidja.py +119 -0
  782. package/apps/smidja-factory/skills/add-or-edit-ai-models/SKILL.md +151 -0
  783. package/apps/smidja-factory/skills/create-new-agent/SKILL.md +125 -0
  784. package/apps/smidja-factory/skills/create-new-teams/SKILL.md +151 -0
  785. package/apps/smidja-factory/skills/git-ops/README.md +25 -0
  786. package/apps/smidja-factory/skills/git-ops/SKILL.md +18 -0
  787. package/apps/smidja-factory/skills/git-ops/create_branch.sh +79 -0
  788. package/apps/smidja-factory/skills/git-ops/safe_commit.sh +82 -0
  789. package/apps/smidja-factory/skills/git-ops/sync_upstream.sh +94 -0
  790. package/apps/smidja-factory/skills/how-to-run-agent-teams/SKILL.md +117 -0
  791. package/apps/smidja-factory/skills/smidja-instructions/SKILL.md +168 -0
  792. package/apps/smidja-factory/skills/smidja-launcher/SKILL.md +155 -0
  793. package/apps/smidja-factory/skills/smidja-start/SKILL.md +266 -0
  794. package/apps/smidja-factory/skills/volundr/SKILL.md +232 -0
  795. package/apps/smidja-factory/skills/volundr/prompt/dispatch-prompt.md +91 -0
  796. package/apps/smidja-factory/skills/volundr/prompt/volundr-system.md +63 -0
  797. package/apps/smidja-factory/skills/volundr/scripts/volundr-observe.py +52 -0
  798. package/apps/smidja-factory/skills/volundr/scripts/volundr-recall.py +54 -0
  799. package/apps/smidja-factory/skills/volundr/scripts/volundr-status.py +51 -0
  800. package/apps/smidja-factory/skills/volundr/scripts/volundr-teach.py +47 -0
  801. package/apps/smidja-factory/templates/env.sample +28 -0
  802. package/apps/smidja-factory/templates/harness_engineering/subagents.ts +706 -0
  803. package/apps/smidja-factory/templates/harness_engineering/themeMap.ts +145 -0
  804. package/apps/smidja-factory/templates/justfile +89 -0
  805. package/apps/smidja-factory/templates/prompt_engineering/builder/system.md +13 -0
  806. package/apps/smidja-factory/templates/prompt_engineering/builder/user.md +34 -0
  807. package/apps/smidja-factory/templates/prompt_engineering/documenter/system.md +17 -0
  808. package/apps/smidja-factory/templates/prompt_engineering/documenter/user.md +48 -0
  809. package/apps/smidja-factory/templates/prompt_engineering/orchestrator/system.md +55 -0
  810. package/apps/smidja-factory/templates/prompt_engineering/orchestrator/user.md +39 -0
  811. package/apps/smidja-factory/templates/prompt_engineering/planner/system.md +21 -0
  812. package/apps/smidja-factory/templates/prompt_engineering/planner/user.md +45 -0
  813. package/apps/smidja-factory/templates/prompt_engineering/recon_orchestrator/system.md +51 -0
  814. package/apps/smidja-factory/templates/prompt_engineering/recon_orchestrator/user.md +26 -0
  815. package/apps/smidja-factory/templates/prompt_engineering/reviewer/system.md +16 -0
  816. package/apps/smidja-factory/templates/prompt_engineering/reviewer/user.md +44 -0
  817. package/apps/smidja-factory/templates/prompt_engineering/scout/system.md +32 -0
  818. package/apps/smidja-factory/templates/prompt_engineering/scout/user.md +34 -0
  819. package/apps/smidja-factory/templates/smidja/smidja_build.py +45 -0
  820. package/apps/smidja-factory/templates/smidja/smidja_build_review.py +76 -0
  821. package/apps/smidja-factory/templates/smidja/smidja_build_test.py +79 -0
  822. package/apps/smidja-factory/templates/smidja/smidja_document.py +76 -0
  823. package/apps/smidja-factory/templates/smidja/smidja_modules/agent_cc.py +15 -0
  824. package/apps/smidja-factory/templates/smidja/smidja_modules/agent_opencode.py +194 -0
  825. package/apps/smidja-factory/templates/smidja/smidja_modules/agent_pi.py +392 -0
  826. package/apps/smidja-factory/templates/smidja/smidja_modules/agents.py +993 -0
  827. package/apps/smidja-factory/templates/smidja/smidja_modules/changes.py +103 -0
  828. package/apps/smidja-factory/templates/smidja/smidja_modules/console.py +131 -0
  829. package/apps/smidja-factory/templates/smidja/smidja_modules/data_types.py +447 -0
  830. package/apps/smidja-factory/templates/smidja/smidja_modules/gates.py +108 -0
  831. package/apps/smidja-factory/templates/smidja/smidja_modules/git_helper.py +120 -0
  832. package/apps/smidja-factory/templates/smidja/smidja_modules/lm_local.py +144 -0
  833. package/apps/smidja-factory/templates/smidja/smidja_modules/permissions.py +194 -0
  834. package/apps/smidja-factory/templates/smidja/smidja_modules/prompts.py +21 -0
  835. package/apps/smidja-factory/templates/smidja/smidja_modules/quality.py +240 -0
  836. package/apps/smidja-factory/templates/smidja/smidja_modules/runner.py +155 -0
  837. package/apps/smidja-factory/templates/smidja/smidja_modules/session.py +50 -0
  838. package/apps/smidja-factory/templates/smidja/smidja_modules/stall_watch.py +149 -0
  839. package/apps/smidja-factory/templates/smidja/smidja_modules/tracer.py +271 -0
  840. package/apps/smidja-factory/templates/smidja/smidja_modules/utils.py +77 -0
  841. package/apps/smidja-factory/templates/smidja/smidja_orchestrate.py +162 -0
  842. package/apps/smidja-factory/templates/smidja/smidja_plan.py +45 -0
  843. package/apps/smidja-factory/templates/smidja/smidja_plan_build.py +55 -0
  844. package/apps/smidja-factory/templates/smidja/smidja_plan_build_test.py +86 -0
  845. package/apps/smidja-factory/templates/smidja/smidja_plan_build_test_quality.py +98 -0
  846. package/apps/smidja-factory/templates/smidja/smidja_prompt.py +44 -0
  847. package/apps/smidja-factory/templates/smidja/smidja_quality.py +49 -0
  848. package/apps/smidja-factory/templates/smidja/smidja_recon_iv.py +202 -0
  849. package/apps/smidja-factory/templates/smidja/smidja_scout.py +45 -0
  850. package/apps/smidja-factory/templates/smidja/smidja_simple_sdlc.py +183 -0
  851. package/apps/smidja-factory/templates/smidja.config.yaml +143 -0
  852. package/bin/app-build.sh +36 -0
  853. package/bin/electron-lib.sh +25 -0
  854. package/bin/sessrumnir.sh +1 -1
  855. package/bin/ymir-install.sh +7 -0
  856. package/install.sh +88 -0
  857. package/package.json +16 -7
  858. package/scripts/electron.sh +6 -2
  859. package/.agents/bus/messages.json +0 -1
  860. package/.agents/config/agents.machine.example.yaml +0 -16
  861. package/.pi/mcp.json +0 -16
  862. package/assets/reference/state/.branch-eligible-owner +0 -4
  863. package/assets/reference/state/.branch-mirror-cursor +0 -1
  864. package/assets/reference/state/.branch-outcomes-cursor +0 -1
  865. package/assets/reference/state/.branch-session +0 -1
  866. package/assets/reference/state/.count-default_wF_p5 +0 -1
  867. package/assets/reference/state/.count-default_wF_p6 +0 -1
  868. package/assets/reference/state/.count-default_wR_p2 +0 -1
  869. package/assets/reference/state/.count-default_wS_p2 +0 -1
  870. package/assets/reference/state/.count-firstmate_fm-server-knowledge +0 -1
  871. package/assets/reference/state/.hash-default_wF_p5 +0 -1
  872. package/assets/reference/state/.hash-default_wF_p6 +0 -1
  873. package/assets/reference/state/.hash-default_wR_p2 +0 -1
  874. package/assets/reference/state/.hash-default_wS_p2 +0 -1
  875. package/assets/reference/state/.hash-firstmate_fm-server-knowledge +0 -1
  876. package/assets/reference/state/.heartbeat-streak +0 -1
  877. package/assets/reference/state/.inactive-outcome-reconcile +0 -2
  878. package/assets/reference/state/.lock +0 -1
  879. package/assets/reference/state/.pi-branch-extension-loaded +0 -1
  880. package/assets/reference/state/.pi-turnend-extension-loaded +0 -2
  881. package/assets/reference/state/.pi-watch-extension-loaded +0 -2
  882. package/assets/reference/state/.seen-server-knowledge_turn-ended +0 -1
  883. package/assets/reference/state/.session-start-agents-baseline +0 -2
  884. package/assets/reference/state/.session-start-complete +0 -1
  885. package/assets/reference/state/.stale-default_wF_p5 +0 -1
  886. package/assets/reference/state/.stale-default_wR_p2 +0 -1
  887. package/assets/reference/state/.stale-firstmate_fm-server-knowledge +0 -1
  888. package/assets/reference/state/.stale-since-default_wF_p5 +0 -1
  889. package/assets/reference/state/.stale-since-default_wR_p2 +0 -1
  890. package/assets/reference/state/.stale-since-firstmate_fm-server-knowledge +0 -1
  891. package/assets/reference/state/.startup-network.delivered +0 -1
  892. package/assets/reference/state/.startup-network.report +0 -1
  893. package/assets/reference/state/.startup-network.status +0 -10
  894. package/assets/reference/state/.startup-network.timings +0 -6
  895. package/assets/reference/state/.trace-context-effective +0 -1
  896. package/assets/reference/state/.wake-queue +0 -0
  897. package/assets/reference/state/.wake-queue.seq +0 -1
  898. package/assets/reference/state/.wake-rows.consume.BcqXSq +0 -0
  899. package/assets/reference/state/.wake-rows.consume.NDZZei +0 -0
  900. package/assets/reference/state/.wake-rows.consume.VGkhYm +0 -0
  901. package/assets/reference/state/.wake-rows.consume.tR2rTy +0 -0
  902. package/assets/reference/state/.watch-arm-output.3581p6 +0 -0
  903. package/assets/reference/state/.watch-arm-output.3ciDNP +0 -0
  904. package/assets/reference/state/.watch-arm-output.SksR6k +0 -0
  905. package/assets/reference/state/.watch-cycle-exits.log +0 -22
  906. package/assets/reference/state/.watch-deliveries.log +0 -19
  907. package/assets/reference/state/.watch-triage.log +0 -38
  908. package/assets/reference/state/.watch.lock/fm-home +0 -1
  909. package/assets/reference/state/.watch.lock/pid +0 -1
  910. package/assets/reference/state/.watch.lock/pid-identity +0 -1
  911. package/assets/reference/state/.watch.lock/watcher-path +0 -1
  912. package/assets/reference/state/.watch.lock.owner.qqbxmS/fm-home +0 -1
  913. package/assets/reference/state/.watch.lock.owner.qqbxmS/pid +0 -1
  914. package/assets/reference/state/.watch.lock.owner.qqbxmS/pid-identity +0 -1
  915. package/assets/reference/state/.watch.lock.owner.qqbxmS/watcher-path +0 -1
  916. package/assets/reference/state/.watcher-down +0 -1
  917. package/assets/reference/state/branch-outcomes.jsonl +0 -1
  918. package/assets/reference/state/branch-session/2026-09-04T16-49-38-671Z_01a06d53-6b6f-7619-930d-e6dfd9a4a29e.jsonl +0 -211
  919. package/assets/reference/state/home-summary.json +0 -42
  920. package/assets/reference/state/server-knowledge.busy-gen +0 -1
  921. package/assets/reference/state/server-knowledge.busy-state +0 -1
  922. package/assets/reference/state/server-knowledge.herdr-presentation +0 -12
  923. package/assets/reference/state/server-knowledge.meta +0 -18
  924. package/assets/reference/state/server-knowledge.pi-ext.ts +0 -26
  925. package/assets/reference/state/server-knowledge.turn-ended +0 -0
  926. /package/{assets/reference/state/.last-check → apps/hlidskjalf/src/app/.gitkeep} +0 -0
  927. /package/{assets/reference/state/.last-heartbeat → apps/hlidskjalf/src/components/.gitkeep} +0 -0
  928. /package/{assets/reference/state/.last-watcher-beat → apps/hlidskjalf/src/services/.gitkeep} +0 -0
  929. /package/{assets/reference/state/.main-eligible-rows.tmp.p2HMKo → apps/smidja/smidja_modules/__init__.py} +0 -0
  930. /package/{assets/reference/state/.main-eligible-rows.tmp.Exxm82 → apps/smidja-factory/templates/smidja/smidja_modules/__init__.py} +0 -0
@@ -0,0 +1,1360 @@
1
+ import { useAppStore } from '../store'
2
+ import { DEFAULT_AGENT_ENGINE_NAME, agentEngineName } from '../../../shared/agent-engine-label'
3
+ import { useState, useEffect, useRef, useCallback } from 'react'
4
+ import { clsx } from 'clsx'
5
+ import { useTranslation } from 'react-i18next'
6
+ import { languagePickerOptions, loadI18nEnvironment, loadedI18nEnvironment } from '../i18n'
7
+ import { SOURCE_LANGUAGE } from '../../../shared/i18n/languages'
8
+ import type {
9
+ AgentDetectionOptions,
10
+ AgentEngine,
11
+ AgentInstallation,
12
+ AppSettings,
13
+ PermissionMode,
14
+ CouncilConfig,
15
+ PermissionRule,
16
+ PermissionRulesScope,
17
+ PermissionRulesWorkspaceStatus,
18
+ I18nEnvironment,
19
+ } from '../../../shared/ipc-contracts'
20
+ import type { ThemeFile } from '../../../shared/theme/theme-file'
21
+ import { Settings, Save, RotateCcw, FolderOpen, RefreshCw, Check, ChevronDown } from 'lucide-react'
22
+ import { DEFAULT_SETTINGS } from '../../../shared/default-settings'
23
+ import { PermissionSelector } from './permission-selector'
24
+ import { PermissionRulesEditor } from './permission-rules-editor'
25
+ import { validateRuleList, shouldPersistScope } from './permission-rules-editor-helpers'
26
+ import {
27
+ SYSTEM_THEME_ID,
28
+ applyTheme,
29
+ applyThemeSettings,
30
+ getRegisteredThemes,
31
+ registerThemes,
32
+ resolveSystemThemeSlot,
33
+ resolveThemeId,
34
+ setUserThemes,
35
+ type ThemeKind,
36
+ } from '../utils/theme'
37
+ import { BUILTIN_THEME_IDS, themeDisplayName } from '../themes'
38
+ import { CustomModelsEditor } from './custom-models-editor'
39
+ import { ThemeEditor } from './theme-editor'
40
+ import { ThemeGallery } from './theme-gallery'
41
+ import type { UserThemeRecord } from '../../../shared/ipc-contracts'
42
+ import {
43
+ MIN_TIMEOUT_SECONDS as COUNCIL_MIN_TIMEOUT,
44
+ MAX_TIMEOUT_SECONDS as COUNCIL_MAX_TIMEOUT,
45
+ clampTimeoutSeconds as clampCouncilTimeout,
46
+ councilAgentLabel,
47
+ } from '../../../shared/council-config'
48
+
49
+ // Empty `match` from the input means "no pattern" and must not be persisted
50
+ // as `""` — the main-process validator rejects unknown/empty-string quirks
51
+ // and downstream matching treats a missing key as "match anything".
52
+ function normalizedRules(rules: PermissionRule[]): PermissionRule[] {
53
+ return rules.map((rule) => {
54
+ const tool = rule.tool.trim()
55
+ const match = rule.match?.trim()
56
+ return match ? { action: rule.action, tool, match } : { action: rule.action, tool }
57
+ })
58
+ }
59
+
60
+ interface ScopeRulesState {
61
+ rules: PermissionRule[]
62
+ loaded: boolean
63
+ loadError: string | null
64
+ exists: boolean
65
+ }
66
+
67
+ const EMPTY_SCOPE_RULES: ScopeRulesState = { rules: [], loaded: false, loadError: null, exists: false }
68
+
69
+ export function SettingsPanel(): React.JSX.Element {
70
+ const { t, i18n } = useTranslation()
71
+ // A user who picks a language by mistake can still find this row.
72
+ const languageLabel = i18n.language === SOURCE_LANGUAGE
73
+ ? t('settings.language.label')
74
+ : t('settings.language.labelWithEnglish', {
75
+ label: t('settings.language.label'),
76
+ english: t('settings.language.label', { lng: SOURCE_LANGUAGE }),
77
+ })
78
+ const settings = useAppStore((state) => state.settings)
79
+ const loadSettings = useAppStore((state) => state.loadSettings)
80
+ const setSettingsDraft = useAppStore((state) => state.setSettingsDraft)
81
+ const clearSettingsDraft = useAppStore((state) => state.clearSettingsDraft)
82
+ const activeWorkspace = useAppStore((state) => state.activeWorkspace)
83
+
84
+ // Snapshot the unsaved draft once, for seeding initial local state. This is
85
+ // what makes edits survive leaving/returning to Settings without saving.
86
+ const draft0 = useAppStore.getState().settingsDraft
87
+
88
+ const initialPiPath = draft0.piExecutablePath ?? settings?.piExecutablePath ?? DEFAULT_SETTINGS.piExecutablePath
89
+ const initialPiEngine = draft0.piEngine ?? settings?.piEngine ?? DEFAULT_SETTINGS.piEngine
90
+ const [piPath, setPiPath] = useState(initialPiPath)
91
+ const [piEngine, setPiEngine] = useState<AgentEngine>(initialPiEngine)
92
+ // The setting above may be 'auto'; this is the engine that actually resolved,
93
+ // which is what any sentence naming the running agent has to say.
94
+ const runningEngineLabel = useAppStore((state) => agentEngineName(state.piEngine) ?? DEFAULT_AGENT_ENGINE_NAME)
95
+ const [customAgentPathMode, setCustomAgentPathMode] = useState(() => {
96
+ const normalized = initialPiPath.trim().toLowerCase()
97
+ return Boolean(initialPiPath.trim()) && normalized !== 'pi' && normalized !== 'omp'
98
+ })
99
+ const [detectedAgentInstalls, setDetectedAgentInstalls] = useState<AgentInstallation[]>([])
100
+ const [scanningAgentInstalls, setScanningAgentInstalls] = useState(false)
101
+ const [theme, setTheme] = useState(draft0.theme ?? settings?.theme ?? DEFAULT_SETTINGS.theme)
102
+ const [systemLightTheme, setSystemLightTheme] = useState(
103
+ draft0.systemLightTheme ?? settings?.systemLightTheme ?? DEFAULT_SETTINGS.systemLightTheme,
104
+ )
105
+ const [systemDarkTheme, setSystemDarkTheme] = useState(
106
+ draft0.systemDarkTheme ?? settings?.systemDarkTheme ?? DEFAULT_SETTINGS.systemDarkTheme,
107
+ )
108
+ const [themeActionError, setThemeActionError] = useState<string | null>(null)
109
+ const [themeEditorState, setThemeEditorState] = useState<{
110
+ baseTheme: ThemeFile
111
+ baseId: string
112
+ isUserTheme: boolean
113
+ } | null>(null)
114
+ const [installUrl, setInstallUrl] = useState('')
115
+ const [galleryOpen, setGalleryOpen] = useState(false)
116
+ const [fontSize, setFontSize] = useState(draft0.fontSize ?? settings?.fontSize ?? DEFAULT_SETTINGS.fontSize)
117
+ const [terminalFontSize, setTerminalFontSize] = useState(draft0.terminalFontSize ?? settings?.terminalFontSize ?? DEFAULT_SETTINGS.terminalFontSize)
118
+ const [codeEditorFontSize, setCodeEditorFontSize] = useState(draft0.codeEditorFontSize ?? settings?.codeEditorFontSize ?? DEFAULT_SETTINGS.codeEditorFontSize)
119
+ const [showThinking, setShowThinking] = useState(draft0.showThinking ?? settings?.showThinking ?? DEFAULT_SETTINGS.showThinking)
120
+ const [autoScroll, setAutoScroll] = useState(draft0.autoScroll ?? settings?.autoScroll ?? DEFAULT_SETTINGS.autoScroll)
121
+ const [desktopNotifications, setDesktopNotifications] = useState(draft0.desktopNotifications ?? settings?.desktopNotifications ?? DEFAULT_SETTINGS.desktopNotifications)
122
+ const [resumeLastSession, setResumeLastSession] = useState(draft0.resumeLastSession ?? settings?.resumeLastSession ?? DEFAULT_SETTINGS.resumeLastSession)
123
+ const [openToHomeOnLaunch, setOpenToHomeOnLaunch] = useState(draft0.openToHomeOnLaunch ?? settings?.openToHomeOnLaunch ?? DEFAULT_SETTINGS.openToHomeOnLaunch)
124
+ const [runOnStartup, setRunOnStartup] = useState(draft0.runOnStartup ?? settings?.runOnStartup ?? DEFAULT_SETTINGS.runOnStartup)
125
+ const [minimizeToTrayOnClose, setMinimizeToTrayOnClose] = useState(draft0.minimizeToTrayOnClose ?? settings?.minimizeToTrayOnClose ?? DEFAULT_SETTINGS.minimizeToTrayOnClose)
126
+ const [permissionMode, setPermissionMode] = useState<PermissionMode>(
127
+ draft0.permissionMode ?? settings?.permissionMode ?? DEFAULT_SETTINGS.permissionMode,
128
+ )
129
+ const [language, setLanguage] = useState(draft0.language ?? settings?.language ?? DEFAULT_SETTINGS.language)
130
+ const [rulesScope, setRulesScope] = useState<PermissionRulesScope>('global')
131
+ const [scopeRules, setScopeRules] = useState<Record<PermissionRulesScope, ScopeRulesState>>({
132
+ global: EMPTY_SCOPE_RULES,
133
+ workspace: EMPTY_SCOPE_RULES,
134
+ })
135
+ const [rulesActionError, setRulesActionError] = useState<string | null>(null)
136
+ const [workspaceRulesStatus, setWorkspaceRulesStatus] = useState<PermissionRulesWorkspaceStatus | null>(null)
137
+ const [saved, setSaved] = useState(false)
138
+
139
+ const [showCouncilWarning, setShowCouncilWarning] = useState(false)
140
+ const [detectedAgents, setDetectedAgents] = useState<Record<'pi' | 'claude' | 'codex', boolean>>({
141
+ pi: false,
142
+ claude: false,
143
+ codex: false,
144
+ })
145
+ // Free-text draft for the timeout field so the user can clear it and type a
146
+ // new value; it is clamped and persisted only on blur / Enter (not per keystroke).
147
+ const [timeoutDraft, setTimeoutDraft] = useState('')
148
+ const agentScanToken = useRef(0)
149
+
150
+ const scanAgentInstallations = useCallback(async (options?: AgentDetectionOptions): Promise<void> => {
151
+ const token = ++agentScanToken.current
152
+ setScanningAgentInstalls(true)
153
+ try {
154
+ const result = await window.piDesktop.pi.detectInstallations(options)
155
+ if (token === agentScanToken.current) setDetectedAgentInstalls(result.installations)
156
+ } catch {
157
+ if (token === agentScanToken.current) setDetectedAgentInstalls([])
158
+ } finally {
159
+ if (token === agentScanToken.current) setScanningAgentInstalls(false)
160
+ }
161
+ }, [])
162
+
163
+ useEffect(() => () => {
164
+ agentScanToken.current++
165
+ }, [])
166
+
167
+ // Detect available agent engines on mount. Cached results are fine here —
168
+ // only the explicit Rescan below needs a fresh look at the disk.
169
+ useEffect(() => {
170
+ void scanAgentInstallations()
171
+ }, [scanAgentInstallations])
172
+
173
+ useEffect(() => {
174
+ const installation = detectedAgentInstalls.find((candidate) => candidate.path === piPath)
175
+ if (installation && installation.kind === piEngine) setCustomAgentPathMode(false)
176
+ }, [detectedAgentInstalls, piEngine, piPath])
177
+
178
+ // Detect available council agents on mount
179
+ useEffect(() => {
180
+ let cancelled = false
181
+ void window.piDesktop.council.detect().then((result) => {
182
+ if (cancelled) return
183
+ const next: Record<'pi' | 'claude' | 'codex', boolean> = { pi: false, claude: false, codex: false }
184
+ for (const agent of result.agents) {
185
+ next[agent.id] = agent.found
186
+ }
187
+ setDetectedAgents(next)
188
+ })
189
+ return () => {
190
+ cancelled = true
191
+ }
192
+ }, [])
193
+
194
+ // Load permission rules for one scope. The store draft for that scope wins
195
+ // over the saved file so unsaved edits survive view switches. The draft is
196
+ // read at resolve time (inside the functional update), not before the
197
+ // await, so an edit made during the round-trip isn't visually reverted by
198
+ // a draft snapshot that predates it.
199
+ const loadRulesScope = useCallback(async (scope: PermissionRulesScope): Promise<void> => {
200
+ const saved = await window.piDesktop.permissionRules.get(scope)
201
+ setScopeRules((prev) => {
202
+ const draft = useAppStore.getState().permissionRulesDrafts[scope]
203
+ return {
204
+ ...prev,
205
+ [scope]: saved.ok
206
+ ? { rules: draft ?? saved.rules, loaded: true, loadError: null, exists: saved.exists }
207
+ : // Corrupt file: only a pre-existing user draft keeps Save enabled —
208
+ // see shouldPersistScope; an unrelated Save must not clobber it.
209
+ { rules: draft ?? [], loaded: draft !== null, loadError: saved.error, exists: true },
210
+ }
211
+ })
212
+ }, [])
213
+
214
+ const loadWorkspaceRulesStatus = useCallback(async (): Promise<void> => {
215
+ const status = await window.piDesktop.permissionRules.workspaceStatus()
216
+ setWorkspaceRulesStatus(status)
217
+ }, [])
218
+
219
+ const handleSetWorkspaceTrust = useCallback(async (trusted: boolean): Promise<void> => {
220
+ const status = await window.piDesktop.permissionRules.setWorkspaceTrust(trusted)
221
+ setWorkspaceRulesStatus(status)
222
+ }, [])
223
+
224
+ useEffect(() => {
225
+ void loadRulesScope('global')
226
+ void loadRulesScope('workspace')
227
+ void loadWorkspaceRulesStatus()
228
+ }, [loadRulesScope, loadWorkspaceRulesStatus])
229
+
230
+ // The workspace-scope rules are keyed by scope, not by workspace, so if the
231
+ // active workspace changes while this panel stays mounted (e.g. switching
232
+ // workspaces from the sidebar without leaving Settings), the previous
233
+ // workspace's loaded/exists state would otherwise stick around and could
234
+ // pass shouldPersistScope on an unrelated Save, writing it into the new
235
+ // workspace's file. Reset and re-fetch whenever the path changes. Skipped
236
+ // on the initial mount — the load-on-mount effect above already covers it.
237
+ const activeWorkspacePathRef = useRef(activeWorkspace?.path ?? null)
238
+ useEffect(() => {
239
+ const path = activeWorkspace?.path ?? null
240
+ if (activeWorkspacePathRef.current === path) return
241
+ activeWorkspacePathRef.current = path
242
+ setScopeRules((prev) => ({ ...prev, workspace: EMPTY_SCOPE_RULES }))
243
+ void loadRulesScope('workspace')
244
+ void loadWorkspaceRulesStatus()
245
+ }, [activeWorkspace?.path, loadRulesScope, loadWorkspaceRulesStatus])
246
+
247
+ // Re-read a scope's file when the user switches to its tab, so manual edits
248
+ // to the file on disk show up — but not if there's an unsaved draft for it.
249
+ const handleRulesScopeChange = (scope: PermissionRulesScope): void => {
250
+ setRulesScope(scope)
251
+ setRulesActionError(null)
252
+ if (useAppStore.getState().permissionRulesDrafts[scope] === null) void loadRulesScope(scope)
253
+ }
254
+
255
+ // Keep the timeout draft in sync with the persisted value (e.g. after a save
256
+ // clamps it, or when settings first load).
257
+ const councilTimeout = settings?.council?.timeoutSeconds
258
+ useEffect(() => {
259
+ if (councilTimeout !== undefined) setTimeoutDraft(String(councilTimeout))
260
+ }, [councilTimeout])
261
+
262
+ // Merge a council patch into the current config and persist via the store mechanism
263
+ const saveCouncil = async (patch: Partial<CouncilConfig>): Promise<void> => {
264
+ if (!settings) return
265
+ const nextCouncil: CouncilConfig = { ...settings.council, ...patch }
266
+ await window.piDesktop.settings.save({ council: nextCouncil })
267
+ await loadSettings()
268
+ }
269
+
270
+ // Persist and apply a setting immediately, for toggles with an OS-level side
271
+ // effect (tray behavior, login item). These must take effect the instant they
272
+ // are flipped — staging them behind the Save button makes a toggle look "on"
273
+ // while the behavior is still off, which is surprising and easy to miss.
274
+ const applyImmediate = async (patch: Partial<AppSettings>): Promise<void> => {
275
+ await window.piDesktop.settings.save(patch)
276
+ await loadSettings()
277
+ }
278
+
279
+ // Populate the form once, when settings first load. We deliberately do NOT
280
+ // re-sync on every settings change: the UI font previews live and the
281
+ // terminal/editor sizes are staged in store state, so re-syncing would
282
+ // clobber other unsaved edits. Save/Reset set local state directly, so the
283
+ // form stays correct without a re-sync.
284
+ const didInitRef = useRef(false)
285
+ useEffect(() => {
286
+ if (!settings || didInitRef.current) return
287
+ didInitRef.current = true
288
+ const store = useAppStore.getState()
289
+ const draft = store.settingsDraft
290
+ const nextPiPath = draft.piExecutablePath ?? settings.piExecutablePath
291
+ const nextPiEngine = draft.piEngine ?? settings.piEngine
292
+ setPiPath(nextPiPath)
293
+ setPiEngine(nextPiEngine)
294
+ const normalizedPiPath = nextPiPath.trim().toLowerCase()
295
+ setCustomAgentPathMode(Boolean(nextPiPath.trim()) && normalizedPiPath !== 'pi' && normalizedPiPath !== 'omp')
296
+ setTheme(draft.theme ?? settings.theme)
297
+ setSystemLightTheme(draft.systemLightTheme ?? settings.systemLightTheme)
298
+ setSystemDarkTheme(draft.systemDarkTheme ?? settings.systemDarkTheme)
299
+ setFontSize(draft.fontSize ?? settings.fontSize)
300
+ setTerminalFontSize(draft.terminalFontSize ?? settings.terminalFontSize)
301
+ setCodeEditorFontSize(draft.codeEditorFontSize ?? settings.codeEditorFontSize)
302
+ setShowThinking(draft.showThinking ?? settings.showThinking)
303
+ setAutoScroll(draft.autoScroll ?? settings.autoScroll)
304
+ setDesktopNotifications(draft.desktopNotifications ?? settings.desktopNotifications)
305
+ setResumeLastSession(draft.resumeLastSession ?? settings.resumeLastSession)
306
+ setOpenToHomeOnLaunch(draft.openToHomeOnLaunch ?? settings.openToHomeOnLaunch)
307
+ setRunOnStartup(draft.runOnStartup ?? settings.runOnStartup)
308
+ setMinimizeToTrayOnClose(draft.minimizeToTrayOnClose ?? settings.minimizeToTrayOnClose)
309
+ setPermissionMode(draft.permissionMode ?? settings.permissionMode)
310
+ setLanguage(draft.language ?? settings.language)
311
+ }, [settings])
312
+
313
+ const setAgentPath = (path: string, custom = true): void => {
314
+ setPiPath(path)
315
+ setCustomAgentPathMode(custom)
316
+ setSettingsDraft({ piExecutablePath: path })
317
+ }
318
+
319
+ const setAgentEngine = (engine: AgentEngine): void => {
320
+ setPiEngine(engine)
321
+ setSettingsDraft({ piEngine: engine })
322
+ }
323
+
324
+ const handleAgentSelection = (value: string): void => {
325
+ if (value === '__auto__') {
326
+ setAgentPath('pi', false)
327
+ setAgentEngine('auto')
328
+ return
329
+ }
330
+ if (value === '__custom__') {
331
+ if (!customAgentPathMode) setAgentPath('', true)
332
+ return
333
+ }
334
+ const installation = detectedAgentInstalls.find((candidate) => candidate.path === value)
335
+ setAgentPath(value, false)
336
+ setAgentEngine(installation?.kind ?? (value.toLowerCase() === 'omp' ? 'omp' : 'auto'))
337
+ }
338
+
339
+ const handleSelectPath = async (): Promise<void> => {
340
+ const path = await window.piDesktop.system.openDialog({ title: t('settings.agentInstallation.selectDialogTitle'), mode: 'either' })
341
+ if (path) {
342
+ const installation = detectedAgentInstalls.find((candidate) => candidate.path === path)
343
+ setAgentPath(path)
344
+ if (installation) setAgentEngine(installation.kind)
345
+ }
346
+ }
347
+
348
+ const autoAgentSelection = !customAgentPathMode && (piPath.trim().toLowerCase() === 'pi' || piPath.trim() === '')
349
+ const detectedAgentSelection = !customAgentPathMode && detectedAgentInstalls.some((installation) => installation.path === piPath)
350
+ const agentSelection = customAgentPathMode
351
+ ? '__custom__'
352
+ : autoAgentSelection
353
+ ? '__auto__'
354
+ : detectedAgentSelection
355
+ ? piPath
356
+ : piPath.trim().toLowerCase() === 'omp' && !detectedAgentInstalls.some((installation) => installation.kind === 'omp')
357
+ ? 'omp'
358
+ : '__custom__'
359
+ const showCustomAgentPath = customAgentPathMode || agentSelection === 'omp'
360
+
361
+ const isBuiltinTheme = (themeId: string): boolean => (BUILTIN_THEME_IDS as string[]).includes(themeId)
362
+ const isEditableUserTheme = theme !== SYSTEM_THEME_ID && !isBuiltinTheme(theme)
363
+
364
+ const handleSystemThemesChange = (patch: Partial<Pick<AppSettings, 'systemLightTheme' | 'systemDarkTheme'>>): void => {
365
+ const next = { systemLightTheme, systemDarkTheme, ...patch }
366
+ setSystemLightTheme(next.systemLightTheme)
367
+ setSystemDarkTheme(next.systemDarkTheme)
368
+ applyThemeSettings({ theme, ...next })
369
+ setSettingsDraft(patch)
370
+ }
371
+
372
+ const openCreateThemeEditor = () => {
373
+ const effectiveId = resolveThemeId(theme)
374
+ const registered = getRegisteredThemes()
375
+ const baseTheme =
376
+ registered.find((entry) => entry.id === effectiveId)?.file ??
377
+ registered.find((entry) => entry.id === 'dark')!.file
378
+ setThemeEditorState({ baseTheme, baseId: effectiveId, isUserTheme: false })
379
+ }
380
+
381
+ const openEditThemeEditor = () => {
382
+ const baseTheme = getRegisteredThemes().find((entry) => entry.id === theme)?.file
383
+ if (!baseTheme) {
384
+ setThemeActionError(t('settings.theme.editNotFoundError'))
385
+ return
386
+ }
387
+ setThemeEditorState({ baseTheme, baseId: theme, isUserTheme: true })
388
+ }
389
+
390
+ const handleThemeEditorSaved = async (id: string, warning?: string) => {
391
+ setTheme(id)
392
+ // A warning is a non-fatal post-save problem (rename cleanup failure).
393
+ // It has to live in the panel's themeActionError, not the editor's own
394
+ // saveError: the editor unmounts in this same commit, so only state
395
+ // owned here survives long enough to render.
396
+ setThemeActionError(warning ?? null)
397
+ setThemeEditorState(null)
398
+ // Reconcile the registry against disk so a rename drops the old id from
399
+ // the dropdown (the editor already registered + applied the new one).
400
+ const { themes, warnings } = await window.piDesktop.themes.list()
401
+ for (const w of warnings) console.warn(w)
402
+ setUserThemes(themes)
403
+ }
404
+
405
+ const handleImportTheme = async () => {
406
+ const result = await window.piDesktop.themes.import()
407
+ if (result.ok) {
408
+ registerThemes([result.theme])
409
+ applyTheme(result.theme.id)
410
+ setTheme(result.theme.id)
411
+ setSettingsDraft({ theme: result.theme.id })
412
+ setThemeActionError(null)
413
+ } else if (!('canceled' in result)) {
414
+ setThemeActionError(result.error)
415
+ }
416
+ }
417
+
418
+ const handleExportTheme = async () => {
419
+ const effectiveThemeId = resolveThemeId(theme)
420
+ const currentThemeFile = getRegisteredThemes().find((entry) => entry.id === effectiveThemeId)?.file
421
+ if (!currentThemeFile) {
422
+ setThemeActionError(t('settings.theme.exportNotFoundError'))
423
+ return
424
+ }
425
+ const result = await window.piDesktop.themes.export(currentThemeFile)
426
+ if (result.ok) {
427
+ setThemeActionError(null)
428
+ } else if (!('canceled' in result)) {
429
+ setThemeActionError(result.error)
430
+ }
431
+ }
432
+
433
+ const handleInstallFromUrl = async () => {
434
+ if (!installUrl.trim()) return
435
+ const result = await window.piDesktop.themes.installFromUrl(installUrl.trim())
436
+ if (result.ok) {
437
+ registerThemes([result.theme])
438
+ applyTheme(result.theme.id)
439
+ setTheme(result.theme.id)
440
+ setSettingsDraft({ theme: result.theme.id })
441
+ setThemeActionError(null)
442
+ setInstallUrl('')
443
+ } else if (!('canceled' in result)) {
444
+ setThemeActionError(result.error)
445
+ }
446
+ }
447
+ const handleGalleryInstalled = (installed: UserThemeRecord) => {
448
+ registerThemes([installed])
449
+ applyTheme(installed.id)
450
+ setTheme(installed.id)
451
+ setSettingsDraft({ theme: installed.id })
452
+ setThemeActionError(null)
453
+ }
454
+
455
+ const handleDeleteTheme = async () => {
456
+ const themeName = getRegisteredThemes().find((entry) => entry.id === theme)?.file.name ?? theme
457
+ // Confirm before destructive action via the app's themed dialog, matching
458
+ // the pattern used for session delete (context-menu.tsx) rather than the
459
+ // native window.confirm. Deleting a theme file has no undo.
460
+ const ok = await useAppStore.getState().requestConfirm({
461
+ title: t('settings.theme.deleteConfirmTitle'),
462
+ message: t('settings.theme.deleteConfirmMessage', { themeName }),
463
+ confirmLabel: t('common.delete'),
464
+ danger: true,
465
+ })
466
+ if (!ok) return
467
+ await window.piDesktop.themes.delete(theme)
468
+ const { themes, warnings } = await window.piDesktop.themes.list()
469
+ for (const warning of warnings) {
470
+ console.warn(warning)
471
+ }
472
+ setUserThemes(themes)
473
+ setTheme('dark')
474
+ applyTheme('dark')
475
+ setSettingsDraft({ theme: 'dark' })
476
+ setThemeActionError(null)
477
+ }
478
+
479
+ const handleRulesChange = (rules: PermissionRule[]): void => {
480
+ setScopeRules((prev) => ({
481
+ ...prev,
482
+ [rulesScope]: { ...prev[rulesScope], rules, loaded: true },
483
+ }))
484
+ setRulesActionError(null)
485
+ // The user edited or imported rules in this panel — Save is now allowed
486
+ // to persist the list even if the on-disk file failed to load.
487
+ useAppStore.getState().setPermissionRulesDraft(rulesScope, rules)
488
+ }
489
+
490
+ const handleRulesImport = async (): Promise<void> => {
491
+ const result = await window.piDesktop.permissionRules.importFromFile()
492
+ if (result.ok) {
493
+ handleRulesChange(result.rules)
494
+ } else if (!result.canceled) {
495
+ setRulesActionError(result.error ?? t('settings.permissionRules.importFailed'))
496
+ }
497
+ }
498
+
499
+ const handleRulesExport = async (): Promise<void> => {
500
+ const result = await window.piDesktop.permissionRules.exportToFile(normalizedRules(scopeRules[rulesScope].rules))
501
+ if (!result.ok && !result.canceled) {
502
+ setRulesActionError(result.error ?? t('settings.permissionRules.exportFailed'))
503
+ }
504
+ }
505
+
506
+ // Only reachable from the workspace tab (the button is scope-gated in the
507
+ // editor), so `rulesScope` is 'workspace' when this runs.
508
+ const handleCopyFromGlobal = (): void => {
509
+ handleRulesChange(scopeRules.global.rules.map((rule) => ({ ...rule })))
510
+ }
511
+
512
+ const handleRemoveWorkspaceRules = async (): Promise<void> => {
513
+ const confirmed = await useAppStore.getState().requestConfirm({
514
+ title: t('settings.permissionRules.removeWorkspaceConfirmTitle'),
515
+ message: t('settings.permissionRules.removeWorkspaceConfirmMessage'),
516
+ confirmLabel: t('common.remove'),
517
+ danger: true,
518
+ })
519
+ if (!confirmed) return
520
+ const result = await window.piDesktop.permissionRules.removeWorkspace()
521
+ if (!result.ok) {
522
+ setRulesActionError(result.error)
523
+ return
524
+ }
525
+ useAppStore.getState().setPermissionRulesDraft('workspace', null)
526
+ setScopeRules((prev) => ({ ...prev, workspace: { ...EMPTY_SCOPE_RULES, loaded: true } }))
527
+ }
528
+
529
+ const handleSave = async () => {
530
+ // Validate rules before anything persists, so invalid rules abort the
531
+ // whole save cleanly. Only scopes shouldPersistScope would actually
532
+ // write are validated — never validate an empty list caused by a failed
533
+ // load as if the user cleared the rules.
534
+ const drafts = useAppStore.getState().permissionRulesDrafts
535
+ const scopesToPersist = (['global', 'workspace'] as const).filter((scope) =>
536
+ shouldPersistScope(drafts[scope], scopeRules[scope].loaded, scopeRules[scope].exists)
537
+ )
538
+ for (const scope of scopesToPersist) {
539
+ const rulesError = validateRuleList(scopeRules[scope].rules)
540
+ if (rulesError) {
541
+ setRulesScope(scope)
542
+ setRulesActionError(rulesError)
543
+ return
544
+ }
545
+ }
546
+
547
+ const updated: Partial<AppSettings> = {
548
+ piExecutablePath: piPath,
549
+ piEngine,
550
+ theme,
551
+ systemLightTheme,
552
+ systemDarkTheme,
553
+ fontSize,
554
+ terminalFontSize,
555
+ codeEditorFontSize,
556
+ showThinking,
557
+ autoScroll,
558
+ desktopNotifications,
559
+ resumeLastSession,
560
+ openToHomeOnLaunch,
561
+ runOnStartup,
562
+ minimizeToTrayOnClose,
563
+ permissionMode,
564
+ language,
565
+ }
566
+
567
+ const result = await window.piDesktop.settings.save(updated)
568
+
569
+ // Apply theme and font size immediately
570
+ applyThemeSettings(result)
571
+ document.documentElement.style.fontSize = `${result.fontSize}px`
572
+
573
+ // Reload settings in store
574
+ await loadSettings()
575
+
576
+ // Persist permission rules too (only the scopes shouldPersistScope
577
+ // allowed — never overwrite a scope's file with an empty list because
578
+ // loading failed and the user never touched it).
579
+ for (const scope of scopesToPersist) {
580
+ const rulesResult = await window.piDesktop.permissionRules.set(scope, normalizedRules(scopeRules[scope].rules))
581
+ if (!rulesResult.ok) {
582
+ // Settings already saved above; do not report overall success and
583
+ // do not clear either draft, so the user's rules edits survive and
584
+ // can be retried.
585
+ setRulesScope(scope)
586
+ // i18next-cli cannot statically resolve `scope`'s possible values back
587
+ // to `context:` variants here (it comes from iterating a filtered
588
+ // array, not a directly-typed literal), so the two full keys are
589
+ // spelled out explicitly instead of relying on the context option.
590
+ setRulesActionError(
591
+ scope === 'global'
592
+ ? t('settings.permissionRules.saveError_global', { error: rulesResult.error })
593
+ : t('settings.permissionRules.saveError_workspace', { error: rulesResult.error }),
594
+ )
595
+ return
596
+ }
597
+ setScopeRules((prev) => ({
598
+ ...prev,
599
+ [scope]: { ...prev[scope], loadError: null, exists: true },
600
+ }))
601
+ }
602
+
603
+ // Persisted now — drop the unsaved draft so the form and terminal/editor
604
+ // read the saved settings (just refreshed).
605
+ clearSettingsDraft()
606
+
607
+ // Show saved indicator
608
+ setSaved(true)
609
+ setTimeout(() => setSaved(false), 2000)
610
+ }
611
+
612
+ const handleReset = async () => {
613
+ // Reset only the fields this panel exposes; the rest (council, default
614
+ // model/provider/cwd, collapsed groups) are left as-is by the Partial merge.
615
+ // Values come from the shared DEFAULT_SETTINGS so there's one source of truth.
616
+ const defaults: Partial<AppSettings> = {
617
+ piExecutablePath: DEFAULT_SETTINGS.piExecutablePath,
618
+ piEngine: DEFAULT_SETTINGS.piEngine,
619
+ theme: DEFAULT_SETTINGS.theme,
620
+ systemLightTheme: DEFAULT_SETTINGS.systemLightTheme,
621
+ systemDarkTheme: DEFAULT_SETTINGS.systemDarkTheme,
622
+ fontSize: DEFAULT_SETTINGS.fontSize,
623
+ terminalFontSize: DEFAULT_SETTINGS.terminalFontSize,
624
+ codeEditorFontSize: DEFAULT_SETTINGS.codeEditorFontSize,
625
+ showThinking: DEFAULT_SETTINGS.showThinking,
626
+ autoScroll: DEFAULT_SETTINGS.autoScroll,
627
+ desktopNotifications: DEFAULT_SETTINGS.desktopNotifications,
628
+ resumeLastSession: DEFAULT_SETTINGS.resumeLastSession,
629
+ openToHomeOnLaunch: DEFAULT_SETTINGS.openToHomeOnLaunch,
630
+ runOnStartup: DEFAULT_SETTINGS.runOnStartup,
631
+ minimizeToTrayOnClose: DEFAULT_SETTINGS.minimizeToTrayOnClose,
632
+ permissionMode: DEFAULT_SETTINGS.permissionMode,
633
+ language: DEFAULT_SETTINGS.language,
634
+ }
635
+
636
+ setPiPath(defaults.piExecutablePath!)
637
+ setPiEngine(defaults.piEngine!)
638
+ setCustomAgentPathMode(false)
639
+ setTheme(defaults.theme!)
640
+ setSystemLightTheme(defaults.systemLightTheme!)
641
+ setSystemDarkTheme(defaults.systemDarkTheme!)
642
+ setFontSize(defaults.fontSize!)
643
+ setTerminalFontSize(defaults.terminalFontSize!)
644
+ setCodeEditorFontSize(defaults.codeEditorFontSize!)
645
+ setShowThinking(defaults.showThinking!)
646
+ setAutoScroll(defaults.autoScroll!)
647
+ setDesktopNotifications(defaults.desktopNotifications!)
648
+ setResumeLastSession(defaults.resumeLastSession!)
649
+ setOpenToHomeOnLaunch(defaults.openToHomeOnLaunch!)
650
+ setRunOnStartup(defaults.runOnStartup!)
651
+ setMinimizeToTrayOnClose(defaults.minimizeToTrayOnClose!)
652
+ setPermissionMode(defaults.permissionMode!)
653
+ setLanguage(defaults.language!)
654
+ setScopeRules({ global: EMPTY_SCOPE_RULES, workspace: EMPTY_SCOPE_RULES })
655
+ setRulesActionError(null)
656
+ useAppStore.getState().setPermissionRulesDraft('global', null)
657
+ useAppStore.getState().setPermissionRulesDraft('workspace', null)
658
+ void loadRulesScope('global')
659
+ void loadRulesScope('workspace')
660
+
661
+ const result = await window.piDesktop.settings.save(defaults)
662
+ applyThemeSettings(result)
663
+ document.documentElement.style.fontSize = `${result.fontSize}px`
664
+ await loadSettings()
665
+ clearSettingsDraft()
666
+
667
+ setSaved(true)
668
+ setTimeout(() => setSaved(false), 2000)
669
+ }
670
+
671
+ return (
672
+ <div className="flex-1 overflow-y-auto">
673
+ <div className="mx-auto max-w-5xl px-6 py-8">
674
+ {/* Header */}
675
+ <div className="mb-8 flex items-center justify-between">
676
+ <div className="flex items-center gap-3">
677
+ <Settings size={20} className="text-muted" />
678
+ <h1 className="text-lg font-semibold text-primary">{t('common.settings')}</h1>
679
+ </div>
680
+ </div>
681
+
682
+ {/* Agent Configuration */}
683
+ <SettingsSection title={t('settings.sections.agentConfiguration')}>
684
+ <SettingsRow
685
+ label={t('settings.agentInstallation.label')}
686
+ description={t('settings.agentInstallation.description')}
687
+ stack
688
+ >
689
+ <div className="flex flex-col gap-2">
690
+ <div className="flex gap-2">
691
+ <select
692
+ value={agentSelection}
693
+ onChange={(e) => handleAgentSelection(e.target.value)}
694
+ className="min-w-0 flex-1 appearance-none rounded-md border border-border-strong bg-surface px-3 py-1.5 text-sm text-primary hover:border-border-strong-hover focus:border-focus focus:outline-none"
695
+ >
696
+ <option value="__auto__">{t('settings.agentInstallation.autoDetectOption')}</option>
697
+ {detectedAgentInstalls.map((installation) => (
698
+ <option key={`${installation.kind}:${installation.path}`} value={installation.path}>
699
+ {t('settings.agentInstallation.installedOption', {
700
+ engine: agentEngineName(installation.kind) ?? DEFAULT_AGENT_ENGINE_NAME,
701
+ path: installation.path,
702
+ })}
703
+ </option>
704
+ ))}
705
+ {agentSelection === 'omp' && <option value="omp">{t('settings.agentInstallation.ompNotFoundOption')}</option>}
706
+ <option value="__custom__">{t('settings.agentInstallation.customPathOption')}</option>
707
+ </select>
708
+ <button
709
+ onClick={handleSelectPath}
710
+ title={t('settings.agentInstallation.chooseExecutableTitle')}
711
+ className="rounded-md border border-border-strong px-3 py-1.5 text-sm text-muted hover:bg-surface-hover transition-colors"
712
+ >
713
+ <FolderOpen size={14} />
714
+ </button>
715
+ <button
716
+ // Forced: the user clicks this right after installing an
717
+ // engine, so the cached detection result would be stale.
718
+ onClick={() => void scanAgentInstallations({ force: true })}
719
+ disabled={scanningAgentInstalls}
720
+ title={t('settings.agentInstallation.rescanTitle')}
721
+ className="rounded-md border border-border-strong px-3 py-1.5 text-sm text-muted hover:bg-surface-hover transition-colors disabled:opacity-50"
722
+ >
723
+ <RefreshCw size={14} className={scanningAgentInstalls ? 'animate-spin' : undefined} />
724
+ </button>
725
+ </div>
726
+ {showCustomAgentPath && (
727
+ <div className="flex gap-2">
728
+ <input
729
+ type="text"
730
+ value={piPath}
731
+ onChange={(e) => setAgentPath(e.target.value)}
732
+ placeholder={t('settings.agentInstallation.pathPlaceholder')}
733
+ className="min-w-0 flex-1 rounded-md border border-border-strong bg-surface px-3 py-1.5 text-sm text-primary focus:border-focus focus:outline-none"
734
+ />
735
+ <select
736
+ value={piEngine}
737
+ onChange={(e) => setAgentEngine(e.target.value as AgentEngine)}
738
+ aria-label={t('settings.agentInstallation.engineAriaLabel')}
739
+ className="rounded-md border border-border-strong bg-surface px-2 py-1.5 text-sm text-primary focus:border-focus focus:outline-none"
740
+ >
741
+ <option value="auto">{t('settings.agentInstallation.engineAutoOption')}</option>
742
+ <option value="pi">{agentEngineName('pi')}</option>
743
+ <option value="omp">{agentEngineName('omp')}</option>
744
+ </select>
745
+ </div>
746
+ )}
747
+ <div className="text-xs text-dim">
748
+ {scanningAgentInstalls
749
+ ? t('settings.agentInstallation.scanningStatus')
750
+ : detectedAgentInstalls.length > 0
751
+ ? t('settings.agentInstallation.detectedCount', { count: detectedAgentInstalls.length })
752
+ : t('settings.agentInstallation.noneDetected')}
753
+ </div>
754
+ </div>
755
+ </SettingsRow>
756
+ </SettingsSection>
757
+
758
+ {/* Appearance */}
759
+ <SettingsSection title={t('settings.sections.appearance')}>
760
+ <SettingsRow label={languageLabel} description={t('settings.language.description')}>
761
+ <LanguageSelect
762
+ value={language}
763
+ onChange={(next) => {
764
+ setLanguage(next)
765
+ setSettingsDraft({ language: next })
766
+ }}
767
+ />
768
+ </SettingsRow>
769
+
770
+ <SettingsRow label={t('settings.theme.label')} description={t('settings.theme.description')}>
771
+ <SelectField
772
+ value={theme}
773
+ onChange={(newTheme) => {
774
+ setTheme(newTheme)
775
+ applyThemeSettings({ theme: newTheme, systemLightTheme, systemDarkTheme })
776
+ setSettingsDraft({ theme: newTheme })
777
+ }}
778
+ >
779
+ <option value={SYSTEM_THEME_ID}>{t('settings.theme.systemOption')}</option>
780
+ <ThemeOptions />
781
+ </SelectField>
782
+ </SettingsRow>
783
+
784
+ {theme === SYSTEM_THEME_ID && (
785
+ <>
786
+ <SettingsRow label={t('settings.lightTheme.label')} description={t('settings.lightTheme.description')}>
787
+ <SystemThemeSelect
788
+ kind="light"
789
+ value={systemLightTheme}
790
+ onChange={(themeId) => handleSystemThemesChange({ systemLightTheme: themeId })}
791
+ />
792
+ </SettingsRow>
793
+ <SettingsRow label={t('settings.darkTheme.label')} description={t('settings.darkTheme.description')}>
794
+ <SystemThemeSelect
795
+ kind="dark"
796
+ value={systemDarkTheme}
797
+ onChange={(themeId) => handleSystemThemesChange({ systemDarkTheme: themeId })}
798
+ />
799
+ </SettingsRow>
800
+ </>
801
+ )}
802
+
803
+ <SettingsRow label={t('settings.customTheme.label')} description={t('settings.customTheme.description')}>
804
+ <div className="flex gap-2">
805
+ <button
806
+ onClick={openCreateThemeEditor}
807
+ className="rounded-md border border-border-strong px-3 py-1.5 text-sm text-muted hover:bg-surface-hover transition-colors"
808
+ >
809
+ {t('settings.customTheme.createButton')}
810
+ </button>
811
+ {isEditableUserTheme && (
812
+ <button
813
+ onClick={openEditThemeEditor}
814
+ className="rounded-md border border-border-strong px-3 py-1.5 text-sm text-muted hover:bg-surface-hover transition-colors"
815
+ >
816
+ {t('settings.customTheme.editButton')}
817
+ </button>
818
+ )}
819
+ </div>
820
+ </SettingsRow>
821
+
822
+ <SettingsRow label={t('settings.themeActions.label')} description={t('settings.themeActions.description')} stack>
823
+ <div className="flex flex-col gap-2">
824
+ <div className="flex gap-2">
825
+ <button
826
+ onClick={handleImportTheme}
827
+ className="rounded-md border border-border-strong px-3 py-1.5 text-sm text-muted hover:bg-surface-hover transition-colors"
828
+ >
829
+ {t('common.import')}
830
+ </button>
831
+ <button
832
+ onClick={handleExportTheme}
833
+ className="rounded-md border border-border-strong px-3 py-1.5 text-sm text-muted hover:bg-surface-hover transition-colors"
834
+ >
835
+ {t('common.export')}
836
+ </button>
837
+ <button
838
+ onClick={() => setGalleryOpen(true)}
839
+ className="rounded-md border border-border-strong px-3 py-1.5 text-sm text-muted hover:bg-surface-hover transition-colors"
840
+ >
841
+ {t('settings.themeActions.browseGalleryButton')}
842
+ </button>
843
+ {isEditableUserTheme && (
844
+ <button
845
+ onClick={handleDeleteTheme}
846
+ className="rounded-md border border-border-strong px-3 py-1.5 text-sm text-muted hover:bg-surface-hover transition-colors"
847
+ >
848
+ {t('common.delete')}
849
+ </button>
850
+ )}
851
+ </div>
852
+ <div className="flex gap-2">
853
+ <input
854
+ type="text"
855
+ value={installUrl}
856
+ onChange={(e) => setInstallUrl(e.target.value)}
857
+ placeholder="https://example.com/theme.json"
858
+ className="flex-1 rounded-md border border-border-strong bg-surface px-3 py-1.5 text-sm text-primary focus:border-focus focus:outline-none"
859
+ />
860
+ <button
861
+ onClick={handleInstallFromUrl}
862
+ className="shrink-0 rounded-md border border-border-strong px-3 py-1.5 text-sm text-muted hover:bg-surface-hover transition-colors"
863
+ >
864
+ {t('common.install')}
865
+ </button>
866
+ </div>
867
+ {themeActionError && <p className="text-xs text-error">{themeActionError}</p>}
868
+ </div>
869
+ </SettingsRow>
870
+
871
+ <SettingsRow label={t('settings.uiFontSize.label')} description={t('settings.uiFontSize.description')}>
872
+ <div className="flex items-center gap-3">
873
+ <input
874
+ type="range"
875
+ min={10}
876
+ max={20}
877
+ value={fontSize}
878
+ onChange={(e) => {
879
+ const size = Number(e.target.value)
880
+ setFontSize(size)
881
+ document.documentElement.style.fontSize = `${size}px`
882
+ setSettingsDraft({ fontSize: size })
883
+ }}
884
+ className="flex-1 accent-accent"
885
+ />
886
+ <span className="w-8 text-right text-sm text-muted">{fontSize}</span>
887
+ </div>
888
+ </SettingsRow>
889
+
890
+ <SettingsRow label={t('settings.terminalFontSize.label')} description={t('settings.terminalFontSize.description')}>
891
+ <div className="flex items-center gap-3">
892
+ <input
893
+ type="range"
894
+ min={10}
895
+ max={20}
896
+ value={terminalFontSize}
897
+ onChange={(e) => {
898
+ const size = Number(e.target.value)
899
+ setTerminalFontSize(size)
900
+ setSettingsDraft({ terminalFontSize: size })
901
+ }}
902
+ className="flex-1 accent-accent"
903
+ />
904
+ <span className="w-8 text-right text-sm text-muted">{terminalFontSize}</span>
905
+ </div>
906
+ </SettingsRow>
907
+
908
+ <SettingsRow label={t('settings.codeEditorFontSize.label')} description={t('settings.codeEditorFontSize.description')}>
909
+ <div className="flex items-center gap-3">
910
+ <input
911
+ type="range"
912
+ min={10}
913
+ max={20}
914
+ value={codeEditorFontSize}
915
+ onChange={(e) => {
916
+ const size = Number(e.target.value)
917
+ setCodeEditorFontSize(size)
918
+ setSettingsDraft({ codeEditorFontSize: size })
919
+ }}
920
+ className="flex-1 accent-accent"
921
+ />
922
+ <span className="w-8 text-right text-sm text-muted">{codeEditorFontSize}</span>
923
+ </div>
924
+ </SettingsRow>
925
+ </SettingsSection>
926
+
927
+ {/* Behavior */}
928
+ <SettingsSection title={t('settings.sections.behavior')}>
929
+ <SettingsRow label={t('settings.permissionMode.label')} description={t('settings.permissionMode.description')}>
930
+ <PermissionSelector
931
+ value={permissionMode}
932
+ onChange={(mode) => {
933
+ setPermissionMode(mode)
934
+ setSettingsDraft({ permissionMode: mode })
935
+ }}
936
+ compact
937
+ />
938
+ </SettingsRow>
939
+
940
+ <SettingsRow label={t('settings.permissionRules.label')} description={t('settings.permissionRules.description')} stack>
941
+ <div className="mb-2 flex gap-1" role="tablist" aria-label={t('settings.permissionRules.scopeTabListAriaLabel')}>
942
+ {(['global', 'workspace'] as const).map((scope) => (
943
+ <button
944
+ key={scope}
945
+ type="button"
946
+ role="tab"
947
+ aria-selected={rulesScope === scope}
948
+ onClick={() => handleRulesScopeChange(scope)}
949
+ className={clsx(
950
+ 'rounded-md px-2 py-1 text-xs transition-colors',
951
+ rulesScope === scope
952
+ ? 'bg-surface border border-border-strong text-primary'
953
+ : 'text-dim hover:text-primary'
954
+ )}
955
+ >
956
+ {scope === 'global' ? t('common.global') : t('settings.permissionRules.workspaceScopeTab')}
957
+ </button>
958
+ ))}
959
+ </div>
960
+ <PermissionRulesEditor
961
+ rules={scopeRules[rulesScope].rules}
962
+ onChange={handleRulesChange}
963
+ onImport={() => void handleRulesImport()}
964
+ onExport={() => void handleRulesExport()}
965
+ scope={rulesScope}
966
+ workspaceExists={scopeRules.workspace.exists}
967
+ onCopyFromGlobal={handleCopyFromGlobal}
968
+ onRemoveWorkspace={() => void handleRemoveWorkspaceRules()}
969
+ workspaceOverride={scopeRules.workspace.exists}
970
+ workspaceActive={!!activeWorkspace}
971
+ workspaceTrusted={workspaceRulesStatus?.trusted ?? false}
972
+ workspaceHasAllowRules={workspaceRulesStatus?.hasAllowRules ?? false}
973
+ onSetWorkspaceTrust={(trusted) => void handleSetWorkspaceTrust(trusted)}
974
+ loadError={scopeRules[rulesScope].loadError}
975
+ actionError={rulesActionError}
976
+ />
977
+ </SettingsRow>
978
+
979
+ <SettingsRow label={t('settings.showThinking.label')} description={t('settings.showThinking.description')}>
980
+ <Toggle checked={showThinking} onChange={(v) => { setShowThinking(v); setSettingsDraft({ showThinking: v }) }} />
981
+ </SettingsRow>
982
+
983
+ <SettingsRow label={t('settings.autoScroll.label')} description={t('settings.autoScroll.description')}>
984
+ <Toggle checked={autoScroll} onChange={(v) => { setAutoScroll(v); setSettingsDraft({ autoScroll: v }) }} />
985
+ </SettingsRow>
986
+
987
+ <SettingsRow
988
+ label={t('settings.desktopNotifications.label')}
989
+ description={t('settings.desktopNotifications.description')}
990
+ >
991
+ <Toggle checked={desktopNotifications} onChange={(v) => { setDesktopNotifications(v); setSettingsDraft({ desktopNotifications: v }) }} />
992
+ </SettingsRow>
993
+
994
+ <SettingsRow
995
+ label={t('settings.openToHomeOnLaunch.label')}
996
+ description={t('settings.openToHomeOnLaunch.description')}
997
+ >
998
+ <Toggle checked={openToHomeOnLaunch} onChange={(v) => { setOpenToHomeOnLaunch(v); setSettingsDraft({ openToHomeOnLaunch: v }) }} />
999
+ </SettingsRow>
1000
+
1001
+ <SettingsRow
1002
+ label={t('settings.resumeLastSession.label')}
1003
+ description={t('settings.resumeLastSession.description')}
1004
+ >
1005
+ <Toggle checked={resumeLastSession} onChange={(v) => { setResumeLastSession(v); setSettingsDraft({ resumeLastSession: v }) }} />
1006
+ </SettingsRow>
1007
+
1008
+ <SettingsRow
1009
+ label={t('settings.runOnStartup.label')}
1010
+ description={t('settings.runOnStartup.description')}
1011
+ >
1012
+ <Toggle checked={runOnStartup} onChange={(v) => { setRunOnStartup(v); void applyImmediate({ runOnStartup: v }) }} />
1013
+ </SettingsRow>
1014
+
1015
+ <SettingsRow
1016
+ label={t('settings.minimizeToTrayOnClose.label')}
1017
+ description={t('settings.minimizeToTrayOnClose.description')}
1018
+ >
1019
+ <Toggle checked={minimizeToTrayOnClose} onChange={(v) => { setMinimizeToTrayOnClose(v); void applyImmediate({ minimizeToTrayOnClose: v }) }} />
1020
+ </SettingsRow>
1021
+ </SettingsSection>
1022
+
1023
+ {/* Multi-Agent Council Planning */}
1024
+ <SettingsSection title={t('settings.sections.multiAgentCouncilPlanning')}>
1025
+ <SettingsRow
1026
+ label={t('settings.councilEnabled.label')}
1027
+ description={t('settings.councilEnabled.description')}
1028
+ >
1029
+ <Toggle
1030
+ checked={settings?.council.enabled ?? false}
1031
+ onChange={(value) => {
1032
+ if (value) {
1033
+ setShowCouncilWarning(true)
1034
+ } else {
1035
+ void saveCouncil({ enabled: false })
1036
+ }
1037
+ }}
1038
+ />
1039
+ </SettingsRow>
1040
+
1041
+ {settings?.council.enabled && (
1042
+ <>
1043
+ <SettingsRow label={t('settings.councilMembers.label')} description={t('settings.councilMembers.description')}>
1044
+ <div className="flex flex-col gap-2">
1045
+ {(['pi', 'claude', 'codex'] as const).map((id) => {
1046
+ const detected = detectedAgents[id]
1047
+ const label = councilAgentLabel(id)
1048
+ return (
1049
+ <label
1050
+ key={id}
1051
+ className={`flex items-center gap-2 text-sm ${
1052
+ detected ? 'text-primary' : 'text-dim'
1053
+ }`}
1054
+ >
1055
+ <input
1056
+ type="checkbox"
1057
+ disabled={!detected}
1058
+ checked={settings.council.members[id]}
1059
+ onChange={(e) =>
1060
+ void saveCouncil({
1061
+ members: { ...settings.council.members, [id]: e.target.checked },
1062
+ })
1063
+ }
1064
+ className="accent-accent disabled:opacity-50"
1065
+ />
1066
+ <span>
1067
+ {label}
1068
+ {!detected && <span className="text-faint"> {t('settings.councilMembers.notDetected')}</span>}
1069
+ </span>
1070
+ </label>
1071
+ )
1072
+ })}
1073
+ </div>
1074
+ </SettingsRow>
1075
+
1076
+ <SettingsRow
1077
+ label={t('settings.consensusMode.label')}
1078
+ description={t('settings.consensusMode.description')}
1079
+ >
1080
+ <SelectField
1081
+ value={settings.council.consensusMode}
1082
+ onChange={(consensusMode) =>
1083
+ void saveCouncil({ consensusMode: consensusMode as CouncilConfig['consensusMode'] })
1084
+ }
1085
+ >
1086
+ <option value="arbiter">{t('settings.consensusMode.arbiterOption')}</option>
1087
+ <option value="debate">{t('settings.consensusMode.debateOption')}</option>
1088
+ </SelectField>
1089
+ </SettingsRow>
1090
+
1091
+ <SettingsRow
1092
+ label={t('settings.councilTimeout.label')}
1093
+ description={t('settings.councilTimeout.description', { min: COUNCIL_MIN_TIMEOUT, max: COUNCIL_MAX_TIMEOUT })}
1094
+ >
1095
+ <input
1096
+ type="number"
1097
+ min={COUNCIL_MIN_TIMEOUT}
1098
+ max={COUNCIL_MAX_TIMEOUT}
1099
+ value={timeoutDraft}
1100
+ onChange={(e) => setTimeoutDraft(e.target.value)}
1101
+ onBlur={() => {
1102
+ const clamped = clampCouncilTimeout(Number(timeoutDraft))
1103
+ setTimeoutDraft(String(clamped))
1104
+ if (clamped !== settings.council.timeoutSeconds) {
1105
+ void saveCouncil({ timeoutSeconds: clamped })
1106
+ }
1107
+ }}
1108
+ onKeyDown={(e) => {
1109
+ if (e.key === 'Enter') (e.target as HTMLInputElement).blur()
1110
+ }}
1111
+ className="w-full rounded-md border border-border-strong bg-surface px-3 py-1.5 text-sm text-primary focus:border-focus focus:outline-none"
1112
+ />
1113
+ </SettingsRow>
1114
+ </>
1115
+ )}
1116
+ </SettingsSection>
1117
+
1118
+ {/* Custom Models */}
1119
+ <SettingsSection title={t('settings.sections.customModels')}>
1120
+ <CustomModelsEditor />
1121
+ </SettingsSection>
1122
+
1123
+ {/* Actions */}
1124
+ <div className="mt-8 flex gap-3">
1125
+ <button
1126
+ onClick={handleSave}
1127
+ className="flex items-center gap-2 rounded-md bg-accent px-4 py-2 text-sm text-inverse hover:bg-accent-hover transition-colors"
1128
+ >
1129
+ {saved ? <Check size={14} /> : <Save size={14} />}
1130
+ {saved ? t('settings.actions.saved') : t('settings.actions.save')}
1131
+ </button>
1132
+ <button
1133
+ onClick={handleReset}
1134
+ className="flex items-center gap-2 rounded-md border border-border-strong px-4 py-2 text-sm text-muted hover:bg-surface-hover transition-colors"
1135
+ >
1136
+ <RotateCcw size={14} />
1137
+ {t('settings.actions.reset')}
1138
+ </button>
1139
+ </div>
1140
+ </div>
1141
+
1142
+ {galleryOpen && (
1143
+ <ThemeGallery
1144
+ onClose={() => setGalleryOpen(false)}
1145
+ onInstalled={handleGalleryInstalled}
1146
+ />
1147
+ )}
1148
+
1149
+ {themeEditorState && (
1150
+ <ThemeEditor
1151
+ baseTheme={themeEditorState.baseTheme}
1152
+ baseId={themeEditorState.baseId}
1153
+ isUserTheme={themeEditorState.isUserTheme}
1154
+ onClose={() => setThemeEditorState(null)}
1155
+ onSaved={handleThemeEditorSaved}
1156
+ />
1157
+ )}
1158
+
1159
+ {/* Council enable confirmation dialog */}
1160
+ {showCouncilWarning && (
1161
+ <div className="fixed inset-0 z-50 flex items-center justify-center bg-black/60 p-4">
1162
+ <div className="w-full max-w-md rounded-lg border border-border-strong bg-surface p-6 shadow-xl">
1163
+ <h3 className="mb-3 text-base font-semibold text-primary">
1164
+ {t('settings.councilEnabled.confirmTitle')}
1165
+ </h3>
1166
+ <p className="mb-6 text-sm text-muted">
1167
+ {t('settings.councilEnabled.confirmMessage', { engine: runningEngineLabel })}
1168
+ </p>
1169
+ <div className="flex justify-end gap-3">
1170
+ <button
1171
+ onClick={() => setShowCouncilWarning(false)}
1172
+ className="rounded-md border border-border-strong px-4 py-2 text-sm text-muted hover:bg-surface-hover transition-colors"
1173
+ >
1174
+ {t('common.cancel')}
1175
+ </button>
1176
+ <button
1177
+ onClick={() => {
1178
+ setShowCouncilWarning(false)
1179
+ void saveCouncil({ enabled: true })
1180
+ }}
1181
+ className="rounded-md bg-accent px-4 py-2 text-sm text-inverse hover:bg-accent-hover transition-colors"
1182
+ >
1183
+ {t('settings.councilEnabled.confirmButton')}
1184
+ </button>
1185
+ </div>
1186
+ </div>
1187
+ </div>
1188
+ )}
1189
+ </div>
1190
+ )
1191
+ }
1192
+
1193
+ // ─── Components ──────────────────────────────────────────────────────────────
1194
+
1195
+ function SettingsSection({
1196
+ title,
1197
+ children,
1198
+ }: {
1199
+ title: string
1200
+ children: React.ReactNode
1201
+ }): React.JSX.Element {
1202
+ return (
1203
+ <div className="mb-8">
1204
+ <h2 className="mb-4 text-sm font-medium text-secondary">{title}</h2>
1205
+ <div className="space-y-4 rounded-lg border border-border bg-surface/50 p-4">
1206
+ {children}
1207
+ </div>
1208
+ </div>
1209
+ )
1210
+ }
1211
+
1212
+ function SettingsRow({
1213
+ label,
1214
+ description,
1215
+ children,
1216
+ stack = false,
1217
+ }: {
1218
+ label: string
1219
+ description: string
1220
+ children: React.ReactNode
1221
+ // Controls that are wider than the fixed control column (e.g. a URL input
1222
+ // beside a button) render below the label at full width instead of being
1223
+ // crammed into the right-hand w-64 column.
1224
+ stack?: boolean
1225
+ }): React.JSX.Element {
1226
+ if (stack) {
1227
+ return (
1228
+ <div className="flex flex-col gap-2">
1229
+ <div>
1230
+ <div className="text-sm text-primary">{label}</div>
1231
+ <div className="text-xs text-dim">{description}</div>
1232
+ </div>
1233
+ <div>{children}</div>
1234
+ </div>
1235
+ )
1236
+ }
1237
+ return (
1238
+ <div className="flex items-center justify-between gap-4">
1239
+ <div>
1240
+ <div className="text-sm text-primary">{label}</div>
1241
+ <div className="text-xs text-dim">{description}</div>
1242
+ </div>
1243
+ <div className="w-64">{children}</div>
1244
+ </div>
1245
+ )
1246
+ }
1247
+
1248
+ function SelectField({
1249
+ value,
1250
+ onChange,
1251
+ children,
1252
+ }: {
1253
+ value: string
1254
+ onChange: (value: string) => void
1255
+ children: React.ReactNode
1256
+ }): React.JSX.Element {
1257
+ return (
1258
+ <div className="relative">
1259
+ <select
1260
+ value={value}
1261
+ onChange={(e) => onChange(e.target.value)}
1262
+ className="w-full appearance-none rounded-md border border-border-strong bg-surface py-1.5 pl-3 pr-9 text-sm text-primary hover:border-border-strong-hover focus:border-focus focus:outline-none"
1263
+ >
1264
+ {children}
1265
+ </select>
1266
+ <ChevronDown
1267
+ size={14}
1268
+ className="pointer-events-none absolute right-3 top-1/2 -translate-y-1/2 text-dim"
1269
+ />
1270
+ </div>
1271
+ )
1272
+ }
1273
+
1274
+ // One <option> per registered theme, optionally only those of one kind.
1275
+ function ThemeOptions({ kind }: { kind?: ThemeKind }): React.JSX.Element {
1276
+ // Subscribes this component to language changes so built-in theme names
1277
+ // (translated by themeDisplayName, which uses the shared t) re-render.
1278
+ useTranslation()
1279
+ return (
1280
+ <>
1281
+ {getRegisteredThemes()
1282
+ .filter((registeredTheme) => !kind || registeredTheme.file.kind === kind)
1283
+ .map((registeredTheme) => (
1284
+ <option key={registeredTheme.id} value={registeredTheme.id}>
1285
+ {themeDisplayName(registeredTheme.id, registeredTheme.file.name)}
1286
+ </option>
1287
+ ))}
1288
+ </>
1289
+ )
1290
+ }
1291
+
1292
+ // Picks the theme 'system' uses for one OS mode. Shows the theme that
1293
+ // actually applies, so a deleted or kind-flipped choice reads as its fallback.
1294
+ function SystemThemeSelect({
1295
+ kind,
1296
+ value,
1297
+ onChange,
1298
+ }: {
1299
+ kind: ThemeKind
1300
+ value: string
1301
+ onChange: (themeId: string) => void
1302
+ }): React.JSX.Element {
1303
+ return (
1304
+ <SelectField value={resolveSystemThemeSlot(kind, value)} onChange={onChange}>
1305
+ <ThemeOptions kind={kind} />
1306
+ </SelectField>
1307
+ )
1308
+ }
1309
+
1310
+ // The saved language applies on Save, like every other setting.
1311
+ function LanguageSelect({
1312
+ value,
1313
+ onChange,
1314
+ }: {
1315
+ value: string
1316
+ onChange: (language: string) => void
1317
+ }): React.JSX.Element {
1318
+ // Subscribes this component to language changes so its option labels
1319
+ // (each language's own name) re-render when the language changes elsewhere.
1320
+ useTranslation()
1321
+ const [environment, setEnvironment] = useState<I18nEnvironment | null>(loadedI18nEnvironment)
1322
+ useEffect(() => {
1323
+ if (!environment) {
1324
+ // On failure, leave the options empty; the next time this panel opens,
1325
+ // environment is still null and this effect retries the load.
1326
+ void loadI18nEnvironment().then(setEnvironment).catch(() => {})
1327
+ }
1328
+ }, [environment])
1329
+ const options = environment ? languagePickerOptions(environment) : []
1330
+ return (
1331
+ <SelectField value={value} onChange={onChange}>
1332
+ {options.map((option) => (
1333
+ <option key={option.value} value={option.value}>{option.label}</option>
1334
+ ))}
1335
+ </SelectField>
1336
+ )
1337
+ }
1338
+
1339
+ function Toggle({
1340
+ checked,
1341
+ onChange,
1342
+ }: {
1343
+ checked: boolean
1344
+ onChange: (value: boolean) => void
1345
+ }): React.JSX.Element {
1346
+ return (
1347
+ <button
1348
+ onClick={() => onChange(!checked)}
1349
+ className={`relative inline-flex h-5 w-9 items-center rounded-full transition-colors ${
1350
+ checked ? 'bg-accent' : 'bg-elevated'
1351
+ }`}
1352
+ >
1353
+ <span
1354
+ className={`inline-block h-3.5 w-3.5 rounded-full transition-transform ${
1355
+ checked ? 'translate-x-4 bg-inverse' : 'translate-x-1 bg-primary'
1356
+ }`}
1357
+ />
1358
+ </button>
1359
+ )
1360
+ }