unspaghettit 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (270) hide show
  1. package/build/client/_app/immutable/assets/0.DSctqr5I.css +1 -0
  2. package/build/client/_app/immutable/assets/0.DSctqr5I.css.br +0 -0
  3. package/build/client/_app/immutable/assets/0.DSctqr5I.css.gz +0 -0
  4. package/build/client/_app/immutable/assets/BehaviorGraph.Bk0xQRZk.css +1 -0
  5. package/build/client/_app/immutable/assets/BehaviorGraph.Bk0xQRZk.css.br +0 -0
  6. package/build/client/_app/immutable/assets/BehaviorGraph.Bk0xQRZk.css.gz +0 -0
  7. package/build/client/_app/immutable/chunks/9nXQ5qrY2.js +1 -0
  8. package/build/client/_app/immutable/chunks/9nXQ5qrY2.js.br +0 -0
  9. package/build/client/_app/immutable/chunks/9nXQ5qrY2.js.gz +0 -0
  10. package/build/client/_app/immutable/chunks/B439_FLv.js +1 -0
  11. package/build/client/_app/immutable/chunks/B439_FLv.js.br +0 -0
  12. package/build/client/_app/immutable/chunks/B439_FLv.js.gz +0 -0
  13. package/build/client/_app/immutable/chunks/BCEY79Dw.js +1 -0
  14. package/build/client/_app/immutable/chunks/BCEY79Dw.js.br +2 -0
  15. package/build/client/_app/immutable/chunks/BCEY79Dw.js.gz +0 -0
  16. package/build/client/_app/immutable/chunks/{DBJWcC6Y.js → BYIrIC5L.js} +1 -1
  17. package/build/client/_app/immutable/chunks/BYIrIC5L.js.br +0 -0
  18. package/build/client/_app/immutable/chunks/BYIrIC5L.js.gz +0 -0
  19. package/build/client/_app/immutable/chunks/B_9TWPrx2.js +1 -0
  20. package/build/client/_app/immutable/chunks/B_9TWPrx2.js.br +0 -0
  21. package/build/client/_app/immutable/chunks/B_9TWPrx2.js.gz +0 -0
  22. package/build/client/_app/immutable/chunks/BvOhVtZg.js +1 -0
  23. package/build/client/_app/immutable/chunks/BvOhVtZg.js.br +1 -0
  24. package/build/client/_app/immutable/chunks/BvOhVtZg.js.gz +0 -0
  25. package/build/client/_app/immutable/chunks/CY3em1ma2.js +1 -0
  26. package/build/client/_app/immutable/chunks/CY3em1ma2.js.br +0 -0
  27. package/build/client/_app/immutable/chunks/CY3em1ma2.js.gz +0 -0
  28. package/build/client/_app/immutable/chunks/CgdRZPgI.js +1 -0
  29. package/build/client/_app/immutable/chunks/CgdRZPgI.js.br +0 -0
  30. package/build/client/_app/immutable/chunks/CgdRZPgI.js.gz +0 -0
  31. package/build/client/_app/immutable/chunks/D5speDV82.js +908 -0
  32. package/build/client/_app/immutable/chunks/D5speDV82.js.br +0 -0
  33. package/build/client/_app/immutable/chunks/D5speDV82.js.gz +0 -0
  34. package/build/client/_app/immutable/chunks/DkxwAIfJ2.js +1 -0
  35. package/build/client/_app/immutable/chunks/DkxwAIfJ2.js.br +0 -0
  36. package/build/client/_app/immutable/chunks/DkxwAIfJ2.js.gz +0 -0
  37. package/build/client/_app/immutable/chunks/{DatGSObE.js → OJscNS3T.js} +1 -1
  38. package/build/client/_app/immutable/chunks/OJscNS3T.js.br +0 -0
  39. package/build/client/_app/immutable/chunks/OJscNS3T.js.gz +0 -0
  40. package/build/client/_app/immutable/chunks/U9p9CtKG2.js +2 -0
  41. package/build/client/_app/immutable/chunks/U9p9CtKG2.js.br +0 -0
  42. package/build/client/_app/immutable/chunks/U9p9CtKG2.js.gz +0 -0
  43. package/build/client/_app/immutable/entry/app.Cd4S3giu.js +2 -0
  44. package/build/client/_app/immutable/entry/app.Cd4S3giu.js.br +0 -0
  45. package/build/client/_app/immutable/entry/app.Cd4S3giu.js.gz +0 -0
  46. package/build/client/_app/immutable/entry/start.C3xXQVkq.js +1 -0
  47. package/build/client/_app/immutable/entry/start.C3xXQVkq.js.br +0 -0
  48. package/build/client/_app/immutable/entry/start.C3xXQVkq.js.gz +0 -0
  49. package/build/client/_app/immutable/nodes/0.dIOlQ-0y.js +4 -0
  50. package/build/client/_app/immutable/nodes/0.dIOlQ-0y.js.br +0 -0
  51. package/build/client/_app/immutable/nodes/0.dIOlQ-0y.js.gz +0 -0
  52. package/build/client/_app/immutable/nodes/{1.KYdA6ppX.js → 1.Dyte3Ggf.js} +1 -1
  53. package/build/client/_app/immutable/nodes/1.Dyte3Ggf.js.br +2 -0
  54. package/build/client/_app/immutable/nodes/1.Dyte3Ggf.js.gz +0 -0
  55. package/build/client/_app/immutable/nodes/10.ivxAosDg.js +2 -0
  56. package/build/client/_app/immutable/nodes/10.ivxAosDg.js.br +0 -0
  57. package/build/client/_app/immutable/nodes/10.ivxAosDg.js.gz +0 -0
  58. package/build/client/_app/immutable/nodes/{9.CMW6a2Lg.js → 11.wvMfJKC2.js} +1 -1
  59. package/build/client/_app/immutable/nodes/11.wvMfJKC2.js.br +0 -0
  60. package/build/client/_app/immutable/nodes/11.wvMfJKC2.js.gz +0 -0
  61. package/build/client/_app/immutable/nodes/{2.DBz20KgG.js → 2.CmPPom9Z.js} +1 -1
  62. package/build/client/_app/immutable/nodes/2.CmPPom9Z.js.br +0 -0
  63. package/build/client/_app/immutable/nodes/2.CmPPom9Z.js.gz +0 -0
  64. package/build/client/_app/immutable/nodes/3.D-iCGCEx.js +1 -0
  65. package/build/client/_app/immutable/nodes/3.D-iCGCEx.js.br +0 -0
  66. package/build/client/_app/immutable/nodes/3.D-iCGCEx.js.gz +0 -0
  67. package/build/client/_app/immutable/nodes/{4.BvMzqBJj.js → 4.DbfAvO8Z.js} +1 -1
  68. package/build/client/_app/immutable/nodes/4.DbfAvO8Z.js.br +0 -0
  69. package/build/client/_app/immutable/nodes/4.DbfAvO8Z.js.gz +0 -0
  70. package/build/client/_app/immutable/nodes/5.CC5Q7lVw.js +42 -0
  71. package/build/client/_app/immutable/nodes/5.CC5Q7lVw.js.br +0 -0
  72. package/build/client/_app/immutable/nodes/5.CC5Q7lVw.js.gz +0 -0
  73. package/build/client/_app/immutable/nodes/6.CHIjlzpO.js +1 -0
  74. package/build/client/_app/immutable/nodes/6.CHIjlzpO.js.br +0 -0
  75. package/build/client/_app/immutable/nodes/6.CHIjlzpO.js.gz +0 -0
  76. package/build/client/_app/immutable/nodes/{6.BOHISqs-.js → 7.Ejs18ZUc.js} +1 -1
  77. package/build/client/_app/immutable/nodes/7.Ejs18ZUc.js.br +0 -0
  78. package/build/client/_app/immutable/nodes/7.Ejs18ZUc.js.gz +0 -0
  79. package/build/client/_app/immutable/nodes/{7.CemgNJfw.js → 8.B-HweAc8.js} +1 -1
  80. package/build/client/_app/immutable/nodes/8.B-HweAc8.js.br +0 -0
  81. package/build/client/_app/immutable/nodes/8.B-HweAc8.js.gz +0 -0
  82. package/build/client/_app/immutable/nodes/9.CKPeM6tx.js +5 -0
  83. package/build/client/_app/immutable/nodes/9.CKPeM6tx.js.br +0 -0
  84. package/build/client/_app/immutable/nodes/9.CKPeM6tx.js.gz +0 -0
  85. package/build/client/_app/version.json +1 -1
  86. package/build/client/_app/version.json.br +0 -0
  87. package/build/client/_app/version.json.gz +0 -0
  88. package/build/client/lyriks_logo.svg +148 -0
  89. package/build/client/lyriks_logo.svg.br +0 -0
  90. package/build/client/lyriks_logo.svg.gz +0 -0
  91. package/build/server/chunks/{0-C_o0oz-N.js → 0-Co8kcANG.js} +4 -4
  92. package/build/server/chunks/{0-C_o0oz-N.js.map → 0-Co8kcANG.js.map} +1 -1
  93. package/build/server/chunks/1-BSUItTig.js +9 -0
  94. package/build/server/chunks/{1-DPpKAKXV.js.map → 1-BSUItTig.js.map} +1 -1
  95. package/build/server/chunks/10-BygvxrZp.js +9 -0
  96. package/build/server/chunks/10-BygvxrZp.js.map +1 -0
  97. package/build/server/chunks/11-DRx0tRx2.js +9 -0
  98. package/build/server/chunks/11-DRx0tRx2.js.map +1 -0
  99. package/build/server/chunks/{2-AlfFqtL1.js → 2-BQT3m1vc.js} +3 -3
  100. package/build/server/chunks/{2-AlfFqtL1.js.map → 2-BQT3m1vc.js.map} +1 -1
  101. package/build/server/chunks/{3-vbjUt_51.js → 3-DPZ9BquJ.js} +3 -3
  102. package/build/server/chunks/{3-vbjUt_51.js.map → 3-DPZ9BquJ.js.map} +1 -1
  103. package/build/server/chunks/{4-BNow4x6D.js → 4-DHo47YX6.js} +3 -3
  104. package/build/server/chunks/{4-BNow4x6D.js.map → 4-DHo47YX6.js.map} +1 -1
  105. package/build/server/chunks/5-Cp9evBAG.js +9 -0
  106. package/build/server/chunks/5-Cp9evBAG.js.map +1 -0
  107. package/build/server/chunks/6-DiBq3bOV.js +9 -0
  108. package/build/server/chunks/6-DiBq3bOV.js.map +1 -0
  109. package/build/server/chunks/{6-QQ7r8Rd5.js → 7-C4hmS0dG.js} +3 -3
  110. package/build/server/chunks/{6-QQ7r8Rd5.js.map → 7-C4hmS0dG.js.map} +1 -1
  111. package/build/server/chunks/{7-CbPLGaIG.js → 8-CFFuDzBC.js} +4 -4
  112. package/build/server/chunks/{7-CbPLGaIG.js.map → 8-CFFuDzBC.js.map} +1 -1
  113. package/build/server/chunks/9-nhhKZJrs.js +9 -0
  114. package/build/server/chunks/9-nhhKZJrs.js.map +1 -0
  115. package/build/server/chunks/BehaviorGraph-m5kYj5HH.js +757 -0
  116. package/build/server/chunks/BehaviorGraph-m5kYj5HH.js.map +1 -0
  117. package/build/server/chunks/{FeatureCard-BQOY6gJQ.js → FeatureCard-CfbXNYe8.js} +2 -2
  118. package/build/server/chunks/{FeatureCard-BQOY6gJQ.js.map → FeatureCard-CfbXNYe8.js.map} +1 -1
  119. package/build/server/chunks/{ProgressBar-CfhccQ83.js → ProgressBar-DDoQJ_C9.js} +2 -2
  120. package/build/server/chunks/ProgressBar-DDoQJ_C9.js.map +1 -0
  121. package/build/server/chunks/{ProjectsIndex-CoDrvRya.js → ProjectsIndex-DUVJ3hyL.js} +2 -2
  122. package/build/server/chunks/{ProjectsIndex-CoDrvRya.js.map → ProjectsIndex-DUVJ3hyL.js.map} +1 -1
  123. package/build/server/chunks/TransitionCatalog-B8zHs-2E.js +271 -0
  124. package/build/server/chunks/TransitionCatalog-B8zHs-2E.js.map +1 -0
  125. package/build/server/chunks/{_layout.svelte-BREws55o.js → _layout.svelte-CLTmk0xU.js} +66 -15
  126. package/build/server/chunks/_layout.svelte-CLTmk0xU.js.map +1 -0
  127. package/build/server/chunks/{_page.svelte-De508ek8.js → _page.svelte-B1nG3PKn.js} +4 -4
  128. package/build/server/chunks/{_page.svelte-De508ek8.js.map → _page.svelte-B1nG3PKn.js.map} +1 -1
  129. package/build/server/chunks/{_page.svelte-Zf9H4KOP.js → _page.svelte-BW_nbAAH.js} +11 -315
  130. package/build/server/chunks/_page.svelte-BW_nbAAH.js.map +1 -0
  131. package/build/server/chunks/{_page.svelte-BqSC-1vK.js → _page.svelte-Caq7J0jU.js} +91 -47
  132. package/build/server/chunks/_page.svelte-Caq7J0jU.js.map +1 -0
  133. package/build/server/chunks/{_page.svelte-B7hT3P8E.js → _page.svelte-Cham-dsM.js} +4 -4
  134. package/build/server/chunks/{_page.svelte-B7hT3P8E.js.map → _page.svelte-Cham-dsM.js.map} +1 -1
  135. package/build/server/chunks/_page.svelte-Dhwjwph_.js +41 -0
  136. package/build/server/chunks/_page.svelte-Dhwjwph_.js.map +1 -0
  137. package/build/server/chunks/{_page.svelte-BKKCa9H5.js → _page.svelte-DlFVT40-.js} +3 -3
  138. package/build/server/chunks/{_page.svelte-BKKCa9H5.js.map → _page.svelte-DlFVT40-.js.map} +1 -1
  139. package/build/server/chunks/{_page.svelte-BtI2zZ_Z.js → _page.svelte-NVT2dzpG.js} +176 -327
  140. package/build/server/chunks/_page.svelte-NVT2dzpG.js.map +1 -0
  141. package/build/server/chunks/{_page.svelte-BKTveFAj.js → _page.svelte-Oj-W7G5s.js} +5 -5
  142. package/build/server/chunks/{_page.svelte-BKTveFAj.js.map → _page.svelte-Oj-W7G5s.js.map} +1 -1
  143. package/build/server/chunks/_page.svelte-Z_kK2lHY.js +68 -0
  144. package/build/server/chunks/_page.svelte-Z_kK2lHY.js.map +1 -0
  145. package/build/server/chunks/{builderModeStore.svelte-ihupr-3p.js → builderModeStore.svelte-BpRIU_zP.js} +217 -6
  146. package/build/server/chunks/builderModeStore.svelte-BpRIU_zP.js.map +1 -0
  147. package/build/server/chunks/client-DeX3TC3s.js +51 -0
  148. package/build/server/chunks/{client-DfpLcAZ9.js.map → client-DeX3TC3s.js.map} +1 -1
  149. package/build/server/chunks/{error.svelte-C35KOpru.js → error.svelte-Cdjeq3L2.js} +4 -4
  150. package/build/server/chunks/{error.svelte-C35KOpru.js.map → error.svelte-Cdjeq3L2.js.map} +1 -1
  151. package/build/server/chunks/featureStore.svelte-DIYgPBVm.js +161 -0
  152. package/build/server/chunks/featureStore.svelte-DIYgPBVm.js.map +1 -0
  153. package/build/server/chunks/{hooks.server-Rv301GTB.js → hooks.server-y3jdg_sB.js} +6 -2
  154. package/build/server/chunks/hooks.server-y3jdg_sB.js.map +1 -0
  155. package/build/server/chunks/{internal-BPKrFkK1.js → internal-KYK0WpL7.js} +4 -4
  156. package/build/server/chunks/{internal-BPKrFkK1.js.map → internal-KYK0WpL7.js.map} +1 -1
  157. package/build/server/chunks/projectFeaturesStore.svelte-2o-72_vr.js +313 -0
  158. package/build/server/chunks/projectFeaturesStore.svelte-2o-72_vr.js.map +1 -0
  159. package/build/server/chunks/{reconcile-Dv7jS3C8.js → reconcile-B5xqb6-s.js} +3 -272
  160. package/build/server/chunks/reconcile-B5xqb6-s.js.map +1 -0
  161. package/build/server/chunks/registry-DqAn_hVE.js +21 -0
  162. package/build/server/chunks/registry-DqAn_hVE.js.map +1 -0
  163. package/build/server/chunks/{state-CpLVNZq7.js → state-DBjl9lhV.js} +2 -2
  164. package/build/server/chunks/{state-CpLVNZq7.js.map → state-DBjl9lhV.js.map} +1 -1
  165. package/build/server/index.js +1 -1
  166. package/build/server/index.js.map +1 -1
  167. package/build/server/manifest.js +33 -17
  168. package/build/server/manifest.js.map +1 -1
  169. package/cli/commands/dashboard.ts +14 -0
  170. package/cli/commands/init.ts +26 -0
  171. package/cli/commands/theme.ts +62 -0
  172. package/cli/unspa.ts +38 -2
  173. package/cli/util/context-files.ts +5 -0
  174. package/cli/util/theme.ts +34 -0
  175. package/mcp-server/sync-notifier.ts +88 -35
  176. package/package.json +2 -1
  177. package/src/app.css +187 -0
  178. package/src/app.html +15 -1
  179. package/src/features/behavior-model/domain/services/BehaviorGraphModel.ts +531 -0
  180. package/src/features/behavior-model/presentation/adapters/VisBehaviorGraphRenderer.ts +492 -0
  181. package/src/features/behavior-model/presentation/components/BehaviorGraph.svelte +370 -0
  182. package/src/features/behavior-model/presentation/components/FeatureHeader.svelte +13 -5
  183. package/src/features/behavior-model/presentation/view-models/BehaviorGraphTheme.ts +43 -0
  184. package/src/features/builder-mode/domain/BuilderModeDashboard.ts +7 -1
  185. package/src/features/builder-mode/presentation/components/BuilderModeDashboard.svelte +78 -16
  186. package/src/features/builder-mode/presentation/components/BuilderTagChips.svelte +25 -0
  187. package/src/features/builder-mode/presentation/stores/builderModeStore.svelte.ts +247 -3
  188. package/src/features/projects/presentation/components/ProjectEditor.svelte +7 -0
  189. package/src/features/simulator/application/use-cases/RunScenarios.ts +15 -1
  190. package/src/hooks.server.ts +11 -1
  191. package/src/lib/theme/registry.ts +77 -0
  192. package/src/lib/theme/themeStore.svelte.ts +64 -0
  193. package/src/routes/+layout.svelte +184 -30
  194. package/src/routes/features/[id]/graph/+page.svelte +34 -0
  195. package/src/routes/projects/[id]/graph/+page.svelte +79 -0
  196. package/src/shared/presentation/components/ProgressBar.svelte +1 -1
  197. package/src/shared/presentation/toast/SyncToast.svelte +14 -3
  198. package/src/shared/presentation/toast/viewLinkResolver.ts +32 -0
  199. package/static/lyriks_logo.svg +148 -0
  200. package/build/client/_app/immutable/assets/0.DFMDYAU9.css +0 -1
  201. package/build/client/_app/immutable/assets/0.DFMDYAU9.css.br +0 -0
  202. package/build/client/_app/immutable/assets/0.DFMDYAU9.css.gz +0 -0
  203. package/build/client/_app/immutable/chunks/BO66rBOa2.js +0 -1
  204. package/build/client/_app/immutable/chunks/BO66rBOa2.js.br +0 -0
  205. package/build/client/_app/immutable/chunks/BO66rBOa2.js.gz +0 -0
  206. package/build/client/_app/immutable/chunks/DBJWcC6Y.js.br +0 -0
  207. package/build/client/_app/immutable/chunks/DBJWcC6Y.js.gz +0 -0
  208. package/build/client/_app/immutable/chunks/DHoA038D.js +0 -1
  209. package/build/client/_app/immutable/chunks/DHoA038D.js.br +0 -2
  210. package/build/client/_app/immutable/chunks/DHoA038D.js.gz +0 -0
  211. package/build/client/_app/immutable/chunks/DatGSObE.js.br +0 -0
  212. package/build/client/_app/immutable/chunks/DatGSObE.js.gz +0 -0
  213. package/build/client/_app/immutable/chunks/DjWKKtqp.js +0 -1
  214. package/build/client/_app/immutable/chunks/DjWKKtqp.js.br +0 -1
  215. package/build/client/_app/immutable/chunks/DjWKKtqp.js.gz +0 -0
  216. package/build/client/_app/immutable/chunks/Dq0DUAz1.js +0 -1
  217. package/build/client/_app/immutable/chunks/Dq0DUAz1.js.br +0 -0
  218. package/build/client/_app/immutable/chunks/Dq0DUAz1.js.gz +0 -0
  219. package/build/client/_app/immutable/chunks/iQu0D9Ux.js +0 -1
  220. package/build/client/_app/immutable/chunks/iQu0D9Ux.js.br +0 -0
  221. package/build/client/_app/immutable/chunks/iQu0D9Ux.js.gz +0 -0
  222. package/build/client/_app/immutable/entry/app.CLgh6Mx_.js +0 -2
  223. package/build/client/_app/immutable/entry/app.CLgh6Mx_.js.br +0 -0
  224. package/build/client/_app/immutable/entry/app.CLgh6Mx_.js.gz +0 -0
  225. package/build/client/_app/immutable/entry/start.D5GPCQZD.js +0 -1
  226. package/build/client/_app/immutable/entry/start.D5GPCQZD.js.br +0 -0
  227. package/build/client/_app/immutable/entry/start.D5GPCQZD.js.gz +0 -0
  228. package/build/client/_app/immutable/nodes/0.BOoI-hsu.js +0 -4
  229. package/build/client/_app/immutable/nodes/0.BOoI-hsu.js.br +0 -0
  230. package/build/client/_app/immutable/nodes/0.BOoI-hsu.js.gz +0 -0
  231. package/build/client/_app/immutable/nodes/1.KYdA6ppX.js.br +0 -2
  232. package/build/client/_app/immutable/nodes/1.KYdA6ppX.js.gz +0 -0
  233. package/build/client/_app/immutable/nodes/2.DBz20KgG.js.br +0 -0
  234. package/build/client/_app/immutable/nodes/2.DBz20KgG.js.gz +0 -0
  235. package/build/client/_app/immutable/nodes/3.19DIoFtw.js +0 -1
  236. package/build/client/_app/immutable/nodes/3.19DIoFtw.js.br +0 -0
  237. package/build/client/_app/immutable/nodes/3.19DIoFtw.js.gz +0 -0
  238. package/build/client/_app/immutable/nodes/4.BvMzqBJj.js.br +0 -0
  239. package/build/client/_app/immutable/nodes/4.BvMzqBJj.js.gz +0 -0
  240. package/build/client/_app/immutable/nodes/5.Dq6obSGG.js +0 -42
  241. package/build/client/_app/immutable/nodes/5.Dq6obSGG.js.br +0 -0
  242. package/build/client/_app/immutable/nodes/5.Dq6obSGG.js.gz +0 -0
  243. package/build/client/_app/immutable/nodes/6.BOHISqs-.js.br +0 -0
  244. package/build/client/_app/immutable/nodes/6.BOHISqs-.js.gz +0 -0
  245. package/build/client/_app/immutable/nodes/7.CemgNJfw.js.br +0 -0
  246. package/build/client/_app/immutable/nodes/7.CemgNJfw.js.gz +0 -0
  247. package/build/client/_app/immutable/nodes/8.DejSfIYh.js +0 -5
  248. package/build/client/_app/immutable/nodes/8.DejSfIYh.js.br +0 -0
  249. package/build/client/_app/immutable/nodes/8.DejSfIYh.js.gz +0 -0
  250. package/build/client/_app/immutable/nodes/9.CMW6a2Lg.js.br +0 -0
  251. package/build/client/_app/immutable/nodes/9.CMW6a2Lg.js.gz +0 -0
  252. package/build/server/chunks/1-DPpKAKXV.js +0 -9
  253. package/build/server/chunks/5-D7OCJpxD.js +0 -9
  254. package/build/server/chunks/5-D7OCJpxD.js.map +0 -1
  255. package/build/server/chunks/8-CVO-E-sf.js +0 -9
  256. package/build/server/chunks/8-CVO-E-sf.js.map +0 -1
  257. package/build/server/chunks/9-DxT1baO5.js +0 -9
  258. package/build/server/chunks/9-DxT1baO5.js.map +0 -1
  259. package/build/server/chunks/ProgressBar-CfhccQ83.js.map +0 -1
  260. package/build/server/chunks/_layout.svelte-BREws55o.js.map +0 -1
  261. package/build/server/chunks/_page.svelte-BqSC-1vK.js.map +0 -1
  262. package/build/server/chunks/_page.svelte-BtI2zZ_Z.js.map +0 -1
  263. package/build/server/chunks/_page.svelte-Zf9H4KOP.js.map +0 -1
  264. package/build/server/chunks/builderModeStore.svelte-ihupr-3p.js.map +0 -1
  265. package/build/server/chunks/client-DfpLcAZ9.js +0 -24
  266. package/build/server/chunks/hooks.server-Rv301GTB.js.map +0 -1
  267. package/build/server/chunks/reconcile-Dv7jS3C8.js.map +0 -1
  268. /package/build/client/_app/immutable/assets/{9.nv0I59TU.css → 11.nv0I59TU.css} +0 -0
  269. /package/build/client/_app/immutable/assets/{9.nv0I59TU.css.br → 11.nv0I59TU.css.br} +0 -0
  270. /package/build/client/_app/immutable/assets/{9.nv0I59TU.css.gz → 11.nv0I59TU.css.gz} +0 -0
