beads-map 0.3.3 → 0.3.4

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 (30) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/app-build-manifest.json +2 -2
  3. package/.next/app-path-routes-manifest.json +1 -1
  4. package/.next/build-manifest.json +2 -2
  5. package/.next/next-minimal-server.js.nft.json +1 -1
  6. package/.next/next-server.js.nft.json +1 -1
  7. package/.next/prerender-manifest.json +1 -1
  8. package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  9. package/.next/server/app/_not-found.html +1 -1
  10. package/.next/server/app/_not-found.rsc +1 -1
  11. package/.next/server/app/api/beads.body +1 -1
  12. package/.next/server/app/index.html +1 -1
  13. package/.next/server/app/index.rsc +2 -2
  14. package/.next/server/app/page.js +3 -3
  15. package/.next/server/app/page_client-reference-manifest.js +1 -1
  16. package/.next/server/app-paths-manifest.json +5 -5
  17. package/.next/server/functions-config-manifest.json +1 -1
  18. package/.next/server/pages/404.html +1 -1
  19. package/.next/server/pages/500.html +1 -1
  20. package/.next/server/server-reference-manifest.json +1 -1
  21. package/.next/static/chunks/app/page-cf8e14cb4afc8112.js +1 -0
  22. package/.next/static/css/ade5301262971664.css +3 -0
  23. package/app/page.tsx +43 -3
  24. package/components/BeadsGraph.tsx +108 -10
  25. package/components/ContextMenu.tsx +51 -5
  26. package/package.json +1 -1
  27. package/.next/static/chunks/app/page-4a4f07fcb5bd4637.js +0 -1
  28. package/.next/static/css/df2737696baac0fa.css +0 -3
  29. /package/.next/static/{bsmkR-2y8Ra7VuoNZWLzB → JmL0suxsggbSwPxWcmUFV}/_buildManifest.js +0 -0
  30. /package/.next/static/{bsmkR-2y8Ra7VuoNZWLzB → JmL0suxsggbSwPxWcmUFV}/_ssgManifest.js +0 -0
