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.
- package/build/client/_app/immutable/assets/0.DSctqr5I.css +1 -0
- package/build/client/_app/immutable/assets/0.DSctqr5I.css.br +0 -0
- package/build/client/_app/immutable/assets/0.DSctqr5I.css.gz +0 -0
- package/build/client/_app/immutable/assets/BehaviorGraph.Bk0xQRZk.css +1 -0
- package/build/client/_app/immutable/assets/BehaviorGraph.Bk0xQRZk.css.br +0 -0
- package/build/client/_app/immutable/assets/BehaviorGraph.Bk0xQRZk.css.gz +0 -0
- package/build/client/_app/immutable/chunks/9nXQ5qrY2.js +1 -0
- package/build/client/_app/immutable/chunks/9nXQ5qrY2.js.br +0 -0
- package/build/client/_app/immutable/chunks/9nXQ5qrY2.js.gz +0 -0
- package/build/client/_app/immutable/chunks/B439_FLv.js +1 -0
- package/build/client/_app/immutable/chunks/B439_FLv.js.br +0 -0
- package/build/client/_app/immutable/chunks/B439_FLv.js.gz +0 -0
- package/build/client/_app/immutable/chunks/BCEY79Dw.js +1 -0
- package/build/client/_app/immutable/chunks/BCEY79Dw.js.br +2 -0
- package/build/client/_app/immutable/chunks/BCEY79Dw.js.gz +0 -0
- package/build/client/_app/immutable/chunks/{DBJWcC6Y.js → BYIrIC5L.js} +1 -1
- package/build/client/_app/immutable/chunks/BYIrIC5L.js.br +0 -0
- package/build/client/_app/immutable/chunks/BYIrIC5L.js.gz +0 -0
- package/build/client/_app/immutable/chunks/B_9TWPrx2.js +1 -0
- package/build/client/_app/immutable/chunks/B_9TWPrx2.js.br +0 -0
- package/build/client/_app/immutable/chunks/B_9TWPrx2.js.gz +0 -0
- package/build/client/_app/immutable/chunks/BvOhVtZg.js +1 -0
- package/build/client/_app/immutable/chunks/BvOhVtZg.js.br +1 -0
- package/build/client/_app/immutable/chunks/BvOhVtZg.js.gz +0 -0
- package/build/client/_app/immutable/chunks/CY3em1ma2.js +1 -0
- package/build/client/_app/immutable/chunks/CY3em1ma2.js.br +0 -0
- package/build/client/_app/immutable/chunks/CY3em1ma2.js.gz +0 -0
- package/build/client/_app/immutable/chunks/CgdRZPgI.js +1 -0
- package/build/client/_app/immutable/chunks/CgdRZPgI.js.br +0 -0
- package/build/client/_app/immutable/chunks/CgdRZPgI.js.gz +0 -0
- package/build/client/_app/immutable/chunks/D5speDV82.js +908 -0
- package/build/client/_app/immutable/chunks/D5speDV82.js.br +0 -0
- package/build/client/_app/immutable/chunks/D5speDV82.js.gz +0 -0
- package/build/client/_app/immutable/chunks/DkxwAIfJ2.js +1 -0
- package/build/client/_app/immutable/chunks/DkxwAIfJ2.js.br +0 -0
- package/build/client/_app/immutable/chunks/DkxwAIfJ2.js.gz +0 -0
- package/build/client/_app/immutable/chunks/{DatGSObE.js → OJscNS3T.js} +1 -1
- package/build/client/_app/immutable/chunks/OJscNS3T.js.br +0 -0
- package/build/client/_app/immutable/chunks/OJscNS3T.js.gz +0 -0
- package/build/client/_app/immutable/chunks/U9p9CtKG2.js +2 -0
- package/build/client/_app/immutable/chunks/U9p9CtKG2.js.br +0 -0
- package/build/client/_app/immutable/chunks/U9p9CtKG2.js.gz +0 -0
- package/build/client/_app/immutable/entry/app.Cd4S3giu.js +2 -0
- package/build/client/_app/immutable/entry/app.Cd4S3giu.js.br +0 -0
- package/build/client/_app/immutable/entry/app.Cd4S3giu.js.gz +0 -0
- package/build/client/_app/immutable/entry/start.C3xXQVkq.js +1 -0
- package/build/client/_app/immutable/entry/start.C3xXQVkq.js.br +0 -0
- package/build/client/_app/immutable/entry/start.C3xXQVkq.js.gz +0 -0
- package/build/client/_app/immutable/nodes/0.dIOlQ-0y.js +4 -0
- package/build/client/_app/immutable/nodes/0.dIOlQ-0y.js.br +0 -0
- package/build/client/_app/immutable/nodes/0.dIOlQ-0y.js.gz +0 -0
- package/build/client/_app/immutable/nodes/{1.KYdA6ppX.js → 1.Dyte3Ggf.js} +1 -1
- package/build/client/_app/immutable/nodes/1.Dyte3Ggf.js.br +2 -0
- package/build/client/_app/immutable/nodes/1.Dyte3Ggf.js.gz +0 -0
- package/build/client/_app/immutable/nodes/10.ivxAosDg.js +2 -0
- package/build/client/_app/immutable/nodes/10.ivxAosDg.js.br +0 -0
- package/build/client/_app/immutable/nodes/10.ivxAosDg.js.gz +0 -0
- package/build/client/_app/immutable/nodes/{9.CMW6a2Lg.js → 11.wvMfJKC2.js} +1 -1
- package/build/client/_app/immutable/nodes/11.wvMfJKC2.js.br +0 -0
- package/build/client/_app/immutable/nodes/11.wvMfJKC2.js.gz +0 -0
- package/build/client/_app/immutable/nodes/{2.DBz20KgG.js → 2.CmPPom9Z.js} +1 -1
- package/build/client/_app/immutable/nodes/2.CmPPom9Z.js.br +0 -0
- package/build/client/_app/immutable/nodes/2.CmPPom9Z.js.gz +0 -0
- package/build/client/_app/immutable/nodes/3.D-iCGCEx.js +1 -0
- package/build/client/_app/immutable/nodes/3.D-iCGCEx.js.br +0 -0
- package/build/client/_app/immutable/nodes/3.D-iCGCEx.js.gz +0 -0
- package/build/client/_app/immutable/nodes/{4.BvMzqBJj.js → 4.DbfAvO8Z.js} +1 -1
- package/build/client/_app/immutable/nodes/4.DbfAvO8Z.js.br +0 -0
- package/build/client/_app/immutable/nodes/4.DbfAvO8Z.js.gz +0 -0
- package/build/client/_app/immutable/nodes/5.CC5Q7lVw.js +42 -0
- package/build/client/_app/immutable/nodes/5.CC5Q7lVw.js.br +0 -0
- package/build/client/_app/immutable/nodes/5.CC5Q7lVw.js.gz +0 -0
- package/build/client/_app/immutable/nodes/6.CHIjlzpO.js +1 -0
- package/build/client/_app/immutable/nodes/6.CHIjlzpO.js.br +0 -0
- package/build/client/_app/immutable/nodes/6.CHIjlzpO.js.gz +0 -0
- package/build/client/_app/immutable/nodes/{6.BOHISqs-.js → 7.Ejs18ZUc.js} +1 -1
- package/build/client/_app/immutable/nodes/7.Ejs18ZUc.js.br +0 -0
- package/build/client/_app/immutable/nodes/7.Ejs18ZUc.js.gz +0 -0
- package/build/client/_app/immutable/nodes/{7.CemgNJfw.js → 8.B-HweAc8.js} +1 -1
- package/build/client/_app/immutable/nodes/8.B-HweAc8.js.br +0 -0
- package/build/client/_app/immutable/nodes/8.B-HweAc8.js.gz +0 -0
- package/build/client/_app/immutable/nodes/9.CKPeM6tx.js +5 -0
- package/build/client/_app/immutable/nodes/9.CKPeM6tx.js.br +0 -0
- package/build/client/_app/immutable/nodes/9.CKPeM6tx.js.gz +0 -0
- package/build/client/_app/version.json +1 -1
- package/build/client/_app/version.json.br +0 -0
- package/build/client/_app/version.json.gz +0 -0
- package/build/client/lyriks_logo.svg +148 -0
- package/build/client/lyriks_logo.svg.br +0 -0
- package/build/client/lyriks_logo.svg.gz +0 -0
- package/build/server/chunks/{0-C_o0oz-N.js → 0-Co8kcANG.js} +4 -4
- package/build/server/chunks/{0-C_o0oz-N.js.map → 0-Co8kcANG.js.map} +1 -1
- package/build/server/chunks/1-BSUItTig.js +9 -0
- package/build/server/chunks/{1-DPpKAKXV.js.map → 1-BSUItTig.js.map} +1 -1
- package/build/server/chunks/10-BygvxrZp.js +9 -0
- package/build/server/chunks/10-BygvxrZp.js.map +1 -0
- package/build/server/chunks/11-DRx0tRx2.js +9 -0
- package/build/server/chunks/11-DRx0tRx2.js.map +1 -0
- package/build/server/chunks/{2-AlfFqtL1.js → 2-BQT3m1vc.js} +3 -3
- package/build/server/chunks/{2-AlfFqtL1.js.map → 2-BQT3m1vc.js.map} +1 -1
- package/build/server/chunks/{3-vbjUt_51.js → 3-DPZ9BquJ.js} +3 -3
- package/build/server/chunks/{3-vbjUt_51.js.map → 3-DPZ9BquJ.js.map} +1 -1
- package/build/server/chunks/{4-BNow4x6D.js → 4-DHo47YX6.js} +3 -3
- package/build/server/chunks/{4-BNow4x6D.js.map → 4-DHo47YX6.js.map} +1 -1
- package/build/server/chunks/5-Cp9evBAG.js +9 -0
- package/build/server/chunks/5-Cp9evBAG.js.map +1 -0
- package/build/server/chunks/6-DiBq3bOV.js +9 -0
- package/build/server/chunks/6-DiBq3bOV.js.map +1 -0
- package/build/server/chunks/{6-QQ7r8Rd5.js → 7-C4hmS0dG.js} +3 -3
- package/build/server/chunks/{6-QQ7r8Rd5.js.map → 7-C4hmS0dG.js.map} +1 -1
- package/build/server/chunks/{7-CbPLGaIG.js → 8-CFFuDzBC.js} +4 -4
- package/build/server/chunks/{7-CbPLGaIG.js.map → 8-CFFuDzBC.js.map} +1 -1
- package/build/server/chunks/9-nhhKZJrs.js +9 -0
- package/build/server/chunks/9-nhhKZJrs.js.map +1 -0
- package/build/server/chunks/BehaviorGraph-m5kYj5HH.js +757 -0
- package/build/server/chunks/BehaviorGraph-m5kYj5HH.js.map +1 -0
- package/build/server/chunks/{FeatureCard-BQOY6gJQ.js → FeatureCard-CfbXNYe8.js} +2 -2
- package/build/server/chunks/{FeatureCard-BQOY6gJQ.js.map → FeatureCard-CfbXNYe8.js.map} +1 -1
- package/build/server/chunks/{ProgressBar-CfhccQ83.js → ProgressBar-DDoQJ_C9.js} +2 -2
- package/build/server/chunks/ProgressBar-DDoQJ_C9.js.map +1 -0
- package/build/server/chunks/{ProjectsIndex-CoDrvRya.js → ProjectsIndex-DUVJ3hyL.js} +2 -2
- package/build/server/chunks/{ProjectsIndex-CoDrvRya.js.map → ProjectsIndex-DUVJ3hyL.js.map} +1 -1
- package/build/server/chunks/TransitionCatalog-B8zHs-2E.js +271 -0
- package/build/server/chunks/TransitionCatalog-B8zHs-2E.js.map +1 -0
- package/build/server/chunks/{_layout.svelte-BREws55o.js → _layout.svelte-CLTmk0xU.js} +66 -15
- package/build/server/chunks/_layout.svelte-CLTmk0xU.js.map +1 -0
- package/build/server/chunks/{_page.svelte-De508ek8.js → _page.svelte-B1nG3PKn.js} +4 -4
- package/build/server/chunks/{_page.svelte-De508ek8.js.map → _page.svelte-B1nG3PKn.js.map} +1 -1
- package/build/server/chunks/{_page.svelte-Zf9H4KOP.js → _page.svelte-BW_nbAAH.js} +11 -315
- package/build/server/chunks/_page.svelte-BW_nbAAH.js.map +1 -0
- package/build/server/chunks/{_page.svelte-BqSC-1vK.js → _page.svelte-Caq7J0jU.js} +91 -47
- package/build/server/chunks/_page.svelte-Caq7J0jU.js.map +1 -0
- package/build/server/chunks/{_page.svelte-B7hT3P8E.js → _page.svelte-Cham-dsM.js} +4 -4
- package/build/server/chunks/{_page.svelte-B7hT3P8E.js.map → _page.svelte-Cham-dsM.js.map} +1 -1
- package/build/server/chunks/_page.svelte-Dhwjwph_.js +41 -0
- package/build/server/chunks/_page.svelte-Dhwjwph_.js.map +1 -0
- package/build/server/chunks/{_page.svelte-BKKCa9H5.js → _page.svelte-DlFVT40-.js} +3 -3
- package/build/server/chunks/{_page.svelte-BKKCa9H5.js.map → _page.svelte-DlFVT40-.js.map} +1 -1
- package/build/server/chunks/{_page.svelte-BtI2zZ_Z.js → _page.svelte-NVT2dzpG.js} +176 -327
- package/build/server/chunks/_page.svelte-NVT2dzpG.js.map +1 -0
- package/build/server/chunks/{_page.svelte-BKTveFAj.js → _page.svelte-Oj-W7G5s.js} +5 -5
- package/build/server/chunks/{_page.svelte-BKTveFAj.js.map → _page.svelte-Oj-W7G5s.js.map} +1 -1
- package/build/server/chunks/_page.svelte-Z_kK2lHY.js +68 -0
- package/build/server/chunks/_page.svelte-Z_kK2lHY.js.map +1 -0
- package/build/server/chunks/{builderModeStore.svelte-ihupr-3p.js → builderModeStore.svelte-BpRIU_zP.js} +217 -6
- package/build/server/chunks/builderModeStore.svelte-BpRIU_zP.js.map +1 -0
- package/build/server/chunks/client-DeX3TC3s.js +51 -0
- package/build/server/chunks/{client-DfpLcAZ9.js.map → client-DeX3TC3s.js.map} +1 -1
- package/build/server/chunks/{error.svelte-C35KOpru.js → error.svelte-Cdjeq3L2.js} +4 -4
- package/build/server/chunks/{error.svelte-C35KOpru.js.map → error.svelte-Cdjeq3L2.js.map} +1 -1
- package/build/server/chunks/featureStore.svelte-DIYgPBVm.js +161 -0
- package/build/server/chunks/featureStore.svelte-DIYgPBVm.js.map +1 -0
- package/build/server/chunks/{hooks.server-Rv301GTB.js → hooks.server-y3jdg_sB.js} +6 -2
- package/build/server/chunks/hooks.server-y3jdg_sB.js.map +1 -0
- package/build/server/chunks/{internal-BPKrFkK1.js → internal-KYK0WpL7.js} +4 -4
- package/build/server/chunks/{internal-BPKrFkK1.js.map → internal-KYK0WpL7.js.map} +1 -1
- package/build/server/chunks/projectFeaturesStore.svelte-2o-72_vr.js +313 -0
- package/build/server/chunks/projectFeaturesStore.svelte-2o-72_vr.js.map +1 -0
- package/build/server/chunks/{reconcile-Dv7jS3C8.js → reconcile-B5xqb6-s.js} +3 -272
- package/build/server/chunks/reconcile-B5xqb6-s.js.map +1 -0
- package/build/server/chunks/registry-DqAn_hVE.js +21 -0
- package/build/server/chunks/registry-DqAn_hVE.js.map +1 -0
- package/build/server/chunks/{state-CpLVNZq7.js → state-DBjl9lhV.js} +2 -2
- package/build/server/chunks/{state-CpLVNZq7.js.map → state-DBjl9lhV.js.map} +1 -1
- package/build/server/index.js +1 -1
- package/build/server/index.js.map +1 -1
- package/build/server/manifest.js +33 -17
- package/build/server/manifest.js.map +1 -1
- package/cli/commands/dashboard.ts +14 -0
- package/cli/commands/init.ts +26 -0
- package/cli/commands/theme.ts +62 -0
- package/cli/unspa.ts +38 -2
- package/cli/util/context-files.ts +5 -0
- package/cli/util/theme.ts +34 -0
- package/mcp-server/sync-notifier.ts +88 -35
- package/package.json +2 -1
- package/src/app.css +187 -0
- package/src/app.html +15 -1
- package/src/features/behavior-model/domain/services/BehaviorGraphModel.ts +531 -0
- package/src/features/behavior-model/presentation/adapters/VisBehaviorGraphRenderer.ts +492 -0
- package/src/features/behavior-model/presentation/components/BehaviorGraph.svelte +370 -0
- package/src/features/behavior-model/presentation/components/FeatureHeader.svelte +13 -5
- package/src/features/behavior-model/presentation/view-models/BehaviorGraphTheme.ts +43 -0
- package/src/features/builder-mode/domain/BuilderModeDashboard.ts +7 -1
- package/src/features/builder-mode/presentation/components/BuilderModeDashboard.svelte +78 -16
- package/src/features/builder-mode/presentation/components/BuilderTagChips.svelte +25 -0
- package/src/features/builder-mode/presentation/stores/builderModeStore.svelte.ts +247 -3
- package/src/features/projects/presentation/components/ProjectEditor.svelte +7 -0
- package/src/features/simulator/application/use-cases/RunScenarios.ts +15 -1
- package/src/hooks.server.ts +11 -1
- package/src/lib/theme/registry.ts +77 -0
- package/src/lib/theme/themeStore.svelte.ts +64 -0
- package/src/routes/+layout.svelte +184 -30
- package/src/routes/features/[id]/graph/+page.svelte +34 -0
- package/src/routes/projects/[id]/graph/+page.svelte +79 -0
- package/src/shared/presentation/components/ProgressBar.svelte +1 -1
- package/src/shared/presentation/toast/SyncToast.svelte +14 -3
- package/src/shared/presentation/toast/viewLinkResolver.ts +32 -0
- package/static/lyriks_logo.svg +148 -0
- package/build/client/_app/immutable/assets/0.DFMDYAU9.css +0 -1
- package/build/client/_app/immutable/assets/0.DFMDYAU9.css.br +0 -0
- package/build/client/_app/immutable/assets/0.DFMDYAU9.css.gz +0 -0
- package/build/client/_app/immutable/chunks/BO66rBOa2.js +0 -1
- package/build/client/_app/immutable/chunks/BO66rBOa2.js.br +0 -0
- package/build/client/_app/immutable/chunks/BO66rBOa2.js.gz +0 -0
- package/build/client/_app/immutable/chunks/DBJWcC6Y.js.br +0 -0
- package/build/client/_app/immutable/chunks/DBJWcC6Y.js.gz +0 -0
- package/build/client/_app/immutable/chunks/DHoA038D.js +0 -1
- package/build/client/_app/immutable/chunks/DHoA038D.js.br +0 -2
- package/build/client/_app/immutable/chunks/DHoA038D.js.gz +0 -0
- package/build/client/_app/immutable/chunks/DatGSObE.js.br +0 -0
- package/build/client/_app/immutable/chunks/DatGSObE.js.gz +0 -0
- package/build/client/_app/immutable/chunks/DjWKKtqp.js +0 -1
- package/build/client/_app/immutable/chunks/DjWKKtqp.js.br +0 -1
- package/build/client/_app/immutable/chunks/DjWKKtqp.js.gz +0 -0
- package/build/client/_app/immutable/chunks/Dq0DUAz1.js +0 -1
- package/build/client/_app/immutable/chunks/Dq0DUAz1.js.br +0 -0
- package/build/client/_app/immutable/chunks/Dq0DUAz1.js.gz +0 -0
- package/build/client/_app/immutable/chunks/iQu0D9Ux.js +0 -1
- package/build/client/_app/immutable/chunks/iQu0D9Ux.js.br +0 -0
- package/build/client/_app/immutable/chunks/iQu0D9Ux.js.gz +0 -0
- package/build/client/_app/immutable/entry/app.CLgh6Mx_.js +0 -2
- package/build/client/_app/immutable/entry/app.CLgh6Mx_.js.br +0 -0
- package/build/client/_app/immutable/entry/app.CLgh6Mx_.js.gz +0 -0
- package/build/client/_app/immutable/entry/start.D5GPCQZD.js +0 -1
- package/build/client/_app/immutable/entry/start.D5GPCQZD.js.br +0 -0
- package/build/client/_app/immutable/entry/start.D5GPCQZD.js.gz +0 -0
- package/build/client/_app/immutable/nodes/0.BOoI-hsu.js +0 -4
- package/build/client/_app/immutable/nodes/0.BOoI-hsu.js.br +0 -0
- package/build/client/_app/immutable/nodes/0.BOoI-hsu.js.gz +0 -0
- package/build/client/_app/immutable/nodes/1.KYdA6ppX.js.br +0 -2
- package/build/client/_app/immutable/nodes/1.KYdA6ppX.js.gz +0 -0
- package/build/client/_app/immutable/nodes/2.DBz20KgG.js.br +0 -0
- package/build/client/_app/immutable/nodes/2.DBz20KgG.js.gz +0 -0
- package/build/client/_app/immutable/nodes/3.19DIoFtw.js +0 -1
- package/build/client/_app/immutable/nodes/3.19DIoFtw.js.br +0 -0
- package/build/client/_app/immutable/nodes/3.19DIoFtw.js.gz +0 -0
- package/build/client/_app/immutable/nodes/4.BvMzqBJj.js.br +0 -0
- package/build/client/_app/immutable/nodes/4.BvMzqBJj.js.gz +0 -0
- package/build/client/_app/immutable/nodes/5.Dq6obSGG.js +0 -42
- package/build/client/_app/immutable/nodes/5.Dq6obSGG.js.br +0 -0
- package/build/client/_app/immutable/nodes/5.Dq6obSGG.js.gz +0 -0
- package/build/client/_app/immutable/nodes/6.BOHISqs-.js.br +0 -0
- package/build/client/_app/immutable/nodes/6.BOHISqs-.js.gz +0 -0
- package/build/client/_app/immutable/nodes/7.CemgNJfw.js.br +0 -0
- package/build/client/_app/immutable/nodes/7.CemgNJfw.js.gz +0 -0
- package/build/client/_app/immutable/nodes/8.DejSfIYh.js +0 -5
- package/build/client/_app/immutable/nodes/8.DejSfIYh.js.br +0 -0
- package/build/client/_app/immutable/nodes/8.DejSfIYh.js.gz +0 -0
- package/build/client/_app/immutable/nodes/9.CMW6a2Lg.js.br +0 -0
- package/build/client/_app/immutable/nodes/9.CMW6a2Lg.js.gz +0 -0
- package/build/server/chunks/1-DPpKAKXV.js +0 -9
- package/build/server/chunks/5-D7OCJpxD.js +0 -9
- package/build/server/chunks/5-D7OCJpxD.js.map +0 -1
- package/build/server/chunks/8-CVO-E-sf.js +0 -9
- package/build/server/chunks/8-CVO-E-sf.js.map +0 -1
- package/build/server/chunks/9-DxT1baO5.js +0 -9
- package/build/server/chunks/9-DxT1baO5.js.map +0 -1
- package/build/server/chunks/ProgressBar-CfhccQ83.js.map +0 -1
- package/build/server/chunks/_layout.svelte-BREws55o.js.map +0 -1
- package/build/server/chunks/_page.svelte-BqSC-1vK.js.map +0 -1
- package/build/server/chunks/_page.svelte-BtI2zZ_Z.js.map +0 -1
- package/build/server/chunks/_page.svelte-Zf9H4KOP.js.map +0 -1
- package/build/server/chunks/builderModeStore.svelte-ihupr-3p.js.map +0 -1
- package/build/server/chunks/client-DfpLcAZ9.js +0 -24
- package/build/server/chunks/hooks.server-Rv301GTB.js.map +0 -1
- package/build/server/chunks/reconcile-Dv7jS3C8.js.map +0 -1
- /package/build/client/_app/immutable/assets/{9.nv0I59TU.css → 11.nv0I59TU.css} +0 -0
- /package/build/client/_app/immutable/assets/{9.nv0I59TU.css.br → 11.nv0I59TU.css.br} +0 -0
- /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
|
-
<
|
|
116
|
-
|
|
117
|
-
title=
|
|
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
|
|
226
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
237
|
-
//
|
|
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.
|
|
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.
|
|
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-
|
|
615
|
-
{#each builderModeStore.
|
|
616
|
-
|
|
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.
|
|
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}
|