@@ -0,0 +1,370 @@
1
+ <script lang="ts">
2
+ import { onDestroy, onMount } from 'svelte';
3
+ import type { Feature } from '$features/behavior-model/domain/entities/Feature';
4
+ import {
5
+ ALL_BEHAVIOR_GRAPH_EDGE_KINDS,
6
+ ALL_BEHAVIOR_GRAPH_NODE_TYPES,
7
+ buildBehaviorGraph,
8
+ deriveBehaviorGraphView,
9
+ filterBehaviorGraphView,
10
+ type BehaviorGraphEdgeKind
11
+ } from '$features/behavior-model/domain/services/BehaviorGraphModel';
12
+ import {
13
+ behaviorGraphEdgeTheme,
14
+ behaviorGraphNodeTheme
15
+ } from '$features/behavior-model/presentation/view-models/BehaviorGraphTheme';
16
+ import {
17
+ VisBehaviorGraphRenderer,
18
+ type VisNetworkRuntime
19
+ } from '$features/behavior-model/presentation/adapters/VisBehaviorGraphRenderer';
20
+ import type { Project } from '$features/projects/domain/entities/Project';
21
+
22
+ type Props = {
23
+ feature?: Feature;
24
+ features?: readonly Feature[];
25
+ project?: Project;
26
+ };
27
+ let { feature, features, project }: Props = $props();
28
+
29
+ let search = $state('');
30
+ let selectedId = $state<string | null>(null);
31
+ let selectedEdgeId = $state<string | null>(null);
32
+ let labelsVisible = $state(false);
33
+ let physicsSettled = $state(false);
34
+ let stabilizationProgress = $state(0);
35
+ let graphContainer = $state<HTMLDivElement | null>(null);
36
+ let renderer: VisBehaviorGraphRenderer | null = null;
37
+
38
+ let enabledKinds = $state<Record<BehaviorGraphEdgeKind, boolean>>({
39
+ contains: true,
40
+ reads: true,
41
+ writes: true,
42
+ emits: true,
43
+ transitions: true,
44
+ asserts: true,
45
+ uses: true,
46
+ handles: true
47
+ });
48
+
49
+ const sourceFeatures = $derived(features ?? (feature ? [feature] : []));
50
+ const graphTitle = $derived(project?.name ?? feature?.name ?? 'Behavior graph');
51
+ const graphSubtitle = $derived(
52
+ project
53
+ ? `The whole project behavior map across ${sourceFeatures.length} feature${sourceFeatures.length === 1 ? '' : 's'} from the shared Unspa hub.`
54
+ : 'A connected map of the executable behavior model from the shared Unspa hub.'
55
+ );
56
+ const backHref = $derived(project ? `/projects/${project.id}` : feature ? `/features/${feature.id}` : '/projects');
57
+ const backLabel = $derived(project ? 'Back to project' : 'Back to editor');
58
+
59
+ const rawGraph = $derived(buildBehaviorGraph(sourceFeatures, project));
60
+ const graph = $derived(deriveBehaviorGraphView(rawGraph));
61
+ const filteredGraph = $derived(
62
+ filterBehaviorGraphView(graph, {
63
+ search,
64
+ enabledKinds
65
+ })
66
+ );
67
+
68
+ const selectedNode = $derived(selectedId ? graph.nodeById.get(selectedId) ?? null : null);
69
+ const selectedEdge = $derived(
70
+ selectedEdgeId ? filteredGraph.edges.find((edge) => edge.id === selectedEdgeId) ?? null : null
71
+ );
72
+ const selectedEdgeFrom = $derived(selectedEdge ? graph.nodeById.get(selectedEdge.from) ?? null : null);
73
+ const selectedEdgeTo = $derived(selectedEdge ? graph.nodeById.get(selectedEdge.to) ?? null : null);
74
+ const centralNodes = $derived([...graph.nodes].sort((a, b) => b.degree - a.degree).slice(0, 6));
75
+ const countsByType = $derived.by(() => {
76
+ const counts = new Map<string, number>();
77
+ for (const node of graph.nodes) counts.set(node.type, (counts.get(node.type) ?? 0) + 1);
78
+ return counts;
79
+ });
80
+
81
+ const renderGraph = (): void => {
82
+ renderer?.render({
83
+ graph: filteredGraph,
84
+ selectedId,
85
+ selectedEdgeId,
86
+ labelsVisible,
87
+ nodeTheme: behaviorGraphNodeTheme,
88
+ edgeTheme: behaviorGraphEdgeTheme
89
+ });
90
+ };
91
+
92
+ const toggleKind = (kind: BehaviorGraphEdgeKind): void => {
93
+ enabledKinds = { ...enabledKinds, [kind]: !enabledKinds[kind] };
94
+ };
95
+
96
+ const resetView = (): void => {
97
+ selectedId = null;
98
+ selectedEdgeId = null;
99
+ search = '';
100
+ renderer?.fit();
101
+ };
102
+
103
+ const focusNode = (nodeId: string): void => {
104
+ selectedId = nodeId;
105
+ selectedEdgeId = null;
106
+ renderer?.select(nodeId, null);
107
+ };
108
+
109
+ $effect(() => {
110
+ filteredGraph.nodes;
111
+ filteredGraph.edges;
112
+ labelsVisible;
113
+ selectedId;
114
+ selectedEdgeId;
115
+ renderGraph();
116
+ });
117
+
118
+ onMount(() => {
119
+ let disposed = false;
120
+ const handleResize = (): void => renderer?.fit();
121
+ void (async () => {
122
+ const runtime = (await import('vis-network/standalone')) as VisNetworkRuntime;
123
+ if (disposed || !graphContainer) return;
124
+ renderer = new VisBehaviorGraphRenderer(runtime, graphContainer, {
125
+ onSelectedNodeChange: (id) => {
126
+ selectedId = id;
127
+ },
128
+ onSelectedEdgeChange: (id) => {
129
+ selectedEdgeId = id;
130
+ },
131
+ onStabilizationProgress: (progress) => {
132
+ stabilizationProgress = progress;
133
+ },
134
+ onSettledChange: (settled) => {
135
+ physicsSettled = settled;
136
+ }
137
+ });
138
+ renderGraph();
139
+ window.addEventListener('resize', handleResize);
140
+ })();
141
+ return () => {
142
+ disposed = true;
143
+ window.removeEventListener('resize', handleResize);
144
+ };
145
+ });
146
+
147
+ onDestroy(() => {
148
+ renderer?.destroy();
149
+ renderer = null;
150
+ });
151
+ </script>
152
+
153
+ <section class="space-y-4">
154
+ <div class="overflow-hidden rounded-lg border border-slate-200 bg-white shadow-sm shadow-slate-950/5">
155
+ <div class="border-b border-slate-200 bg-slate-950 px-4 py-4 text-white">
156
+ <div class="flex flex-wrap items-start justify-between gap-4">
157
+ <div class="min-w-0">
158
+ <p class="text-xs font-semibold uppercase tracking-wide text-cyan-200">Behavior Graph</p>
159
+ <h2 class="mt-1 truncate text-2xl font-semibold">{graphTitle}</h2>
160
+ <p class="mt-1 max-w-3xl text-sm leading-6 text-slate-300">{graphSubtitle}</p>
161
+ </div>
162
+ <a
163
+ href={backHref}
164
+ class="rounded-md border border-white/20 px-3 py-2 text-sm font-medium text-white hover:bg-white/10"
165
+ >
166
+ {backLabel}
167
+ </a>
168
+ </div>
169
+ </div>
170
+
171
+ <div class="grid gap-0 lg:grid-cols-[minmax(0,1fr)_320px]">
172
+ <div class="min-w-0 border-b border-slate-200 lg:border-b-0 lg:border-r">
173
+ <div class="flex flex-wrap items-center gap-3 border-b border-slate-200 bg-slate-50 px-4 py-3">
174
+ <input
175
+ type="search"
176
+ bind:value={search}
177
+ placeholder="Search nodes"
178
+ class="w-full rounded-md border border-slate-300 bg-white px-3 py-2 text-sm outline-none focus:border-brand-700 sm:w-64"
179
+ />
180
+ <button
181
+ type="button"
182
+ class="rounded-md border border-slate-300 px-3 py-2 text-sm font-medium text-slate-700 hover:bg-white"
183
+ onclick={() => renderer?.relayout(filteredGraph.nodes.length)}
184
+ >
185
+ Re-layout
186
+ </button>
187
+ <button
188
+ type="button"
189
+ class="rounded-md border border-slate-300 px-3 py-2 text-sm font-medium text-slate-700 hover:bg-white"
190
+ onclick={() => renderer?.fit()}
191
+ >
192
+ Fit
193
+ </button>
194
+ <div class="flex rounded-md border border-slate-300 bg-white p-1">
195
+ <button
196
+ type="button"
197
+ class="rounded px-2.5 py-1 text-sm font-semibold text-slate-700 hover:bg-slate-50"
198
+ aria-label="Zoom out"
199
+ onclick={() => renderer?.zoomBy(0.72)}
200
+ >
201
+ -
202
+ </button>
203
+ <button
204
+ type="button"
205
+ class="rounded px-2.5 py-1 text-sm font-semibold text-slate-700 hover:bg-slate-50"
206
+ aria-label="Zoom in"
207
+ onclick={() => renderer?.zoomBy(1.38)}
208
+ >
209
+ +
210
+ </button>
211
+ </div>
212
+ <button
213
+ type="button"
214
+ class="rounded-md border border-slate-300 px-3 py-2 text-sm font-medium text-slate-700 hover:bg-white"
215
+ onclick={resetView}
216
+ >
217
+ Reset
218
+ </button>
219
+ <button
220
+ type="button"
221
+ class="rounded-md border border-slate-300 px-3 py-2 text-sm font-medium {labelsVisible ? 'bg-slate-900 text-white' : 'text-slate-700 hover:bg-white'}"
222
+ onclick={() => (labelsVisible = !labelsVisible)}
223
+ >
224
+ Labels
225
+ </button>
226
+ <div class="flex flex-wrap gap-1">
227
+ {#each ALL_BEHAVIOR_GRAPH_EDGE_KINDS as kind}
228
+ <button
229
+ type="button"
230
+ class="rounded-full border px-2.5 py-1 text-xs font-medium {enabledKinds[kind]
231
+ ? 'border-slate-300 bg-white text-slate-800'
232
+ : 'border-slate-200 bg-slate-100 text-slate-400'}"
233
+ onclick={() => toggleKind(kind)}
234
+ >
235
+ {behaviorGraphEdgeTheme[kind].label}
236
+ </button>
237
+ {/each}
238
+ </div>
239
+ </div>
240
+
241
+ <div class="graph-canvas relative">
242
+ <div
243
+ bind:this={graphContainer}
244
+ class="h-[76vh] min-h-[720px] w-full"
245
+ aria-label={`Interactive behavior graph for ${graphTitle}`}
246
+ ></div>
247
+ <div class="pointer-events-none absolute bottom-3 left-3 rounded-md border border-slate-200 bg-white/88 px-3 py-2 text-xs text-slate-600 shadow-sm backdrop-blur">
248
+ ForceAtlas2 settles the graph, then physics stops for smooth panning. Wheel, pinch, or +/- to zoom.
249
+ </div>
250
+ {#if !physicsSettled && stabilizationProgress < 100}
251
+ <div class="pointer-events-none absolute left-1/2 top-3 w-64 -translate-x-1/2 rounded-md border border-slate-200 bg-white/90 p-3 text-xs text-slate-600 shadow-sm backdrop-blur">
252
+ <div class="flex justify-between font-medium text-slate-800">
253
+ <span>Stabilizing layout</span>
254
+ <span>{stabilizationProgress}%</span>
255
+ </div>
256
+ <div class="mt-2 h-1.5 overflow-hidden rounded-full bg-slate-200">
257
+ <div class="h-full rounded-full bg-brand-500 transition-all" style={`width:${stabilizationProgress}%`}></div>
258
+ </div>
259
+ </div>
260
+ {/if}
261
+ </div>
262
+ </div>
263
+
264
+ <aside class="space-y-4 p-4">
265
+ <div>
266
+ <h3 class="text-sm font-semibold text-slate-950">Graph Summary</h3>
267
+ <div class="mt-3 grid grid-cols-2 gap-2 text-sm">
268
+ <div class="rounded-md bg-slate-100 p-3">
269
+ <div class="text-2xl font-semibold text-slate-950">{graph.nodes.length}</div>
270
+ <div class="text-xs text-slate-500">nodes</div>
271
+ </div>
272
+ <div class="rounded-md bg-slate-100 p-3">
273
+ <div class="text-2xl font-semibold text-slate-950">{graph.edges.length}</div>
274
+ <div class="text-xs text-slate-500">edges</div>
275
+ </div>
276
+ </div>
277
+ </div>
278
+
279
+ <div>
280
+ <h3 class="text-sm font-semibold text-slate-950">Node Types</h3>
281
+ <div class="mt-2 flex flex-wrap gap-1.5">
282
+ {#each ALL_BEHAVIOR_GRAPH_NODE_TYPES as type}
283
+ {#if countsByType.get(type)}
284
+ <span
285
+ class="inline-flex items-center gap-1 rounded-full border border-slate-200 bg-white px-2 py-1 text-xs text-slate-700"
286
+ >
287
+ <span
288
+ class="h-2 w-2 rounded-full"
289
+ style={`background:${behaviorGraphNodeTheme[type].fill}`}
290
+ ></span>
291
+ {behaviorGraphNodeTheme[type].label} {countsByType.get(type)}
292
+ </span>
293
+ {/if}
294
+ {/each}
295
+ </div>
296
+ </div>
297
+
298
+ <div>
299
+ <h3 class="text-sm font-semibold text-slate-950">Central Nodes</h3>
300
+ <div class="mt-2 space-y-2">
301
+ {#each centralNodes as node}
302
+ <button
303
+ type="button"
304
+ class="w-full rounded-md border border-slate-200 bg-white px-3 py-2 text-left hover:border-brand-300 hover:bg-brand-50"
305
+ onclick={() => focusNode(node.id)}
306
+ >
307
+ <span class="block truncate text-sm font-medium text-slate-900">{node.label}</span>
308
+ <span class="text-xs text-slate-500">{behaviorGraphNodeTheme[node.type].label} / {node.degree} links</span>
309
+ </button>
310
+ {/each}
311
+ </div>
312
+ </div>
313
+
314
+ <div class="rounded-md border border-slate-200 bg-slate-50 p-3">
315
+ {#if selectedNode}
316
+ <p class="text-xs font-semibold uppercase tracking-wide text-slate-500">
317
+ {behaviorGraphNodeTheme[selectedNode.type].label}
318
+ </p>
319
+ <h3 class="mt-1 text-base font-semibold text-slate-950">{selectedNode.label}</h3>
320
+ {#if selectedNode.detail}
321
+ <p class="mt-2 text-sm leading-6 text-slate-600">{selectedNode.detail}</p>
322
+ {/if}
323
+ <p class="mt-2 text-xs text-slate-500">{selectedNode.degree} graph links</p>
324
+ {#if selectedNode.href}
325
+ <a class="mt-3 inline-flex text-sm font-medium text-brand-700 hover:underline" href={selectedNode.href}>
326
+ Open in editor
327
+ </a>
328
+ {/if}
329
+ {:else if selectedEdge}
330
+ <p class="text-xs font-semibold uppercase tracking-wide text-slate-500">
331
+ {behaviorGraphEdgeTheme[selectedEdge.kind].label}
332
+ </p>
333
+ <h3 class="mt-1 text-base font-semibold text-slate-950">{selectedEdge.label ?? 'Behavior link'}</h3>
334
+ <p class="mt-2 text-sm leading-6 text-slate-600">
335
+ {selectedEdgeFrom?.label ?? selectedEdge.from} -> {selectedEdgeTo?.label ?? selectedEdge.to}
336
+ </p>
337
+ <p class="mt-2 text-xs text-slate-500">Click another node or edge to move the highlighted neighborhood.</p>
338
+ {:else}
339
+ <h3 class="text-sm font-semibold text-slate-950">Explore the Map</h3>
340
+ <p class="mt-2 text-sm leading-6 text-slate-600">
341
+ Click a node or edge to highlight its linked behavior. Hover highlighting is disabled so
342
+ the canvas stays lighter on slower laptops.
343
+ </p>
344
+ {/if}
345
+ </div>
346
+ </aside>
347
+ </div>
348
+ </div>
349
+ </section>
350
+
351
+ <style>
352
+ .graph-canvas {
353
+ background:
354
+ radial-gradient(circle at 50% 45%, rgba(34, 211, 238, 0.12), transparent 34rem),
355
+ radial-gradient(circle at 15% 20%, rgba(14, 116, 144, 0.08), transparent 22rem),
356
+ linear-gradient(#f8fafc, #eef6fb);
357
+ }
358
+
359
+ .graph-canvas::before {
360
+ position: absolute;
361
+ inset: 0;
362
+ pointer-events: none;
363
+ content: '';
364
+ background-image:
365
+ linear-gradient(rgba(15, 23, 42, 0.045) 1px, transparent 1px),
366
+ linear-gradient(90deg, rgba(15, 23, 42, 0.045) 1px, transparent 1px);
367
+ background-size: 28px 28px;
368
+ mask-image: radial-gradient(circle at center, black 0, black 55%, transparent 100%);
369
+ }
370
+ </style>
@@ -110,11 +110,19 @@
110
110
  {/if}
111
111
  </div>
112
112
 
113
- <div class="flex shrink-0 flex-wrap items-center gap-2 rounded-xl border border-slate-200 bg-white p-1.5 shadow-sm shadow-slate-950/5">
114
- <div class="flex rounded-lg border border-hairline bg-white p-1">
115
- <button
116
- type="button"
117
- title={undoTitle}
113
+ <div class="flex shrink-0 flex-wrap items-center gap-2 rounded-xl border border-slate-200 bg-white p-1.5 shadow-sm shadow-slate-950/5">
114
+ <div class="flex rounded-lg border border-hairline bg-white p-1">
115
+ <a
116
+ href={`/features/${feature.id}/graph`}
117
+ title="Open behavior graph"
118
+ aria-label="Open behavior graph"
119
+ class="rounded-md px-2 py-1.5 text-xs font-medium text-slate-700 hover:bg-slate-50"
120
+ >
121
+ Graph
122
+ </a>
123
+ <button
124
+ type="button"
125
+ title={undoTitle}
118
126
  aria-label={undoTitle}
119
127
  class="rounded-md px-2 py-1.5 text-xs font-medium text-slate-700 hover:bg-slate-50 disabled:cursor-not-allowed disabled:opacity-40"
120
128
  disabled={!featureStore.canUndo}
@@ -0,0 +1,43 @@
1
+ import type {
2
+ BehaviorGraphEdgeKind,
3
+ BehaviorGraphNodeType
4
+ } from '$features/behavior-model/domain/services/BehaviorGraphModel';
5
+
6
+ export type BehaviorGraphNodeTheme = {
7
+ readonly label: string;
8
+ readonly fill: string;
9
+ readonly stroke: string;
10
+ };
11
+
12
+ export type BehaviorGraphEdgeTheme = {
13
+ readonly label: string;
14
+ readonly color: string;
15
+ };
16
+
17
+ export const behaviorGraphNodeTheme: Record<BehaviorGraphNodeType, BehaviorGraphNodeTheme> = {
18
+ feature: { label: 'Feature', fill: '#0f172a', stroke: '#020617' },
19
+ surface: { label: 'Surface', fill: '#0e7490', stroke: '#155e75' },
20
+ action: { label: 'Action', fill: '#2563eb', stroke: '#1d4ed8' },
21
+ state: { label: 'State', fill: '#7c3aed', stroke: '#6d28d9' },
22
+ event: { label: 'Event', fill: '#db2777', stroke: '#be185d' },
23
+ parameter: { label: 'Parameter', fill: '#0891b2', stroke: '#0e7490' },
24
+ rule: { label: 'Rule', fill: '#ea580c', stroke: '#c2410c' },
25
+ effect: { label: 'Effect', fill: '#16a34a', stroke: '#15803d' },
26
+ invariant: { label: 'Invariant', fill: '#ca8a04', stroke: '#a16207' },
27
+ scenario: { label: 'Scenario', fill: '#475569', stroke: '#334155' },
28
+ persona: { label: 'Persona', fill: '#0f766e', stroke: '#115e59' },
29
+ resource: { label: 'Resource', fill: '#4f46e5', stroke: '#4338ca' },
30
+ entity: { label: 'Entity', fill: '#9333ea', stroke: '#7e22ce' },
31
+ valueSet: { label: 'Value set', fill: '#64748b', stroke: '#475569' }
32
+ };
33
+
34
+ export const behaviorGraphEdgeTheme: Record<BehaviorGraphEdgeKind, BehaviorGraphEdgeTheme> = {
35
+ contains: { label: 'Contains', color: '#94a3b8' },
36
+ reads: { label: 'Reads', color: '#7c3aed' },
37
+ writes: { label: 'Writes', color: '#16a34a' },
38
+ emits: { label: 'Emits', color: '#db2777' },
39
+ transitions: { label: 'Transitions', color: '#0891b2' },
40
+ asserts: { label: 'Asserts', color: '#ca8a04' },
41
+ uses: { label: 'Uses', color: '#475569' },
42
+ handles: { label: 'Handles', color: '#2563eb' }
43
+ };
@@ -20,6 +20,10 @@ export type BuilderModeFeature = {
20
20
  readonly maturityPercentage: number;
21
21
  readonly implementationPercentage: number | null;
22
22
  readonly suggestions: readonly BuilderModeSuggestion[];
23
+ // The surface this feature (action) lives on. A surface is a higher-level
24
+ // grouping — the Builder lists features under their surface as a heading.
25
+ readonly surfaceId: string;
26
+ readonly surfaceName: string;
23
27
  };
24
28
 
25
29
  export type BuilderModeCoreFeature = {
@@ -137,7 +141,9 @@ export const buildBuilderModeDashboard = (
137
141
  implementationPercentage,
138
142
  // Per-action advice was deterministic noise; suggestions now live at
139
143
  // the feature level as the model's Evolutions.
140
- suggestions: []
144
+ suggestions: [],
145
+ surfaceId: String(surface.id),
146
+ surfaceName: surface.name
141
147
  };
142
148
  })
143
149
  );
@@ -1,7 +1,7 @@
1
1
  <script lang="ts">
2
2
  import { onDestroy, onMount, tick } from 'svelte';
3
3
  import { page } from '$app/state';
4
- import { replaceState } from '$app/navigation';
4
+ import { afterNavigate, replaceState } from '$app/navigation';
5
5
  import { fade, fly, slide } from 'svelte/transition';
6
6
  import { flip } from 'svelte/animate';
7
7
  import { cubicOut } from 'svelte/easing';
@@ -10,6 +10,7 @@
10
10
  import { humanizeTagText } from '$shared/domain/Tags';
11
11
  import GameScore from './GameScore.svelte';
12
12
  import { builderModeStore } from '$features/builder-mode/presentation/stores/builderModeStore.svelte';
13
+ import { registerViewLinkResolver } from '$shared/presentation/toast/viewLinkResolver';
13
14
  import type {
14
15
  BuilderModeFeature,
15
16
  BuilderModeSuggestion
@@ -191,6 +192,7 @@
191
192
  });
192
193
 
193
194
  let unsubscribeSync: (() => void) | null = null;
195
+ let unregisterViewLink: (() => void) | null = null;
194
196
 
195
197
  // Keep tag colors resolvable in this view: load the palette and feed it the
196
198
  // tag types as the dashboard surfaces them (same handshake the expert pages
@@ -219,28 +221,54 @@
219
221
  if (url.search !== before) replaceState(`${url.pathname}${url.search}`, {});
220
222
  });
221
223
 
224
+ // The other half of the URL ↔ selection binding, but driven by real
225
+ // navigations only — the activity-toast "View" links into the Builder and
226
+ // browser back/forward. Crucially NOT a reactive `page.url` effect: that
227
+ // raced the writer above (on a card click it would run before the writer
228
+ // had reflected the new selection into the URL, read the stale empty query,
229
+ // and revert the click). `afterNavigate` fires on link clicks / popstate but
230
+ // NOT on the writer's `replaceState`, so there's no race. The initial load
231
+ // (`from` is null) is left to onMount's restore.
232
+ afterNavigate((nav) => {
233
+ if (!nav.from) return;
234
+ const params = page.url.searchParams;
235
+ builderModeStore.applySelection(params.get('project'), params.get('core'));
236
+ });
237
+
222
238
  onMount(() => {
223
239
  // Restore selection from the URL before the sync effect runs.
224
240
  const params = page.url.searchParams;
225
- const pid = params.get('project');
226
- const cid = params.get('core');
227
- if (pid) {
228
- builderModeStore.selectProject(pid);
229
- if (cid) builderModeStore.selectCoreFeature(cid);
230
- }
241
+ const initialProject = params.get('project');
242
+ builderModeStore.applySelection(initialProject, params.get('core'));
231
243
  urlRestored = true;
232
244
 
233
- void builderModeStore.refresh();
245
+ // Teach the activity-toast to deep-link into the Builder while it's open
246
+ // (falling back to the Expert route when an entity isn't shown here).
247
+ unregisterViewLink = registerViewLinkResolver((kind, id) =>
248
+ builderModeStore.deepLinkFor(kind, id)
249
+ );
250
+
251
+ // Deep-linked to one project → load just that project (the rest of the
252
+ // list fills in lazily if the user goes back to all projects). No project
253
+ // in the URL → load the full list up front.
254
+ if (initialProject) {
255
+ void builderModeStore.loadProjectOnly(initialProject);
256
+ } else {
257
+ void builderModeStore.refresh();
258
+ }
234
259
  void builderModeStore.refreshTagPalette();
235
260
  unsubscribeSync = builderModeStore.subscribeSync((event) => {
236
- // Refresh on any change that can move what the Builder shows: project /
237
- // feature edits and implementation-status reports (the Built dials).
261
+ // Scoped, in-place update rebuild only the affected project's card
262
+ // (like the Expert view's refetchOne) instead of the whole dashboard,
263
+ // so scroll / selection / the queue widget / expanded descriptions are
264
+ // preserved. The store decides project vs feature scope and only does a
265
+ // full refresh for a genuinely new entity.
238
266
  if (
239
267
  event.kind === 'project' ||
240
268
  event.kind === 'feature' ||
241
269
  event.kind === 'implementation-status'
242
270
  ) {
243
- void builderModeStore.refreshSilent();
271
+ void builderModeStore.applySyncEvent(event);
244
272
  }
245
273
  });
246
274
  });
@@ -248,6 +276,8 @@
248
276
  onDestroy(() => {
249
277
  unsubscribeSync?.();
250
278
  unsubscribeSync = null;
279
+ unregisterViewLink?.();
280
+ unregisterViewLink = null;
251
281
  });
252
282
 
253
283
  // Floating build-queue widget: minimized (bubble) vs expanded (panel).
@@ -438,7 +468,7 @@
438
468
  {@const selectedProject = builderModeStore.selectedProject}
439
469
  <div class="space-y-6">
440
470
  <button type="button" class={ghostPill} onclick={() => builderModeStore.deselectProject()}>
441
- ← All projects ({builderModeStore.dashboard.projects.length})
471
+ ← All projects ({builderModeStore.projectCount})
442
472
  </button>
443
473
 
444
474
  <section class="overflow-hidden rounded-2xl border border-slate-700/60 bg-linear-to-b from-slate-800/45 to-slate-900 shadow-xl shadow-black/20">
@@ -533,6 +563,7 @@
533
563
  onRemove={(tag) => builderModeStore.removeCoreFeatureTag(coreFeature.id, tag)}
534
564
  onRename={(from, to) => builderModeStore.renameTag(from, to)}
535
565
  typeOptions={builderModeStore.allTagTypes}
566
+ collapsible
536
567
  class="relative z-20 mt-2"
537
568
  />
538
569
  </div>
@@ -611,9 +642,22 @@
611
642
  </div>
612
643
  {:else}
613
644
  {@const coreFeatureId = builderModeStore.selectedCoreFeature.id}
614
- <div class="space-y-3">
615
- {#each builderModeStore.visibleSelectedFeatures as feature, i (feature.id)}
616
- {@render FeatureCard(coreFeatureId, feature, i)}
645
+ <div class="space-y-4">
646
+ {#each builderModeStore.visibleSelectedSurfaces as surface (surface.surfaceId)}
647
+ <div class="space-y-2">
648
+ <!-- A surface is a higher-level grouping; its features
649
+ (actions) are the specified leaves listed under it.
650
+ Always shown so the surface level is visible even when a
651
+ core feature has a single surface. -->
652
+ <div class="flex items-center gap-2 px-1 pt-1">
653
+ <span class="text-[11px] font-semibold uppercase tracking-wide text-slate-500">{surface.surfaceName}</span>
654
+ <span class="h-px flex-1 bg-slate-800" aria-hidden="true"></span>
655
+ <span class="text-[10px] tabular-nums text-slate-600">{surface.features.length}</span>
656
+ </div>
657
+ {#each surface.features as feature, i (feature.id)}
658
+ {@render FeatureCard(coreFeatureId, feature, i)}
659
+ {/each}
660
+ </div>
617
661
  {/each}
618
662
  </div>
619
663
  {/if}
@@ -630,7 +674,7 @@
630
674
  <div class="flex shrink-0 items-center gap-3">
631
675
  {@render ActiveTagFilter()}
632
676
  <p class="text-xs text-slate-500">
633
- {builderModeStore.filteredProjects.length}{builderModeStore.searchActive || builderModeStore.tagActive ? ` of ${builderModeStore.dashboard.projects.length}` : ' total'}
677
+ {builderModeStore.filteredProjects.length}{builderModeStore.searchActive || builderModeStore.tagActive ? ` of ${builderModeStore.projectCount}` : ' total'}
634
678
  </p>
635
679
  </div>
636
680
  </div>
@@ -666,6 +710,7 @@
666
710
  onRemove={(tag) => builderModeStore.removeProjectTag(project.id, tag)}
667
711
  onRename={(from, to) => builderModeStore.renameTag(from, to)}
668
712
  typeOptions={builderModeStore.allTagTypes}
713
+ collapsible
669
714
  class="relative z-20"
670
715
  />
671
716
  </div>
@@ -677,6 +722,23 @@
677
722
  </article>
678
723
  </li>
679
724
  {/each}
725
+ <!-- Placeholders for projects still streaming in (progressive load).
726
+ Only while not searching/filtering, where the visible set equals
727
+ the loaded set. -->
728
+ {#if builderModeStore.pendingProjectCount > 0 && !builderModeStore.searchActive && !builderModeStore.tagActive}
729
+ {#each Array.from({ length: builderModeStore.pendingProjectCount }) as _, i (`pending-${i}`)}
730
+ <li class="builder-skeleton__card" aria-hidden="true">
731
+ <div class="flex items-center justify-between gap-3">
732
+ <span class="builder-skeleton__shape h-2.5 w-3/5 rounded-full"></span>
733
+ <span class="builder-skeleton__shape h-4 w-14 rounded-full"></span>
734
+ </div>
735
+ <div class="flex justify-center gap-10 pt-5">
736
+ <span class="builder-skeleton__shape size-11 rounded-full"></span>
737
+ <span class="builder-skeleton__shape size-11 rounded-full"></span>
738
+ </div>
739
+ </li>
740
+ {/each}
741
+ {/if}
680
742
  </ul>
681
743
  </section>
682
744
  {/if}