@@ -0,0 +1,3 @@
1
+ *,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }/*
2
+ ! tailwindcss v3.4.19 | MIT License | https://tailwindcss.com
3
+ */*,:after,:before{box-sizing:border-box;border:0 solid #e5e7eb}:after,:before{--tw-content:""}:host,html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:Inter,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:JetBrains Mono,Fira Code,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}body,html{height:100%;overflow:hidden}body{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity,1));--tw-text-opacity:1;color:rgb(39 39 42/var(--tw-text-opacity,1));-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;font-feature-settings:"rlig" 1,"calt" 1}.\!container{width:100%!important}.container{width:100%}@media (min-width:640px){.\!container{max-width:640px!important}.container{max-width:640px}}@media (min-width:768px){.\!container{max-width:768px!important}.container{max-width:768px}}@media (min-width:1024px){.\!container{max-width:1024px!important}.container{max-width:1024px}}@media (min-width:1280px){.\!container{max-width:1280px!important}.container{max-width:1280px}}@media (min-width:1536px){.\!container{max-width:1536px!important}.container{max-width:1536px}}.status-badge{display:inline-flex;align-items:center;border-radius:9999px;padding:.125rem .5rem;font-size:.75rem;line-height:1rem;font-weight:500}.custom-scrollbar::-webkit-scrollbar{width:4px}.custom-scrollbar::-webkit-scrollbar-track{background-color:transparent}.custom-scrollbar::-webkit-scrollbar-thumb{border-radius:9999px;--tw-bg-opacity:1;background-color:rgb(228 228 231/var(--tw-bg-opacity,1))}.custom-scrollbar::-webkit-scrollbar-thumb:hover{--tw-bg-opacity:1;background-color:rgb(212 212 216/var(--tw-bg-opacity,1))}.visible{visibility:visible}.invisible{visibility:hidden}.collapse{visibility:collapse}.static{position:static}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.sticky{position:sticky}.inset-0{inset:0}.inset-x-0{left:0;right:0}.bottom-0{bottom:0}.bottom-2{bottom:.5rem}.bottom-4{bottom:1rem}.left-0{left:0}.left-2{left:.5rem}.left-3{left:.75rem}.left-4{left:1rem}.right-0{right:0}.right-2{right:.5rem}.right-3{right:.75rem}.top-0{top:0}.top-2{top:.5rem}.top-3{top:.75rem}.top-full{top:100%}.z-10{z-index:10}.z-20{z-index:20}.z-30{z-index:30}.z-50{z-index:50}.z-\[100\]{z-index:100}.z-\[1\]{z-index:1}.z-\[55\]{z-index:55}.z-\[56\]{z-index:56}.z-\[60\]{z-index:60}.mx-2{margin-left:.5rem;margin-right:.5rem}.mx-4{margin-left:1rem;margin-right:1rem}.mx-auto{margin-left:auto;margin-right:auto}.my-1{margin-top:.25rem;margin-bottom:.25rem}.-mr-0\.5{margin-right:-.125rem}.mb-1{margin-bottom:.25rem}.mb-1\.5{margin-bottom:.375rem}.mb-2{margin-bottom:.5rem}.mb-2\.5{margin-bottom:.625rem}.mb-3{margin-bottom:.75rem}.mb-4{margin-bottom:1rem}.mb-5{margin-bottom:1.25rem}.mb-6{margin-bottom:1.5rem}.ml-1{margin-left:.25rem}.ml-2{margin-left:.5rem}.ml-4{margin-left:1rem}.ml-auto{margin-left:auto}.mr-1\.5{margin-right:.375rem}.mt-0\.5{margin-top:.125rem}.mt-1{margin-top:.25rem}.mt-1\.5{margin-top:.375rem}.mt-2{margin-top:.5rem}.mt-3{margin-top:.75rem}.mt-5{margin-top:1.25rem}.mt-\[6px\]{margin-top:6px}.line-clamp-2{overflow:hidden;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2}.block{display:block}.inline{display:inline}.flex{display:flex}.inline-flex{display:inline-flex}.grid{display:grid}.hidden{display:none}.h-1{height:.25rem}.h-1\.5{height:.375rem}.h-12{height:3rem}.h-14{height:3.5rem}.h-2{height:.5rem}.h-2\.5{height:.625rem}.h-3{height:.75rem}.h-3\.5{height:.875rem}.h-4{height:1rem}.h-5{height:1.25rem}.h-6{height:1.5rem}.h-7{height:1.75rem}.h-8{height:2rem}.h-full{height:100%}.h-px{height:1px}.h-screen{height:100vh}.max-h-40{max-height:10rem}.max-h-72{max-height:18rem}.max-h-\[260px\]{max-height:260px}.max-h-\[60vh\]{max-height:60vh}.max-h-\[70vh\]{max-height:70vh}.max-h-\[80vh\]{max-height:80vh}.min-h-\[300px\]{min-height:300px}.w-0\.5{width:.125rem}.w-1{width:.25rem}.w-1\.5{width:.375rem}.w-12{width:3rem}.w-14{width:3.5rem}.w-2{width:.5rem}.w-2\.5{width:.625rem}.w-3{width:.75rem}.w-3\.5{width:.875rem}.w-4{width:1rem}.w-44{width:11rem}.w-5{width:1.25rem}.w-6{width:1.5rem}.w-7{width:1.75rem}.w-72{width:18rem}.w-8{width:2rem}.w-\[360px\]{width:360px}.w-\[90vw\]{width:90vw}.w-full{width:100%}.w-px{width:1px}.min-w-0{min-width:0}.min-w-\[18px\]{min-width:18px}.min-w-\[280px\]{min-width:280px}.max-w-2xl{max-width:42rem}.max-w-\[100px\]{max-width:100px}.max-w-\[140px\]{max-width:140px}.max-w-\[180px\]{max-width:180px}.max-w-\[480px\]{max-width:480px}.max-w-\[80px\]{max-width:80px}.max-w-md{max-width:28rem}.max-w-sm{max-width:24rem}.flex-1{flex:1 1 0%}.flex-shrink-0,.shrink-0{flex-shrink:0}.translate-x-0{--tw-translate-x:0px}.translate-x-0,.translate-x-full{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.translate-x-full{--tw-translate-x:100%}.translate-y-0{--tw-translate-y:0px}.translate-y-0,.translate-y-full{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.translate-y-full{--tw-translate-y:100%}.transform{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}@keyframes pulse{50%{opacity:.5}}.animate-pulse{animation:pulse 2s cubic-bezier(.4,0,.6,1) infinite}@keyframes spin{to{transform:rotate(1turn)}}.animate-spin{animation:spin 1s linear infinite}.cursor-crosshair{cursor:crosshair}.cursor-e-resize{cursor:e-resize}.cursor-n-resize{cursor:n-resize}.cursor-ne-resize{cursor:ne-resize}.cursor-pointer{cursor:pointer}.resize-none{resize:none}.resize{resize:both}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-start{align-items:flex-start}.items-center{align-items:center}.items-baseline{align-items:baseline}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-0\.5{gap:.125rem}.gap-1{gap:.25rem}.gap-1\.5{gap:.375rem}.gap-2{gap:.5rem}.gap-2\.5{gap:.625rem}.gap-3{gap:.75rem}.gap-x-3{-moz-column-gap:.75rem;column-gap:.75rem}.gap-x-4{-moz-column-gap:1rem;column-gap:1rem}.gap-y-1{row-gap:.25rem}.gap-y-1\.5{row-gap:.375rem}.space-y-0>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(0px * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(0px * var(--tw-space-y-reverse))}.space-y-1>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.25rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.25rem * var(--tw-space-y-reverse))}.space-y-1\.5>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.375rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.375rem * var(--tw-space-y-reverse))}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem * var(--tw-space-y-reverse))}.space-y-4>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(1rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1rem * var(--tw-space-y-reverse))}.space-y-5>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(1.25rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1.25rem * var(--tw-space-y-reverse))}.divide-y>:not([hidden])~:not([hidden]){--tw-divide-y-reverse:0;border-top-width:calc(1px * calc(1 - var(--tw-divide-y-reverse)));border-bottom-width:calc(1px * var(--tw-divide-y-reverse))}.divide-zinc-50>:not([hidden])~:not([hidden]){--tw-divide-opacity:1;border-color:rgb(250 250 250/var(--tw-divide-opacity,1))}.self-stretch{align-self:stretch}.overflow-hidden{overflow:hidden}.overflow-y-auto{overflow-y:auto}.truncate{overflow:hidden;text-overflow:ellipsis}.truncate,.whitespace-nowrap{white-space:nowrap}.whitespace-pre-wrap{white-space:pre-wrap}.break-words{overflow-wrap:break-word}.rounded{border-radius:.25rem}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:.5rem}.rounded-md{border-radius:.375rem}.rounded-sm{border-radius:.125rem}.rounded-xl{border-radius:.75rem}.rounded-r-lg{border-top-right-radius:.5rem;border-bottom-right-radius:.5rem}.rounded-t-2xl{border-top-left-radius:1rem;border-top-right-radius:1rem}.rounded-t-lg{border-top-left-radius:.5rem}.rounded-t-lg,.rounded-tr-lg{border-top-right-radius:.5rem}.border{border-width:1px}.border-2{border-width:2px}.border-b{border-bottom-width:1px}.border-l{border-left-width:1px}.border-t{border-top-width:1px}.border-emerald-200{--tw-border-opacity:1;border-color:rgb(167 243 208/var(--tw-border-opacity,1))}.border-emerald-500{--tw-border-opacity:1;border-color:rgb(16 185 129/var(--tw-border-opacity,1))}.border-transparent{border-color:transparent}.border-zinc-100{--tw-border-opacity:1;border-color:rgb(244 244 245/var(--tw-border-opacity,1))}.border-zinc-100\/60{border-color:hsla(240,5%,96%,.6)}.border-zinc-100\/80{border-color:hsla(240,5%,96%,.8)}.border-zinc-200{--tw-border-opacity:1;border-color:rgb(228 228 231/var(--tw-border-opacity,1))}.border-zinc-200\/60{border-color:hsla(240,6%,90%,.6)}.border-zinc-200\/80{border-color:hsla(240,6%,90%,.8)}.border-zinc-50{--tw-border-opacity:1;border-color:rgb(250 250 250/var(--tw-border-opacity,1))}.border-t-emerald-500{--tw-border-opacity:1;border-top-color:rgb(16 185 129/var(--tw-border-opacity,1))}.border-t-zinc-400{--tw-border-opacity:1;border-top-color:rgb(161 161 170/var(--tw-border-opacity,1))}.bg-amber-400{--tw-bg-opacity:1;background-color:rgb(251 191 36/var(--tw-bg-opacity,1))}.bg-amber-500{--tw-bg-opacity:1;background-color:rgb(245 158 11/var(--tw-bg-opacity,1))}.bg-black\/20{background-color:rgba(0,0,0,.2)}.bg-black\/40{background-color:rgba(0,0,0,.4)}.bg-blue-400{--tw-bg-opacity:1;background-color:rgb(96 165 250/var(--tw-bg-opacity,1))}.bg-emerald-100{--tw-bg-opacity:1;background-color:rgb(209 250 229/var(--tw-bg-opacity,1))}.bg-emerald-400{--tw-bg-opacity:1;background-color:rgb(52 211 153/var(--tw-bg-opacity,1))}.bg-emerald-50{--tw-bg-opacity:1;background-color:rgb(236 253 245/var(--tw-bg-opacity,1))}.bg-emerald-50\/30{background-color:rgba(236,253,245,.3)}.bg-emerald-50\/90{background-color:rgba(236,253,245,.9)}.bg-emerald-500{--tw-bg-opacity:1;background-color:rgb(16 185 129/var(--tw-bg-opacity,1))}.bg-emerald-600{--tw-bg-opacity:1;background-color:rgb(5 150 105/var(--tw-bg-opacity,1))}.bg-red-300{--tw-bg-opacity:1;background-color:rgb(252 165 165/var(--tw-bg-opacity,1))}.bg-red-50{--tw-bg-opacity:1;background-color:rgb(254 242 242/var(--tw-bg-opacity,1))}.bg-red-500{--tw-bg-opacity:1;background-color:rgb(239 68 68/var(--tw-bg-opacity,1))}.bg-rose-300{--tw-bg-opacity:1;background-color:rgb(253 164 175/var(--tw-bg-opacity,1))}.bg-transparent{background-color:transparent}.bg-violet-500{--tw-bg-opacity:1;background-color:rgb(139 92 246/var(--tw-bg-opacity,1))}.bg-white{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity,1))}.bg-white\/90{background-color:hsla(0,0%,100%,.9)}.bg-white\/95{background-color:hsla(0,0%,100%,.95)}.bg-zinc-100{--tw-bg-opacity:1;background-color:rgb(244 244 245/var(--tw-bg-opacity,1))}.bg-zinc-200{--tw-bg-opacity:1;background-color:rgb(228 228 231/var(--tw-bg-opacity,1))}.bg-zinc-300{--tw-bg-opacity:1;background-color:rgb(212 212 216/var(--tw-bg-opacity,1))}.bg-zinc-400{--tw-bg-opacity:1;background-color:rgb(161 161 170/var(--tw-bg-opacity,1))}.bg-zinc-50{--tw-bg-opacity:1;background-color:rgb(250 250 250/var(--tw-bg-opacity,1))}.bg-zinc-50\/30{background-color:hsla(0,0%,98%,.3)}.bg-zinc-50\/50{background-color:hsla(0,0%,98%,.5)}.bg-zinc-50\/80{background-color:hsla(0,0%,98%,.8)}.bg-zinc-50\/95{background-color:hsla(0,0%,98%,.95)}.object-cover{-o-object-fit:cover;object-fit:cover}.p-0\.5{padding:.125rem}.p-1{padding:.25rem}.p-3{padding:.75rem}.p-6{padding:1.5rem}.p-\[18px_20px\]{padding:18px 20px}.px-1{padding-left:.25rem;padding-right:.25rem}.px-1\.5{padding-left:.375rem;padding-right:.375rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-2\.5{padding-left:.625rem;padding-right:.625rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-3\.5{padding-left:.875rem;padding-right:.875rem}.px-4{padding-left:1rem;padding-right:1rem}.px-5{padding-left:1.25rem;padding-right:1.25rem}.px-6{padding-left:1.5rem;padding-right:1.5rem}.py-0\.5{padding-top:.125rem;padding-bottom:.125rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.py-12{padding-top:3rem;padding-bottom:3rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-2\.5{padding-top:.625rem;padding-bottom:.625rem}.py-3{padding-top:.75rem;padding-bottom:.75rem}.py-4{padding-top:1rem;padding-bottom:1rem}.py-6{padding-top:1.5rem;padding-bottom:1.5rem}.py-8{padding-top:2rem;padding-bottom:2rem}.pb-1{padding-bottom:.25rem}.pb-2{padding-bottom:.5rem}.pb-3{padding-bottom:.75rem}.pb-6{padding-bottom:1.5rem}.pl-3{padding-left:.75rem}.pr-1{padding-right:.25rem}.pr-3{padding-right:.75rem}.pt-3{padding-top:.75rem}.pt-4{padding-top:1rem}.pt-\[20vh\]{padding-top:20vh}.text-left{text-align:left}.text-center{text-align:center}.text-right{text-align:right}.font-mono{font-family:JetBrains Mono,Fira Code,monospace}.text-2xl{font-size:1.5rem;line-height:2rem}.text-\[10px\]{font-size:10px}.text-\[11px\]{font-size:11px}.text-\[12px\]{font-size:12px}.text-\[13px\]{font-size:13px}.text-\[15px\]{font-size:15px}.text-\[7px\]{font-size:7px}.text-\[8px\]{font-size:8px}.text-\[9px\]{font-size:9px}.text-base{font-size:1rem;line-height:1.5rem}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xl{font-size:1.25rem;line-height:1.75rem}.text-xs{font-size:.75rem;line-height:1rem}.font-bold{font-weight:700}.font-medium{font-weight:500}.font-normal{font-weight:400}.font-semibold{font-weight:600}.uppercase{text-transform:uppercase}.italic{font-style:italic}.tabular-nums{--tw-numeric-spacing:tabular-nums;font-variant-numeric:var(--tw-ordinal) var(--tw-slashed-zero) var(--tw-numeric-figure) var(--tw-numeric-spacing) var(--tw-numeric-fraction)}.leading-relaxed{line-height:1.625}.leading-snug{line-height:1.375}.leading-tight{line-height:1.25}.tracking-tight{letter-spacing:-.025em}.tracking-wider{letter-spacing:.05em}.tracking-widest{letter-spacing:.1em}.text-amber-500{--tw-text-opacity:1;color:rgb(245 158 11/var(--tw-text-opacity,1))}.text-blue-500{--tw-text-opacity:1;color:rgb(59 130 246/var(--tw-text-opacity,1))}.text-emerald-500{--tw-text-opacity:1;color:rgb(16 185 129/var(--tw-text-opacity,1))}.text-emerald-600{--tw-text-opacity:1;color:rgb(5 150 105/var(--tw-text-opacity,1))}.text-emerald-700{--tw-text-opacity:1;color:rgb(4 120 87/var(--tw-text-opacity,1))}.text-red-400{--tw-text-opacity:1;color:rgb(248 113 113/var(--tw-text-opacity,1))}.text-red-500{--tw-text-opacity:1;color:rgb(239 68 68/var(--tw-text-opacity,1))}.text-rose-400{--tw-text-opacity:1;color:rgb(251 113 133/var(--tw-text-opacity,1))}.text-rose-500{--tw-text-opacity:1;color:rgb(244 63 94/var(--tw-text-opacity,1))}.text-white{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity,1))}.text-zinc-200{--tw-text-opacity:1;color:rgb(228 228 231/var(--tw-text-opacity,1))}.text-zinc-300{--tw-text-opacity:1;color:rgb(212 212 216/var(--tw-text-opacity,1))}.text-zinc-400{--tw-text-opacity:1;color:rgb(161 161 170/var(--tw-text-opacity,1))}.text-zinc-500{--tw-text-opacity:1;color:rgb(113 113 122/var(--tw-text-opacity,1))}.text-zinc-600{--tw-text-opacity:1;color:rgb(82 82 91/var(--tw-text-opacity,1))}.text-zinc-700{--tw-text-opacity:1;color:rgb(63 63 70/var(--tw-text-opacity,1))}.text-zinc-800{--tw-text-opacity:1;color:rgb(39 39 42/var(--tw-text-opacity,1))}.text-zinc-900{--tw-text-opacity:1;color:rgb(24 24 27/var(--tw-text-opacity,1))}.underline{text-decoration-line:underline}.underline-offset-2{text-underline-offset:2px}.placeholder-zinc-400::-moz-placeholder{--tw-placeholder-opacity:1;color:rgb(161 161 170/var(--tw-placeholder-opacity,1))}.placeholder-zinc-400::placeholder{--tw-placeholder-opacity:1;color:rgb(161 161 170/var(--tw-placeholder-opacity,1))}.opacity-0{opacity:0}.opacity-25{opacity:.25}.opacity-50{opacity:.5}.opacity-75{opacity:.75}.opacity-\[0\.03\]{opacity:.03}.shadow{--tw-shadow:0 1px 3px 0 rgba(0,0,0,.1),0 1px 2px -1px rgba(0,0,0,.1);--tw-shadow-colored:0 1px 3px 0 var(--tw-shadow-color),0 1px 2px -1px var(--tw-shadow-color)}.shadow,.shadow-2xl{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.shadow-2xl{--tw-shadow:0 25px 50px -12px rgba(0,0,0,.25);--tw-shadow-colored:0 25px 50px -12px var(--tw-shadow-color)}.shadow-lg{--tw-shadow:0 10px 15px -3px rgba(0,0,0,.1),0 4px 6px -4px rgba(0,0,0,.1);--tw-shadow-colored:0 10px 15px -3px var(--tw-shadow-color),0 4px 6px -4px var(--tw-shadow-color)}.shadow-lg,.shadow-sm{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.shadow-sm{--tw-shadow:0 1px 2px 0 rgba(0,0,0,.05);--tw-shadow-colored:0 1px 2px 0 var(--tw-shadow-color)}.shadow-xl{--tw-shadow:0 20px 25px -5px rgba(0,0,0,.1),0 8px 10px -6px rgba(0,0,0,.1);--tw-shadow-colored:0 20px 25px -5px var(--tw-shadow-color),0 8px 10px -6px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.outline-none{outline:2px solid transparent;outline-offset:2px}.ring{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width)) var(--tw-ring-color)}.ring,.ring-1{box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.ring-1{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color)}.ring-2{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.ring-emerald-400\/60{--tw-ring-color:rgba(52,211,153,.6)}.ring-zinc-100{--tw-ring-opacity:1;--tw-ring-color:rgb(244 244 245/var(--tw-ring-opacity,1))}.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.backdrop-blur-sm{--tw-backdrop-blur:blur(4px);backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia)}.transition{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-\[right\]{transition-property:right;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-all{transition-property:all;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-colors{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-opacity{transition-property:opacity;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-transform{transition-property:transform;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.duration-300{transition-duration:.3s}.ease-out{transition-timing-function:cubic-bezier(0,0,.2,1)}@keyframes fadeIn{0%{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}}.animate-fade-in{animation:fadeIn .3s ease-out forwards}@keyframes pulseSoft{0%,to{opacity:1}50%{opacity:.6}}.animate-pulse-soft{animation:pulseSoft 2s ease-in-out infinite}@keyframes beadTooltipFade{0%{opacity:0;transform:translateY(4px)}to{opacity:1;transform:translateY(0)}}.description-markdown h1,.description-markdown h2,.description-markdown h3,.description-markdown h4{margin-top:.5rem;margin-bottom:.25rem;font-weight:600;--tw-text-opacity:1;color:rgb(63 63 70/var(--tw-text-opacity,1))}.description-markdown h1{font-size:.85rem}.description-markdown h2{font-size:.8rem}.description-markdown h3{font-size:.75rem}.description-markdown h4{font-size:.7rem}.description-markdown p{margin-bottom:.375rem}.description-markdown p:last-child{margin-bottom:0}.description-markdown ol,.description-markdown ul{margin-bottom:.375rem}.description-markdown ol>:not([hidden])~:not([hidden]),.description-markdown ul>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.125rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.125rem * var(--tw-space-y-reverse))}.description-markdown ol,.description-markdown ul{padding-left:1rem}.description-markdown ul{list-style-type:disc}.description-markdown ol{list-style-type:decimal}.description-markdown code{border-radius:.25rem;background-color:hsla(240,6%,90%,.6);padding:.125rem .25rem;font-family:JetBrains Mono,Fira Code,monospace;font-size:10px;--tw-text-opacity:1;color:rgb(63 63 70/var(--tw-text-opacity,1))}.description-markdown pre{margin-bottom:.375rem;overflow-x:auto;border-radius:.375rem;background-color:hsla(240,6%,90%,.6);padding:.5rem}.description-markdown pre code{background-color:transparent;padding:0;font-size:10px}.description-markdown a{--tw-text-opacity:1;color:rgb(5 150 105/var(--tw-text-opacity,1));text-decoration-line:underline;text-underline-offset:2px}.description-markdown a:hover{--tw-text-opacity:1;color:rgb(4 120 87/var(--tw-text-opacity,1))}.description-markdown blockquote{margin-top:.375rem;margin-bottom:.375rem;border-left-width:2px;--tw-border-opacity:1;border-color:rgb(212 212 216/var(--tw-border-opacity,1));padding-left:.5rem;font-style:italic;--tw-text-opacity:1;color:rgb(113 113 122/var(--tw-text-opacity,1))}.description-markdown table{margin-bottom:.375rem;width:100%;border-collapse:collapse;font-size:10px}.description-markdown th{border-bottom-width:1px;--tw-border-opacity:1;border-color:rgb(228 228 231/var(--tw-border-opacity,1));padding-bottom:.125rem;padding-right:.5rem;text-align:left;font-weight:600;--tw-text-opacity:1;color:rgb(82 82 91/var(--tw-text-opacity,1))}.description-markdown td{border-bottom-width:1px;--tw-border-opacity:1;border-color:rgb(244 244 245/var(--tw-border-opacity,1));padding-top:.125rem;padding-bottom:.125rem;padding-right:.5rem}.description-markdown hr{margin-top:.5rem;margin-bottom:.5rem;--tw-border-opacity:1;border-color:rgb(228 228 231/var(--tw-border-opacity,1))}.description-markdown strong{font-weight:600;--tw-text-opacity:1;color:rgb(63 63 70/var(--tw-text-opacity,1))}.description-markdown img{max-width:100%;border-radius:.25rem}.timeline-slider{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:transparent;cursor:pointer}.timeline-slider::-webkit-slider-runnable-track{height:4px;background:#e4e4e7;border-radius:2px}.timeline-slider::-webkit-slider-thumb{-webkit-appearance:none;width:12px;height:12px;background:#10b981;border-radius:50%;margin-top:-4px;cursor:pointer;-webkit-transition:transform .1s ease;transition:transform .1s ease}.timeline-slider::-webkit-slider-thumb:hover{transform:scale(1.2)}.timeline-slider::-moz-range-track{height:4px;background:#e4e4e7;border-radius:2px;border:none}.timeline-slider::-moz-range-thumb{width:12px;height:12px;background:#10b981;border-radius:50%;border:none;cursor:pointer}.force-graph-container canvas{border-radius:.5rem;touch-action:none}.placeholder\:text-zinc-300::-moz-placeholder{--tw-text-opacity:1;color:rgb(212 212 216/var(--tw-text-opacity,1))}.placeholder\:text-zinc-300::placeholder{--tw-text-opacity:1;color:rgb(212 212 216/var(--tw-text-opacity,1))}.placeholder\:text-zinc-400::-moz-placeholder{--tw-text-opacity:1;color:rgb(161 161 170/var(--tw-text-opacity,1))}.placeholder\:text-zinc-400::placeholder{--tw-text-opacity:1;color:rgb(161 161 170/var(--tw-text-opacity,1))}.first\:mt-0:first-child{margin-top:0}.hover\:border-zinc-200:hover{--tw-border-opacity:1;border-color:rgb(228 228 231/var(--tw-border-opacity,1))}.hover\:border-zinc-300:hover{--tw-border-opacity:1;border-color:rgb(212 212 216/var(--tw-border-opacity,1))}.hover\:bg-emerald-100:hover{--tw-bg-opacity:1;background-color:rgb(209 250 229/var(--tw-bg-opacity,1))}.hover\:bg-emerald-200\/50:hover{background-color:rgba(167,243,208,.5)}.hover\:bg-emerald-50:hover{--tw-bg-opacity:1;background-color:rgb(236 253 245/var(--tw-bg-opacity,1))}.hover\:bg-emerald-600:hover{--tw-bg-opacity:1;background-color:rgb(5 150 105/var(--tw-bg-opacity,1))}.hover\:bg-emerald-700:hover{--tw-bg-opacity:1;background-color:rgb(4 120 87/var(--tw-bg-opacity,1))}.hover\:bg-red-50:hover{--tw-bg-opacity:1;background-color:rgb(254 242 242/var(--tw-bg-opacity,1))}.hover\:bg-white:hover{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity,1))}.hover\:bg-zinc-100:hover{--tw-bg-opacity:1;background-color:rgb(244 244 245/var(--tw-bg-opacity,1))}.hover\:bg-zinc-100\/50:hover{background-color:hsla(240,5%,96%,.5)}.hover\:bg-zinc-200\/60:hover{background-color:hsla(240,6%,90%,.6)}.hover\:bg-zinc-300\/40:hover{background-color:hsla(240,5%,84%,.4)}.hover\:bg-zinc-50:hover{--tw-bg-opacity:1;background-color:rgb(250 250 250/var(--tw-bg-opacity,1))}.hover\:bg-zinc-50\/50:hover{background-color:hsla(0,0%,98%,.5)}.hover\:bg-zinc-50\/60:hover{background-color:hsla(0,0%,98%,.6)}.hover\:text-emerald-600:hover{--tw-text-opacity:1;color:rgb(5 150 105/var(--tw-text-opacity,1))}.hover\:text-emerald-700:hover{--tw-text-opacity:1;color:rgb(4 120 87/var(--tw-text-opacity,1))}.hover\:text-red-400:hover{--tw-text-opacity:1;color:rgb(248 113 113/var(--tw-text-opacity,1))}.hover\:text-rose-500:hover{--tw-text-opacity:1;color:rgb(244 63 94/var(--tw-text-opacity,1))}.hover\:text-zinc-500:hover{--tw-text-opacity:1;color:rgb(113 113 122/var(--tw-text-opacity,1))}.hover\:text-zinc-600:hover{--tw-text-opacity:1;color:rgb(82 82 91/var(--tw-text-opacity,1))}.hover\:text-zinc-700:hover{--tw-text-opacity:1;color:rgb(63 63 70/var(--tw-text-opacity,1))}.hover\:text-zinc-900:hover{--tw-text-opacity:1;color:rgb(24 24 27/var(--tw-text-opacity,1))}.hover\:opacity-80:hover{opacity:.8}.hover\:shadow-md:hover{--tw-shadow:0 4px 6px -1px rgba(0,0,0,.1),0 2px 4px -2px rgba(0,0,0,.1);--tw-shadow-colored:0 4px 6px -1px var(--tw-shadow-color),0 2px 4px -2px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.focus\:border-emerald-400:focus{--tw-border-opacity:1;border-color:rgb(52 211 153/var(--tw-border-opacity,1))}.focus\:border-emerald-500:focus{--tw-border-opacity:1;border-color:rgb(16 185 129/var(--tw-border-opacity,1))}.focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}.focus\:ring-1:focus{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color)}.focus\:ring-1:focus,.focus\:ring-2:focus{box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.focus\:ring-2:focus{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color)}.focus\:ring-emerald-500:focus{--tw-ring-opacity:1;--tw-ring-color:rgb(16 185 129/var(--tw-ring-opacity,1))}.focus\:ring-emerald-500\/30:focus{--tw-ring-color:rgba(16,185,129,.3)}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:opacity-40:disabled{opacity:.4}.disabled\:opacity-50:disabled{opacity:.5}.group:hover .group-hover\:scale-105{--tw-scale-x:1.05;--tw-scale-y:1.05;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.group:hover .group-hover\:text-emerald-500{--tw-text-opacity:1;color:rgb(16 185 129/var(--tw-text-opacity,1))}.group:hover .group-hover\:text-emerald-700{--tw-text-opacity:1;color:rgb(4 120 87/var(--tw-text-opacity,1))}.group:hover .group-hover\:text-zinc-700{--tw-text-opacity:1;color:rgb(63 63 70/var(--tw-text-opacity,1))}.group:hover .group-hover\:opacity-100{opacity:1}@media (min-width:640px){.sm\:left-4{left:1rem}.sm\:right-4{right:1rem}.sm\:top-4{top:1rem}.sm\:block{display:block}.sm\:inline-block{display:inline-block}.sm\:inline{display:inline}.sm\:flex{display:flex}.sm\:hidden{display:none}.sm\:gap-2{gap:.5rem}}@media (min-width:768px){.md\:flex{display:flex}.md\:hidden{display:none}}
package/app/page.tsx CHANGED
@@ -191,6 +191,7 @@ export default function Home() {
191
191
  const [selectedNode, setSelectedNode] = useState<GraphNode | null>(null);
192
192
  const [hoveredNode, setHoveredNode] = useState<GraphNode | null>(null);
193
193
  const [collapsedEpicIds, setCollapsedEpicIds] = useState<Set<string>>(new Set());
194
+ const [focusedEpicId, setFocusedEpicId] = useState<string | null>(null);
194
195
  const [colorMode, setColorMode] = useState<ColorMode>("status");
195
196
  const [projectName, setProjectName] = useState("Beads");
196
197
  const [repoCount, setRepoCount] = useState(0);
@@ -252,16 +253,28 @@ export default function Home() {
252
253
  const [helpPanelOpen, setHelpPanelOpen] = useState(false);
253
254
  const [tutorialStep, setTutorialStep] = useState<number | null>(null);
254
255
 
256
+ // Set of node IDs in the local graph — used to filter global comments/activity
257
+ // to only events relevant to beads in this repo
258
+ const localNodeIds = useMemo(() => {
259
+ if (!data) return new Set<string>();
260
+ return new Set(data.graphData.nodes.map((n) => n.id));
261
+ }, [data]);
262
+
255
263
  // Rebuild historical feed when data or comments change
264
+ // Filter comments to only those targeting nodes in our graph, since the
265
+ // Hypergoat indexer returns comments globally across all repos using beads
256
266
  useEffect(() => {
257
267
  if (!data) return;
268
+ const localComments = allComments
269
+ ? allComments.filter((c) => localNodeIds.has(c.nodeId))
270
+ : null;
258
271
  const historical = buildHistoricalFeed(
259
272
  data.graphData.nodes,
260
273
  data.graphData.links,
261
- allComments
274
+ localComments
262
275
  );
263
276
  setActivityFeed((prev) => mergeFeedEvents(prev, historical));
264
- }, [data, allComments]);
277
+ }, [data, allComments, localNodeIds]);
265
278
 
266
279
  // Context menu state for right-click (phase 1: shows ContextMenu)
267
280
  const [contextMenu, setContextMenu] = useState<{
@@ -583,6 +596,14 @@ export default function Home() {
583
596
  setCollapsedEpicIds(new Set());
584
597
  }, []);
585
598
 
599
+ const handleFocusEpic = useCallback((epicId: string) => {
600
+ setFocusedEpicId(epicId);
601
+ }, []);
602
+
603
+ const handleExitFocusedEpic = useCallback(() => {
604
+ setFocusedEpicId(null);
605
+ }, []);
606
+
586
607
  // --- Tutorial callbacks ---
587
608
  const handleStartTutorial = useCallback(() => {
588
609
  setHelpPanelOpen(true);
@@ -1323,6 +1344,8 @@ export default function Home() {
1323
1344
  collapsedEpicIds={collapsedEpicIds}
1324
1345
  onCollapseAll={handleCollapseAll}
1325
1346
  onExpandAll={handleExpandAll}
1347
+ focusedEpicId={focusedEpicId}
1348
+ onExitFocusedEpic={handleExitFocusedEpic}
1326
1349
  colorMode={colorMode}
1327
1350
  onColorModeChange={setColorMode}
1328
1351
  autoFit={autoFit}
@@ -1429,6 +1452,23 @@ export default function Home() {
1429
1452
  }
1430
1453
  : undefined
1431
1454
  }
1455
+ onFocusEpic={
1456
+ contextMenu.node.issueType === "epic" && !focusedEpicId
1457
+ ? () => {
1458
+ handleFocusEpic(contextMenu.node.id);
1459
+ setContextMenu(null);
1460
+ }
1461
+ : undefined
1462
+ }
1463
+ onExitFocusEpic={
1464
+ contextMenu.node.issueType === "epic" &&
1465
+ focusedEpicId === contextMenu.node.id
1466
+ ? () => {
1467
+ handleExitFocusedEpic();
1468
+ setContextMenu(null);
1469
+ }
1470
+ : undefined
1471
+ }
1432
1472
  onClose={() => setContextMenu(null)}
1433
1473
  />
1434
1474
  )}
@@ -1645,7 +1685,7 @@ export default function Home() {
1645
1685
  <AllCommentsPanel
1646
1686
  isOpen={allCommentsPanelOpen}
1647
1687
  onClose={() => setAllCommentsPanelOpen(false)}
1648
- allComments={allComments}
1688
+ allComments={allComments.filter((c) => localNodeIds.has(c.nodeId))}
1649
1689
  onNodeNavigate={(nodeId) => {
1650
1690
  handleNodeNavigate(nodeId);
1651
1691
  setAllCommentsPanelOpen(false);
@@ -62,6 +62,10 @@ interface BeadsGraphProps {
62
62
  showPulse?: boolean;
63
63
  /** Callback to toggle pulse animation */
64
64
  onShowPulseToggle?: () => void;
65
+ /** When set, only show this epic and its connected subgraph */
66
+ focusedEpicId?: string | null;
67
+ /** Callback to exit focused epic mode */
68
+ onExitFocusedEpic?: () => void;
65
69
  }
66
70
 
67
71
  // Node size calculation
@@ -254,6 +258,8 @@ const BeadsGraph = forwardRef<BeadsGraphHandle, BeadsGraphProps>(function BeadsG
254
258
  pulseNodeId,
255
259
  showPulse = true,
256
260
  onShowPulseToggle,
261
+ focusedEpicId,
262
+ onExitFocusedEpic,
257
263
  }, ref) {
258
264
  const graphRef = useRef<any>(null);
259
265
  const containerRef = useRef<HTMLDivElement>(null);
@@ -347,11 +353,64 @@ const BeadsGraph = forwardRef<BeadsGraphHandle, BeadsGraphProps>(function BeadsG
347
353
  // then removes child nodes and remaps their links to the parent epic.
348
354
  // Must be declared BEFORE effects that reference viewNodes/viewLinks.
349
355
  const { viewNodes, viewLinks } = useMemo(() => {
350
- if (!collapsedEpicIds || collapsedEpicIds.size === 0) return { viewNodes: nodes, viewLinks: links };
356
+ let currentNodes = nodes;
357
+ let currentLinks = links;
358
+
359
+ // === PHASE 1: Epic focus mode ===
360
+ // When focused on an epic, filter to only the epic's subgraph
361
+ if (focusedEpicId) {
362
+ // Build child->parent map
363
+ const childToParent = new Map<string, string>();
364
+ for (const link of currentLinks) {
365
+ const src = typeof link.source === "object" ? (link.source as any).id : link.source;
366
+ const tgt = typeof link.target === "object" ? (link.target as any).id : link.target;
367
+ if (link.type === "parent-child") {
368
+ childToParent.set(tgt, src);
369
+ }
370
+ }
371
+ const nodeIdSet = new Set(currentNodes.map((n) => n.id));
372
+ for (const node of currentNodes) {
373
+ if (!childToParent.has(node.id) && node.id.includes(".")) {
374
+ const parentId = node.id.split(".")[0];
375
+ if (nodeIdSet.has(parentId)) {
376
+ childToParent.set(node.id, parentId);
377
+ }
378
+ }
379
+ }
380
+
381
+ // Collect epic + direct children
382
+ const subgraphIds = new Set<string>();
383
+ subgraphIds.add(focusedEpicId);
384
+ for (const [childId, parentId] of childToParent) {
385
+ if (parentId === focusedEpicId) {
386
+ subgraphIds.add(childId);
387
+ }
388
+ }
389
+
390
+ // Add 1-hop neighbors connected via blocks/relates_to links
391
+ for (const link of currentLinks) {
392
+ const src = typeof link.source === "object" ? (link.source as any).id : link.source;
393
+ const tgt = typeof link.target === "object" ? (link.target as any).id : link.target;
394
+ if (link.type !== "parent-child") {
395
+ if (subgraphIds.has(src) && nodeIdSet.has(tgt)) subgraphIds.add(tgt);
396
+ if (subgraphIds.has(tgt) && nodeIdSet.has(src)) subgraphIds.add(src);
397
+ }
398
+ }
399
+
400
+ currentNodes = currentNodes.filter((n) => subgraphIds.has(n.id));
401
+ currentLinks = currentLinks.filter((link) => {
402
+ const src = typeof link.source === "object" ? (link.source as any).id : link.source;
403
+ const tgt = typeof link.target === "object" ? (link.target as any).id : link.target;
404
+ return subgraphIds.has(src) && subgraphIds.has(tgt);
405
+ });
406
+ }
407
+
408
+ // === PHASE 2: Collapse mode ===
409
+ if (!collapsedEpicIds || collapsedEpicIds.size === 0) return { viewNodes: currentNodes, viewLinks: currentLinks };
351
410
 
352
411
  // Build child->parent map from parent-child links
353
412
  const childToParent = new Map<string, string>();
354
- for (const link of links) {
413
+ for (const link of currentLinks) {
355
414
  const src = typeof link.source === "object" ? (link.source as any).id : link.source;
356
415
  const tgt = typeof link.target === "object" ? (link.target as any).id : link.target;
357
416
  if (link.type === "parent-child") {
@@ -360,8 +419,8 @@ const BeadsGraph = forwardRef<BeadsGraphHandle, BeadsGraphProps>(function BeadsG
360
419
  }
361
420
  }
362
421
  // Fallback: infer from hierarchical IDs (e.g., "beads-map-3r3.1" -> parent "beads-map-3r3")
363
- const nodeIds = new Set(nodes.map((n) => n.id));
364
- for (const node of nodes) {
422
+ const nodeIds = new Set(currentNodes.map((n) => n.id));
423
+ for (const node of currentNodes) {
365
424
  if (!childToParent.has(node.id) && node.id.includes(".")) {
366
425
  const parentId = node.id.split(".")[0];
367
426
  if (nodeIds.has(parentId)) {
@@ -378,7 +437,7 @@ const BeadsGraph = forwardRef<BeadsGraphHandle, BeadsGraphProps>(function BeadsG
378
437
  }
379
438
  }
380
439
 
381
- if (childIds.size === 0) return { viewNodes: nodes, viewLinks: links };
440
+ if (childIds.size === 0) return { viewNodes: currentNodes, viewLinks: currentLinks };
382
441
 
383
442
  // Also build a filtered childToParent for only the collapsed children (for link remapping)
384
443
  const collapsedChildToParent = new Map<string, string>();
@@ -392,7 +451,7 @@ const BeadsGraph = forwardRef<BeadsGraphHandle, BeadsGraphProps>(function BeadsG
392
451
  const extraDependentCount = new Map<string, number>();
393
452
  for (const [childId, parentId] of collapsedChildToParent) {
394
453
  collapsedCounts.set(parentId, (collapsedCounts.get(parentId) || 0) + 1);
395
- const child = nodes.find((n) => n.id === childId);
454
+ const child = currentNodes.find((n) => n.id === childId);
396
455
  if (child) {
397
456
  extraBlockerCount.set(parentId, (extraBlockerCount.get(parentId) || 0) + child.blockerCount);
398
457
  extraDependentCount.set(parentId, (extraDependentCount.get(parentId) || 0) + child.dependentCount);
@@ -400,7 +459,7 @@ const BeadsGraph = forwardRef<BeadsGraphHandle, BeadsGraphProps>(function BeadsG
400
459
  }
401
460
 
402
461
  // Filter nodes: remove collapsed children, augment their parents
403
- const filteredNodes: GraphNode[] = nodes
462
+ const filteredNodes: GraphNode[] = currentNodes
404
463
  .filter((n) => !childIds.has(n.id))
405
464
  .map((n) => ({
406
465
  ...n,
@@ -412,7 +471,7 @@ const BeadsGraph = forwardRef<BeadsGraphHandle, BeadsGraphProps>(function BeadsG
412
471
  // Remap links: replace collapsed child IDs with parent IDs, drop internal parent-child links
413
472
  const remappedLinks: GraphLink[] = [];
414
473
  const linkSeen = new Set<string>();
415
- for (const link of links) {
474
+ for (const link of currentLinks) {
416
475
  let src = typeof link.source === "object" ? (link.source as any).id : link.source;
417
476
  let tgt = typeof link.target === "object" ? (link.target as any).id : link.target;
418
477
  // Drop parent-child links where the child is collapsed
@@ -428,7 +487,7 @@ const BeadsGraph = forwardRef<BeadsGraphHandle, BeadsGraphProps>(function BeadsG
428
487
  }
429
488
 
430
489
  return { viewNodes: filteredNodes, viewLinks: remappedLinks };
431
- }, [nodes, links, collapsedEpicIds]);
490
+ }, [nodes, links, collapsedEpicIds, focusedEpicId]);
432
491
 
433
492
  // Keep viewNodesRef in sync for mousemove avatar hit-testing
434
493
  viewNodesRef.current = viewNodes;
@@ -897,6 +956,24 @@ const BeadsGraph = forwardRef<BeadsGraphHandle, BeadsGraphProps>(function BeadsG
897
956
  }
898
957
  }, [nodes.length, timelineActive, autoFit]);
899
958
 
959
+ // Auto zoom-to-fit when entering/exiting epic focus mode (unconditional)
960
+ const prevFocusedEpicIdRef = useRef<string | null | undefined>(undefined);
961
+ useEffect(() => {
962
+ // Skip on mount (initial render)
963
+ if (prevFocusedEpicIdRef.current === undefined) {
964
+ prevFocusedEpicIdRef.current = focusedEpicId ?? null;
965
+ return;
966
+ }
967
+ prevFocusedEpicIdRef.current = focusedEpicId ?? null;
968
+ const graph = graphRef.current;
969
+ if (!graph) return;
970
+ // Small delay to let the force graph process the new node set
971
+ const timer = setTimeout(() => {
972
+ graph.zoomToFit(400, 60);
973
+ }, 100);
974
+ return () => clearTimeout(timer);
975
+ }, [focusedEpicId]);
976
+
900
977
  // Memoize graphData so the object reference stays stable across renders.
901
978
  // This prevents react-force-graph from treating it as "new data" and
902
979
  // re-heating the simulation on every hover/selection change.
@@ -1822,8 +1899,29 @@ const BeadsGraph = forwardRef<BeadsGraphHandle, BeadsGraphProps>(function BeadsG
1822
1899
 
1823
1900
  return (
1824
1901
  <div ref={containerRef} className="w-full h-full relative" data-tutorial="graph">
1825
- {/* Top-left controls — two rows */}
1902
+ {/* Top-left controls */}
1826
1903
  <div className="absolute top-3 left-3 sm:top-4 sm:left-4 z-10 flex flex-col gap-1.5 sm:gap-2">
1904
+ {/* Focus mode banner */}
1905
+ {focusedEpicId && onExitFocusedEpic && (
1906
+ <div className="flex items-center gap-2 px-3 py-1.5 text-xs font-medium bg-emerald-50/90 backdrop-blur-sm rounded-lg border border-emerald-200 shadow-sm text-emerald-700">
1907
+ <svg className="w-3.5 h-3.5 text-emerald-500 flex-shrink-0" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor">
1908
+ <path strokeLinecap="round" strokeLinejoin="round" d="M7.5 3.75H6A2.25 2.25 0 003.75 6v1.5M16.5 3.75H18A2.25 2.25 0 0120.25 6v1.5M16.5 20.25H18A2.25 2.25 0 0020.25 18v-1.5M7.5 20.25H6A2.25 2.25 0 013.75 18v-1.5" />
1909
+ </svg>
1910
+ <span className="truncate max-w-[180px]">
1911
+ Focused: <span className="font-semibold">{nodes.find((n) => n.id === focusedEpicId)?.title || focusedEpicId}</span>
1912
+ </span>
1913
+ <button
1914
+ onClick={onExitFocusedEpic}
1915
+ className="ml-auto p-0.5 rounded hover:bg-emerald-200/50 transition-colors flex-shrink-0"
1916
+ title="Show full graph"
1917
+ >
1918
+ <svg className="w-3.5 h-3.5" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor">
1919
+ <path strokeLinecap="round" strokeLinejoin="round" d="M6 18L18 6M6 6l12 12" />
1920
+ </svg>
1921
+ </button>
1922
+ </div>
1923
+ )}
1924
+
1827
1925
  {/* Row 1: Layout shape controls */}
1828
1926
  <div className="flex items-start gap-1.5 sm:gap-2">
1829
1927
  {/* Layout mode toggle */}
@@ -13,6 +13,8 @@ interface ContextMenuProps {
13
13
  onUnclaimTask?: () => void;
14
14
  onCollapseEpic?: () => void;
15
15
  onUncollapseEpic?: () => void;
16
+ onFocusEpic?: () => void;
17
+ onExitFocusEpic?: () => void;
16
18
  onClose: () => void;
17
19
  }
18
20
 
@@ -26,6 +28,8 @@ export function ContextMenu({
26
28
  onUnclaimTask,
27
29
  onCollapseEpic,
28
30
  onUncollapseEpic,
31
+ onFocusEpic,
32
+ onExitFocusEpic,
29
33
  onClose,
30
34
  }: ContextMenuProps) {
31
35
  const menuRef = useRef<HTMLDivElement>(null);
@@ -119,7 +123,7 @@ export function ContextMenu({
119
123
  )}
120
124
  <button
121
125
  onClick={onAddComment}
122
- className={`w-full px-3 py-2.5 text-xs text-zinc-700 hover:bg-zinc-50 flex items-center gap-2 transition-colors${onClaimTask || onUnclaimTask || onCollapseEpic || onUncollapseEpic ? " border-b border-zinc-100" : ""}`}
126
+ className={`w-full px-3 py-2.5 text-xs text-zinc-700 hover:bg-zinc-50 flex items-center gap-2 transition-colors${onClaimTask || onUnclaimTask || onCollapseEpic || onUncollapseEpic || onFocusEpic || onExitFocusEpic ? " border-b border-zinc-100" : ""}`}
123
127
  >
124
128
  <svg
125
129
  className="w-3.5 h-3.5 text-zinc-400"
@@ -139,7 +143,7 @@ export function ContextMenu({
139
143
  {onClaimTask && (
140
144
  <button
141
145
  onClick={onClaimTask}
142
- className={`w-full px-3 py-2.5 text-xs text-zinc-700 hover:bg-zinc-50 flex items-center gap-2 transition-colors${onCollapseEpic || onUncollapseEpic ? " border-b border-zinc-100" : ""}`}
146
+ className={`w-full px-3 py-2.5 text-xs text-zinc-700 hover:bg-zinc-50 flex items-center gap-2 transition-colors${onCollapseEpic || onUncollapseEpic || onFocusEpic || onExitFocusEpic ? " border-b border-zinc-100" : ""}`}
143
147
  >
144
148
  <svg
145
149
  className="w-3.5 h-3.5 text-zinc-400"
@@ -160,7 +164,7 @@ export function ContextMenu({
160
164
  {onUnclaimTask && (
161
165
  <button
162
166
  onClick={onUnclaimTask}
163
- className={`w-full px-3 py-2.5 text-xs text-red-500 hover:bg-red-50 flex items-center gap-2 transition-colors${onCollapseEpic || onUncollapseEpic ? " border-b border-zinc-100" : ""}`}
167
+ className={`w-full px-3 py-2.5 text-xs text-red-500 hover:bg-red-50 flex items-center gap-2 transition-colors${onCollapseEpic || onUncollapseEpic || onFocusEpic || onExitFocusEpic ? " border-b border-zinc-100" : ""}`}
164
168
  >
165
169
  <svg
166
170
  className="w-3.5 h-3.5 text-red-400"
@@ -181,7 +185,7 @@ export function ContextMenu({
181
185
  {onCollapseEpic && (
182
186
  <button
183
187
  onClick={onCollapseEpic}
184
- className="w-full px-3 py-2.5 text-xs text-zinc-700 hover:bg-zinc-50 flex items-center gap-2 transition-colors"
188
+ className={`w-full px-3 py-2.5 text-xs text-zinc-700 hover:bg-zinc-50 flex items-center gap-2 transition-colors${onFocusEpic || onExitFocusEpic ? " border-b border-zinc-100" : ""}`}
185
189
  >
186
190
  <svg
187
191
  className="w-3.5 h-3.5 text-zinc-400"
@@ -202,7 +206,7 @@ export function ContextMenu({
202
206
  {onUncollapseEpic && (
203
207
  <button
204
208
  onClick={onUncollapseEpic}
205
- className="w-full px-3 py-2.5 text-xs text-zinc-700 hover:bg-zinc-50 flex items-center gap-2 transition-colors"
209
+ className={`w-full px-3 py-2.5 text-xs text-zinc-700 hover:bg-zinc-50 flex items-center gap-2 transition-colors${onFocusEpic || onExitFocusEpic ? " border-b border-zinc-100" : ""}`}
206
210
  >
207
211
  <svg
208
212
  className="w-3.5 h-3.5 text-zinc-400"
@@ -220,6 +224,48 @@ export function ContextMenu({
220
224
  Uncollapse epic
221
225
  </button>
222
226
  )}
227
+ {onFocusEpic && (
228
+ <button
229
+ onClick={onFocusEpic}
230
+ className="w-full px-3 py-2.5 text-xs text-zinc-700 hover:bg-zinc-50 flex items-center gap-2 transition-colors"
231
+ >
232
+ <svg
233
+ className="w-3.5 h-3.5 text-zinc-400"
234
+ fill="none"
235
+ viewBox="0 0 24 24"
236
+ strokeWidth={1.5}
237
+ stroke="currentColor"
238
+ >
239
+ <path
240
+ strokeLinecap="round"
241
+ strokeLinejoin="round"
242
+ d="M7.5 3.75H6A2.25 2.25 0 003.75 6v1.5M16.5 3.75H18A2.25 2.25 0 0120.25 6v1.5M16.5 20.25H18A2.25 2.25 0 0020.25 18v-1.5M7.5 20.25H6A2.25 2.25 0 013.75 18v-1.5"
243
+ />
244
+ </svg>
245
+ Focus on epic
246
+ </button>
247
+ )}
248
+ {onExitFocusEpic && (
249
+ <button
250
+ onClick={onExitFocusEpic}
251
+ className="w-full px-3 py-2.5 text-xs text-emerald-600 hover:bg-emerald-50 flex items-center gap-2 transition-colors"
252
+ >
253
+ <svg
254
+ className="w-3.5 h-3.5 text-emerald-500"
255
+ fill="none"
256
+ viewBox="0 0 24 24"
257
+ strokeWidth={1.5}
258
+ stroke="currentColor"
259
+ >
260
+ <path
261
+ strokeLinecap="round"
262
+ strokeLinejoin="round"
263
+ d="M3.75 3.75v4.5m0-4.5h4.5m-4.5 0L9 9M3.75 20.25v-4.5m0 4.5h4.5m-4.5 0L9 15M20.25 3.75h-4.5m4.5 0v4.5m0-4.5L15 9m5.25 11.25h-4.5m4.5 0v-4.5m0 4.5L15 15"
264
+ />
265
+ </svg>
266
+ Show full graph
267
+ </button>
268
+ )}
223
269
  </div>
224
270
  </div>
225
271
  );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "beads-map",
3
- "version": "0.3.3",
3
+ "version": "0.3.4",
4
4
  "description": "Interactive dependency graph viewer for beads (bd) issues",
5
5
  "keywords": [
6
6
  "beads",