@metricinsights/pp-dev 1.5.0-beta.1 → 1.5.0-beta.11

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 (43) hide show
  1. package/README.md +11 -5
  2. package/dist/README.md +11 -5
  3. package/dist/cjs/cli.js +1 -1
  4. package/dist/cjs/cli.js.map +1 -1
  5. package/dist/cjs/index-DZYClNrq.js +2 -0
  6. package/dist/cjs/index-DZYClNrq.js.map +1 -0
  7. package/dist/cjs/index.js +1 -1
  8. package/dist/cjs/{migrate-pmX6NEmP.js → migrate-CWaA9822.js} +2 -2
  9. package/dist/cjs/{migrate-pmX6NEmP.js.map → migrate-CWaA9822.js.map} +1 -1
  10. package/dist/cjs/package.json +8 -8
  11. package/dist/cjs/plugin-C9wKTJUJ.js +106 -0
  12. package/dist/cjs/plugin-C9wKTJUJ.js.map +1 -0
  13. package/dist/cjs/plugin.js +1 -1
  14. package/dist/cjs/{version-plugin-0EjcUgtP.js → version-plugin-BYlQIq9P.js} +2 -2
  15. package/dist/cjs/{version-plugin-0EjcUgtP.js.map → version-plugin-BYlQIq9P.js.map} +1 -1
  16. package/dist/client/client.css +1 -1
  17. package/dist/client/client.css.map +1 -1
  18. package/dist/client/client.js +208 -82
  19. package/dist/client/client.js.map +1 -1
  20. package/dist/esm/cli.js +1 -1
  21. package/dist/esm/cli.js.map +1 -1
  22. package/dist/esm/index-D-UQbBcq.js +2 -0
  23. package/dist/esm/index-D-UQbBcq.js.map +1 -0
  24. package/dist/esm/index.js +1 -1
  25. package/dist/esm/{migrate-Dsd7Aqx-.js → migrate-Brx9j28S.js} +2 -2
  26. package/dist/esm/{migrate-Dsd7Aqx-.js.map → migrate-Brx9j28S.js.map} +1 -1
  27. package/dist/esm/package.json +8 -8
  28. package/dist/esm/plugin-B936Mkft.js +106 -0
  29. package/dist/esm/plugin-B936Mkft.js.map +1 -0
  30. package/dist/esm/plugin.js +1 -1
  31. package/dist/esm/{version-plugin-pYbaqnAH.js → version-plugin-CA0tX4h3.js} +2 -2
  32. package/dist/esm/{version-plugin-pYbaqnAH.js.map → version-plugin-CA0tX4h3.js.map} +1 -1
  33. package/dist/types/index.d.ts +7 -0
  34. package/docs/features.md +9 -5
  35. package/package.json +16 -16
  36. package/dist/cjs/index-FgUi7Fqw.js +0 -2
  37. package/dist/cjs/index-FgUi7Fqw.js.map +0 -1
  38. package/dist/cjs/plugin-yWlSj5sK.js +0 -2
  39. package/dist/cjs/plugin-yWlSj5sK.js.map +0 -1
  40. package/dist/esm/index-BA9vecJD.js +0 -2
  41. package/dist/esm/index-BA9vecJD.js.map +0 -1
  42. package/dist/esm/plugin-CFtRWPwU.js +0 -2
  43. package/dist/esm/plugin-CFtRWPwU.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"sourceRoot":"","sources":["client.scss"],"names":[],"mappings":"AA+FA,uBAtBI,sXA2BF,qDA3BE,sXA+BF,2CACE,eACA,SACA,WACA,YACA,6BACA,YACA,gDACA,uCACA,kBACA,aACA,sBACA,qBACA,QACA,aACA,gDACA,gBACA,eACA,uDACA,wBACA,UACA,sBACA,6DAEA,oDACE,wCACA,UAGF,mDACE,wCACA,UAGF,iDACE,aACA,uBACA,8BACA,QACA,UACA,aACA,WACA,sBACA,gBACA,cAEA,sDACE,aACA,mBACA,uBACA,WACA,YACA,cACA,sCAGF,sDACE,OACA,cACA,eACA,gBACA,iBACA,gBACA,sBAGF,uDACE,eACA,eACA,gBACA,aACA,mBACA,uBACA,UACA,SACA,gBACA,aACA,yBACA,kBACA,0BACA,cAEA,6DACE,oCAGF,qEACE,kDACA,mBAGF,2DACE,8BAKN,mDACE,iBACA,UACA,wBACA,gBACA,sBACA,YACA,+BACA,wBACA,eACA,gBACA,iBACA,gBACA,sBACA,qBAGF,oDACE,6DACA,mDAGF,mDACE,4DACA,kDAGF,iDACE,0DACA,gDAGF,oDACE,6DACA,mDAIF,iDACE,WACA,UACA,iBACA,uDAEA,mHAEE,yCAKN,yGAEE,eACA,QACA,eACA,aACA,mBACA,uBACA,iCACA,0BACA,kCAGF,6CACE,8BACA,kBACA,iCACA,gDACA,8CACA,gBAEA,mDACE,kBACA,eACA,gBACA,wCACA,oCACA,iCAGF,qDACE,aACA,qBACA,oCACA,eACA,gBAGF,qDACE,aACA,sBACA,QACA,oBAGF,iDACE,WACA,iBACA,kBACA,+BACA,eACA,gBACA,eACA,wBAEA,yDACE,oCACA,+BACA,4BAGF,0DACE,qCACA,kDAGF,uDACE,WAKN,uEAEE,eACA,eACA,mBACA,kBACA,OAxUY,KAyUZ,kBACA,YACA,QACA,gDACA,oCACA,sBACA,aACA,mBACA,gDACA,iBACA,0BACA,kCAMA,2EACE,oCACA,+BACA,eACA,gBACA,iBACA,iBACA,qBACA,uBACA,uBACA,sBACA,SACA,UACA,SACA,UACA,eACA,gBACA,mCACA,kCAGF,yFACE,aACA,mBACA,QAGF,yFACE,aACA,mBACA,kBACA,cAEA,yIACE,WACA,kBACA,QACA,QACA,2BACA,YACA,UACA,oCAEA,mMACE,sCAGF,+CAdF,yIAeI,sCAEA,qMACE,qCAKN,iHACE,eAGF,+GACE,gBAIJ,mFACE,aACA,mBACA,QACA,eACA,gBACA,iBAGF,qFACE,yCACA,gBAGF,qFACE,gBAGF,mFACE,uCACA,qBACA,0BACA,aACA,mBACA,QAEA,+FACE,uCACA,0BAQJ,yFACE,kDAGF,yFACE,kDAGF,mFACE,+CAGF,yFACE,kDAGF,uFACE,iDAGF,2EACE,uCACA,qBACA,sBACA,iBAEA,uFACE,qBAGF,gLAEE,UAIJ,qFACE,YACA,YACA,gBACA,eACA,aAGF,oLA/CE,gBAoDF,iGACE,aACA,mBACA,uBACA,cACA,WACA,YACA,YACA,kBACA,kBACA,yCACA,YACA,WACE,2CAGF,yGACE,oBACA,kBAGF,6GACE,UACA,oCAIJ,mGACE,aACA,mBACA,uBACA,WACA,YACA,YACA,kBACA,eACA,yCACA,wBAEA,2GACE,WACA,YACA,UACA,8BAGF,+GACE,uCACA,oCAEA,uHACE,wBAKN,2FACE,kBACA,gBACA,aACA,aACA,sBACA,SACA,gDACA,oCACA,kBACA,wCACA,eACA,eACA,mBACA,gBAEA,qJACE,eACA,gBACA,yCACA,yBACA,oBAGF,iJACE,aACA,mBACA,8BACA,SAGF,uJACE,gBACA,4BACA,WACA,YACA,8CACA,eACA,cAGF,+IACE,aACA,sCACA,QAGF,6IACE,WACA,YACA,oCACA,kBACA,eACA,kBACA,yBACA,iCAEA,2JACE,WACA,kBACA,WACA,WACA,kBACA,oDACA,qCAGF,+KACE,QACA,SAGF,iLACE,QACA,UAGF,qLACE,WACA,SAGF,uLACE,WACA,UAGF,yJACE,8CAGF,2JACE,8CAEA,yKACE,kDAKN,6IACE,aACA,QAGF,2IACE,gBACA,oCACA,kBACA,eACA,yBACA,eACA,iBACA,yCACA,WACE,+DAIF,uJACE,8CACA,uCAGF,yJACE,8CACA,kDACA,qCAGF,uKACE,mDACA,mBAIJ,2JACE,WACA,iBACA,mCACA,kBACA,eACA,gBACA,sCACA,qCAEA,uKACE,qCAIJ,qJACE,aACA,sBACA,QACA,iBACA,wCAGF,iJACE,aACA,sBACA,QAGF,yIACE,WACA,iBACA,kBACA,eACA,gBACA,kBACA,uCACA,mCACA,mCACA,WACE,gDAGF,qJACE,oCACA,+BAGF,qKACE,mDACA,mBAIJ,mJACE,eACA,iBACA,yCAEA,6JACE,sBACA,eAIJ,qJACE,sBACA,eACA,uCACA,eAEA,iKACE,0BAMN,6FACE,aACA,mBACA,uBACA,WACA,YACA,YACA,kBACA,eACA,yCACA,wBAEA,qGACE,WACA,YACA,cAGF,yGACE,uCACA,oCAKJ,mKACE,WACA,yBAEA,+KACE,yBAIJ,iIACE,YACA,6BACA,sBAEA,iLACE,eACA,iBACA,oCAEA,2LACE,cACA,eACA,yCAIJ,mLACE,aACA,sBACA,QACA,iBACA,gBAGF,iLACE,aACA,mBACA,QACA,gBACA,kBACA,oCAEA,uMACE,0BACA,sCAIJ,yLACE,gBACA,yBACA,WACA,YACA,SACA,cACA,8CACA,eAEA,2MACE,mBAIJ,iLACE,aACA,sBACA,YACA,OAGF,mLACE,eACA,gBACA,oCACA,gBACA,uBAGF,mLACE,eACA,iBACA,yCACA,gBACA,uBAGF,6MACE,sCAGF,uLACE,cACA,gBACA,oCACA,kBACA,yBACA,eACA,eACA,iBACA,uCAEA,mMACE,8CAGF,uMACE,8CACA,kDACA,qCAIJ,uLACE,cACA,WACA,YACA,UACA,YACA,kBACA,yBACA,eACA,eACA,iBACA,yCAEA,mMACE,sCACA,qCAIJ,qLACE,WACA,sBACA,gBACA,oCACA,kBACA,yBACA,eACA,oCAEA,iMACE,aACA,8CAIJ,sXAEE,eACA,iBACA,sCAGF,iMACE,yCAGF,mJACE,wBAIJ,2FACE,sBACA,aACA,mBACA,uBACA,WACA,YACA,YACA,eACA,kBACA,qCAEA,uGACE,oCACA,yCAGF,uGACE,WACA,YACA,oCACA,UACA,8BAIJ,qFACE,eAGE,+IACE,0CASJ,mHACE,MACA,OACA,wBACA,4CACA,2CAEA,iKACE,qBACA,OA6BA,iKACE,SAEA,6KACE,yBAIJ,iIACE,yCAEA,2LACE,uBAIJ,uOACE,wCACA,WACE,mFAGF,qPACE,WACA,kBACA,SACA,YACA,WACA,UAlER,qHACE,MACA,QACA,wBACA,6CACA,2CAEA,mKACE,qBACA,QAIA,mIACE,wCAEA,6LACE,yBAIJ,yOACE,uCACA,WACE,mFAIF,uPACE,WACA,kBACA,SACA,YACA,UACA,UAlCR,yHACE,SACA,OACA,wBACA,6CACA,wCAEA,uKACE,wBACA,OA6BA,uKACE,SAEA,mLACE,yBAIJ,uIACE,yCAEA,iMACE,uBAIJ,6OACE,wCACA,WACE,oFAGF,2PACE,WACA,kBACA,SACA,YACA,WACA,UAlER,2HACE,SACA,QACA,wBACA,8CACA,wCAEA,yKACE,wBACA,QAIA,yIACE,wCAEA,mMACE,yBAIJ,+OACE,uCACA,WACE,oFAIF,6PACE,WACA,kBACA,SACA,YACA,UACA,UAuCV,qHACE,eAGF,+FACE,gBACA,iBACA,gBAEA,mJACE,gBAIJ,+GACE,aAGF,iGACE,aACA,mBACA,uBACA,WACA,YACA,YACA,+BACA,kBACA,eACA,uCACA,wBACA,kBACA,gBAEA,iHACE,WACA,kBACA,MACA,OACA,WACA,YACA,8BACA,UACA,4BACA,kBAGF,yGACE,WACA,YACA,kBACA,UACA,8BAGF,2IACE,uCACA,oCAEA,mJACE,mCACA,8BAKF,qJACE,mCAIJ,mHACE,WACA,mBACA,oBACA,yCAIA,yHACE,sCAKN,iFACE,oBACA,cACA,UACA,YACA,oCAEA,2IACE,sCAGF,+CAXF,iFAYI,sCAEA,6IACE,qCAKN,kBACE,KACE,uBAEF,GACE,0BAIJ,2FACE,iBAGF,6FACE,kBAGF,iGACE,+BACA,eACA,gBACA,sBACA,iBACA,oCAEA,yIACE,kDAIJ,oBACE,GACE,uBAEF,KACE,0BAIJ,sBACE,GACE,0BAEF,IACE,2BAEF,KACE,2BAOJ,iIAloCA,yXAOF,+KACE,sCACA,kCAEA,2LACE,sCACA,kCAIJ,2LACE,uCAunCA,+CA94BF,uEA3PE,yXAOF,qHACE,sCACA,kCAEA,iIACE,sCACA,kCAIJ,iIACE,wCA6nCF,uCACE,gKAIE","file":"client.css","sourcesContent":["@use 'sass:map';\n\n$pp-dev-height: 32px;\n$pp-dev-peek: 4px;\n\n// Corner map: v/h — anchored edges; sx — horizontal slide direction for minimize/auto-hide\n// (1 → slides right, -1 → slides left); radius/shadow face the viewport center;\n// edge — hairline border on the exposed horizontal edge.\n$pp-dev-corners: (\n 'top-left': (\n v: top,\n h: left,\n sx: -1,\n radius: 0 0 4px 0,\n shadow: 2px 2px 8px 0 rgba(34, 34, 34, 0.08),\n edge: border-bottom,\n ),\n 'top-right': (\n v: top,\n h: right,\n sx: 1,\n radius: 0 0 0 4px,\n shadow: -2px 2px 8px 0 rgba(34, 34, 34, 0.08),\n edge: border-bottom,\n ),\n 'bottom-left': (\n v: bottom,\n h: left,\n sx: -1,\n radius: 0 4px 0 0,\n shadow: 2px -2px 8px 0 rgba(34, 34, 34, 0.08),\n edge: border-top,\n ),\n 'bottom-right': (\n v: bottom,\n h: right,\n sx: 1,\n radius: 4px 0 0 0,\n shadow: -2px -2px 8px 0 rgba(34, 34, 34, 0.08),\n edge: border-top,\n ),\n);\n\n// Light/dark color maps, shared by the default (light) rules, the prefers-color-scheme:dark\n// media query (Auto), and the explicit data-pp-dev-theme override (Dark/Light picked in the\n// panel's settings popover — see src/client/theme.ts). One place to edit either palette.\n$pp-dev-light-colors: (\n primary: #075b7e,\n secondary: rgba(34, 34, 34, 0.64),\n success: #077e45,\n info: #17a2b8,\n warning: #ffb000,\n danger: #ac2b2b,\n light: #f8f9fa,\n dark: #343a40,\n 'white': #ffffff,\n 'black': #000000,\n);\n$pp-dev-dark-colors: (\n primary: #2a8db5,\n secondary: rgba(220, 220, 220, 0.64),\n success: #0fad5e,\n info: #2dccff,\n warning: #ffb302,\n danger: #e05252,\n light: #343a40,\n dark: #f8f9fa,\n 'white': #000000,\n 'black': #ffffff,\n);\n\n@mixin pp-dev-theme-colors($colors) {\n @each $name, $value in $colors {\n --pp-dev-info-color-#{$name}: #{$value};\n }\n}\n\n// rgba() literals don't follow the --pp-dev-info-color-primary reassignment above,\n// so the button fill/border are re-tinted here to match the dark-mode primary.\n@mixin pp-dev-dark-tints {\n .pp-dev-info__vars-btn {\n background-color: rgba(42, 141, 181, 0.14);\n border-color: rgba(42, 141, 181, 0.45);\n\n &:hover {\n background-color: rgba(42, 141, 181, 0.24);\n border-color: rgba(42, 141, 181, 0.65);\n }\n }\n\n .pp-dev-info__settings-group {\n border-top-color: rgba(255, 255, 255, 0.15);\n }\n}\n\n.pp-dev-info-namespace {\n // Define theme colors — light by default; overridden for Auto (media query, below) or an\n // explicit Dark/Light pick (html[data-pp-dev-theme], below — always wins on specificity).\n @include pp-dev-theme-colors($pp-dev-light-colors);\n\n html[data-pp-dev-theme='light'] & {\n @include pp-dev-theme-colors($pp-dev-light-colors);\n }\n\n .pp-dev-info__popup {\n position: fixed;\n top: 10px;\n right: 10px;\n width: 280px;\n max-width: calc(100vw - 20px);\n height: auto;\n background-color: var(--pp-dev-info-color-white);\n box-shadow: 0 2px 4px rgba(34, 34, 34, 0.2);\n border-radius: 2px;\n display: flex;\n flex-direction: column;\n align-items: flex-end;\n gap: 4px;\n padding: 12px;\n transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);\n overflow: hidden;\n z-index: 100001;\n border-left: 2px solid var(--pp-dev-info-color-primary);\n transform: translateX(0);\n opacity: 1;\n box-sizing: border-box;\n --pp-dev-info-popup-accent: var(--pp-dev-info-color-primary);\n\n &.entering {\n transform: translateX(calc(100% + 10px));\n opacity: 0;\n }\n\n &.exiting {\n transform: translateX(calc(100% + 10px));\n opacity: 0;\n }\n\n &-title {\n display: flex;\n align-items: flex-start;\n justify-content: space-between;\n gap: 8px;\n padding: 0;\n min-height: 0;\n width: 100%;\n box-sizing: border-box;\n border-bottom: 0;\n color: #040404;\n\n &-icon {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 16px;\n height: 16px;\n flex-shrink: 0;\n color: var(--pp-dev-info-popup-accent);\n }\n\n &-text {\n flex: 1;\n color: #040404;\n font-size: 12px;\n font-weight: 600;\n line-height: 18px;\n text-align: left;\n word-break: break-word;\n }\n\n &-close {\n cursor: pointer;\n min-width: 24px;\n min-height: 24px;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 0;\n border: 0;\n background: none;\n font: inherit;\n color: rgba(34, 34, 34, 0.64);\n border-radius: 2px;\n transition: color 0.2s ease;\n flex-shrink: 0;\n\n &:hover {\n color: var(--pp-dev-info-color-dark);\n }\n\n &:focus-visible {\n outline: 2px solid var(--pp-dev-info-popup-accent);\n outline-offset: 1px;\n }\n\n svg {\n transition: transform 0.2s ease;\n }\n }\n }\n\n &-content {\n margin-left: 24px;\n padding: 0;\n width: calc(100% - 24px);\n overflow-y: auto;\n box-sizing: border-box;\n flex-grow: 0;\n background-color: transparent;\n color: rgba(34, 34, 34, 0.8);\n font-size: 12px;\n font-weight: 400;\n line-height: 18px;\n text-align: left;\n word-break: break-word;\n white-space: pre-line;\n }\n\n &--success {\n --pp-dev-info-popup-accent: var(--pp-dev-info-color-success);\n border-left-color: var(--pp-dev-info-color-success);\n }\n\n &--danger {\n --pp-dev-info-popup-accent: var(--pp-dev-info-color-danger);\n border-left-color: var(--pp-dev-info-color-danger);\n }\n\n &--info {\n --pp-dev-info-popup-accent: var(--pp-dev-info-color-info);\n border-left-color: var(--pp-dev-info-color-info);\n }\n\n &--warning {\n --pp-dev-info-popup-accent: var(--pp-dev-info-color-warning);\n border-left-color: var(--pp-dev-info-color-warning);\n }\n\n // Popups anchored to the left screen edge (panel in a left corner)\n &--left {\n right: auto;\n left: 10px;\n border-left: none;\n border-right: 2px solid var(--pp-dev-info-popup-accent);\n\n &.entering,\n &.exiting {\n transform: translateX(calc(-100% - 10px));\n }\n }\n }\n\n &.pp-dev-info__confirm-overlay,\n .pp-dev-info__confirm-overlay {\n position: fixed;\n inset: 0;\n z-index: 100003;\n display: flex;\n align-items: center;\n justify-content: center;\n background-color: rgba(0, 0, 0, 0.35);\n backdrop-filter: blur(2px);\n -webkit-backdrop-filter: blur(2px);\n }\n\n .pp-dev-info__confirm {\n width: min(408px, calc(100vw - 32px));\n border-radius: 3px;\n border: 1px solid rgba(0, 0, 0, 0.15);\n background-color: var(--pp-dev-info-color-white);\n box-shadow: 0px 8px 32px 0px rgba(34, 34, 34, 0.4);\n overflow: hidden;\n\n &-title {\n padding: 14px 16px;\n font-size: 15px;\n font-weight: 600;\n border-bottom: 1px solid rgba(0, 0, 0, 0.08);\n color: var(--pp-dev-info-color-dark);\n background-color: rgba(0, 0, 0, 0.03);\n }\n\n &-content {\n padding: 16px;\n white-space: pre-wrap;\n color: var(--pp-dev-info-color-dark);\n font-size: 13px;\n line-height: 1.5;\n }\n\n &-actions {\n display: flex;\n flex-direction: column;\n gap: 8px;\n padding: 0 16px 16px;\n }\n\n &-btn {\n width: 100%;\n padding: 8px 12px;\n border-radius: 6px;\n border: 1px solid transparent;\n font-size: 13px;\n font-weight: 600;\n cursor: pointer;\n transition: all 0.2s ease;\n\n &--cancel {\n color: var(--pp-dev-info-color-dark);\n background-color: transparent;\n border-color: rgba(0, 0, 0, 0.2);\n }\n\n &--confirm {\n color: var(--pp-dev-info-color-white);\n background-color: var(--pp-dev-info-color-primary);\n }\n\n &:hover {\n opacity: 0.9;\n }\n }\n }\n\n &.pp-dev-info,\n .pp-dev-info {\n position: fixed;\n z-index: 100000;\n white-space: nowrap;\n width: fit-content;\n height: $pp-dev-height;\n text-align: center;\n padding: 2px;\n gap: 8px;\n background-color: var(--pp-dev-info-color-white);\n color: var(--pp-dev-info-color-dark);\n box-sizing: border-box;\n display: flex;\n align-items: center;\n transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);\n overflow: visible;\n backdrop-filter: blur(8px);\n -webkit-backdrop-filter: blur(8px);\n\n // Setup default values for styles that can be overlapped by other page styles\n // (e.g. color, font-family, font-size etc.)\n // This is needed to make sure that the extension styles are not overlapped by the page styles\n // and the extension is still usable\n * {\n color: var(--pp-dev-info-color-dark);\n font-family: 'Inter', sans-serif;\n font-size: 12px;\n font-weight: 400;\n line-height: 18px;\n letter-spacing: 0;\n text-decoration: none;\n text-transform: inherit;\n vertical-align: inherit;\n box-sizing: border-box;\n margin: 0;\n padding: 0;\n border: 0;\n outline: 0;\n background: 0 0;\n list-style: none;\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n }\n\n &__content {\n display: flex;\n align-items: center;\n gap: 0px;\n }\n\n &__section {\n display: flex;\n align-items: center;\n position: relative;\n padding: 0 8px;\n\n &:not(:last-child)::after {\n content: '';\n position: absolute;\n right: 0;\n top: 50%;\n transform: translateY(-50%);\n height: 16px;\n width: 1px;\n background-color: rgba(34, 34, 34, 0.24);\n\n html[data-pp-dev-theme='dark'] & {\n background-color: rgba(255, 255, 255, 0.2);\n }\n\n @media screen and (prefers-color-scheme: dark) {\n background-color: rgba(255, 255, 255, 0.2);\n\n html[data-pp-dev-theme='light'] & {\n background-color: rgba(34, 34, 34, 0.24);\n }\n }\n }\n\n &:first-child {\n padding-left: 0;\n }\n\n &:last-child {\n padding-right: 0;\n }\n }\n\n &__item {\n display: flex;\n align-items: center;\n gap: 8px;\n font-size: 12px;\n font-weight: 400;\n line-height: 18px;\n }\n\n &__label {\n color: var(--pp-dev-info-color-secondary);\n font-weight: 400;\n }\n\n &__value {\n font-weight: 600;\n }\n\n &__link {\n color: var(--pp-dev-info-color-primary);\n text-decoration: none;\n transition: color 0.2s ease;\n display: flex;\n align-items: center;\n gap: 8px;\n\n &:hover {\n color: var(--pp-dev-info-color-primary);\n text-decoration: underline;\n }\n }\n\n @mixin bold {\n font-weight: 700;\n }\n\n .primary {\n color: var(--pp-dev-info-color-primary) !important;\n }\n\n .success {\n color: var(--pp-dev-info-color-success) !important;\n }\n\n .info {\n color: var(--pp-dev-info-color-info) !important;\n }\n\n .warning {\n color: var(--pp-dev-info-color-warning) !important;\n }\n\n .danger {\n color: var(--pp-dev-info-color-danger) !important;\n }\n\n a {\n color: var(--pp-dev-info-color-primary);\n text-decoration: none;\n vertical-align: middle;\n line-height: 18px;\n\n &:hover {\n text-decoration: none;\n }\n\n &:active,\n &:hover {\n outline: 0;\n }\n }\n\n button {\n padding: 1px;\n border: none;\n background: none;\n cursor: pointer;\n outline: none;\n }\n\n .bold,\n .status-text {\n @include bold;\n }\n\n &__drag-handle {\n display: flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n width: 14px;\n height: 28px;\n cursor: grab;\n touch-action: none;\n border-radius: 3px;\n color: var(--pp-dev-info-color-secondary);\n opacity: 0.55;\n transition:\n opacity 0.2s ease,\n background-color 0.2s ease;\n\n svg {\n pointer-events: none;\n fill: currentColor;\n }\n\n &:hover {\n opacity: 1;\n background-color: rgba(7, 91, 126, 0.06);\n }\n }\n\n &__settings-btn {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 28px;\n height: 28px;\n padding: 6px;\n border-radius: 3px;\n cursor: pointer;\n color: var(--pp-dev-info-color-secondary);\n transition: all 0.2s ease;\n\n svg {\n width: 16px;\n height: 16px;\n fill: none;\n transition: transform 0.3s ease;\n }\n\n &:hover {\n color: var(--pp-dev-info-color-primary);\n background-color: rgba(7, 91, 126, 0.06);\n\n svg {\n transform: rotate(45deg);\n }\n }\n }\n\n &__settings {\n position: absolute;\n min-width: 216px;\n padding: 12px;\n display: flex;\n flex-direction: column;\n gap: 10px;\n background-color: var(--pp-dev-info-color-white);\n border: 1px solid rgba(34, 34, 34, 0.15);\n border-radius: 3px;\n box-shadow: 0 4px 16px rgba(34, 34, 34, 0.2);\n z-index: 100002;\n cursor: default;\n white-space: normal;\n text-align: left;\n\n .pp-dev-info__settings-title {\n font-size: 10px;\n font-weight: 600;\n color: var(--pp-dev-info-color-secondary);\n text-transform: uppercase;\n letter-spacing: 0.4px;\n }\n\n .pp-dev-info__settings-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n }\n\n .pp-dev-info__settings-toggle {\n appearance: auto;\n -webkit-appearance: checkbox;\n width: 14px;\n height: 14px;\n accent-color: var(--pp-dev-info-color-primary);\n cursor: pointer;\n flex-shrink: 0;\n }\n\n .pp-dev-info__corner-grid {\n display: grid;\n grid-template-columns: repeat(2, 30px);\n gap: 4px;\n }\n\n .pp-dev-info__corner-btn {\n width: 30px;\n height: 22px;\n border: 1px solid rgba(34, 34, 34, 0.25);\n border-radius: 3px;\n cursor: pointer;\n position: relative;\n background: transparent;\n transition: border-color 0.2s ease;\n\n &::after {\n content: '';\n position: absolute;\n width: 10px;\n height: 6px;\n border-radius: 1px;\n background-color: var(--pp-dev-info-color-secondary);\n transition: background-color 0.2s ease;\n }\n\n &--top-left::after {\n top: 2px;\n left: 2px;\n }\n\n &--top-right::after {\n top: 2px;\n right: 2px;\n }\n\n &--bottom-left::after {\n bottom: 2px;\n left: 2px;\n }\n\n &--bottom-right::after {\n bottom: 2px;\n right: 2px;\n }\n\n &:hover {\n border-color: var(--pp-dev-info-color-primary);\n }\n\n &.active {\n border-color: var(--pp-dev-info-color-primary);\n\n &::after {\n background-color: var(--pp-dev-info-color-primary);\n }\n }\n }\n\n .pp-dev-info__theme-grid {\n display: flex;\n gap: 4px;\n }\n\n .pp-dev-info__theme-btn {\n padding: 3px 8px;\n border: 1px solid rgba(34, 34, 34, 0.25);\n border-radius: 3px;\n cursor: pointer;\n background: transparent;\n font-size: 11px;\n line-height: 16px;\n color: var(--pp-dev-info-color-secondary);\n transition:\n border-color 0.2s ease,\n color 0.2s ease,\n background-color 0.2s ease;\n\n &:hover {\n border-color: var(--pp-dev-info-color-primary);\n color: var(--pp-dev-info-color-primary);\n }\n\n &.active {\n border-color: var(--pp-dev-info-color-primary);\n background-color: var(--pp-dev-info-color-primary);\n color: var(--pp-dev-info-color-white);\n }\n\n &:focus-visible {\n outline: 2px solid var(--pp-dev-info-color-primary);\n outline-offset: 1px;\n }\n }\n\n .pp-dev-info__settings-hide-btn {\n width: 100%;\n padding: 6px 10px;\n border: 1px solid rgba(34, 34, 34, 0.2);\n border-radius: 4px;\n cursor: pointer;\n font-weight: 600;\n color: var(--pp-dev-info-color-danger);\n transition: background-color 0.2s ease;\n\n &:hover {\n background-color: rgba(172, 43, 43, 0.06);\n }\n }\n\n .pp-dev-info__settings-group {\n display: flex;\n flex-direction: column;\n gap: 6px;\n padding-top: 10px;\n border-top: 1px solid rgba(34, 34, 34, 0.12);\n }\n\n .pp-dev-info__vars-buttons {\n display: flex;\n flex-direction: column;\n gap: 6px;\n }\n\n .pp-dev-info__vars-btn {\n width: 100%;\n padding: 6px 10px;\n border-radius: 4px;\n cursor: pointer;\n font-weight: 600;\n text-align: center;\n color: var(--pp-dev-info-color-primary);\n background-color: rgba(7, 91, 126, 0.1);\n border: 1px solid rgba(7, 91, 126, 0.4);\n transition:\n background-color 0.2s ease,\n border-color 0.2s ease;\n\n &:hover {\n background-color: rgba(7, 91, 126, 0.18);\n border-color: rgba(7, 91, 126, 0.6);\n }\n\n &:focus-visible {\n outline: 2px solid var(--pp-dev-info-color-primary);\n outline-offset: 1px;\n }\n }\n\n .pp-dev-info__settings-hint {\n font-size: 10px;\n line-height: 14px;\n color: var(--pp-dev-info-color-secondary);\n\n code {\n font-family: monospace;\n font-size: 10px;\n }\n }\n\n .pp-dev-info__settings-reset {\n align-self: flex-start;\n font-size: 11px;\n color: var(--pp-dev-info-color-primary);\n cursor: pointer;\n\n &:hover {\n text-decoration: underline;\n }\n }\n }\n\n // ── Users (PP-4597) ──────────────────────────────────────────────────────\n &__users-btn {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 28px;\n height: 28px;\n padding: 6px;\n border-radius: 3px;\n cursor: pointer;\n color: var(--pp-dev-info-color-secondary);\n transition: all 0.2s ease;\n\n svg {\n width: 16px;\n height: 16px;\n flex-shrink: 0;\n }\n\n &:hover {\n color: var(--pp-dev-info-color-primary);\n background-color: rgba(7, 91, 126, 0.06);\n }\n }\n\n // A saved user's tab: the button stands out, so the tab is never taken for your own.\n &.pp-dev-info--user-tab .pp-dev-info__users-btn {\n color: #ffffff;\n background-color: #7a3fb8;\n\n &:hover {\n background-color: #8d52cc;\n }\n }\n\n &__users.pp-dev-info__settings {\n width: 320px;\n max-width: calc(100vw - 16px);\n box-sizing: border-box;\n\n .pp-dev-info__users-tab {\n font-size: 12px;\n line-height: 16px;\n color: var(--pp-dev-info-color-dark);\n\n span {\n display: block;\n font-size: 10px;\n color: var(--pp-dev-info-color-secondary);\n }\n }\n\n .pp-dev-info__users-list {\n display: flex;\n flex-direction: column;\n gap: 2px;\n max-height: 240px;\n overflow-y: auto;\n }\n\n .pp-dev-info__users-row {\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 5px 6px;\n border-radius: 3px;\n border-left: 2px solid transparent;\n\n &.is-current {\n border-left-color: #7a3fb8;\n background-color: rgba(122, 63, 184, 0.08);\n }\n }\n\n .pp-dev-info__users-default {\n appearance: auto;\n -webkit-appearance: radio;\n width: 13px;\n height: 13px;\n margin: 0;\n flex-shrink: 0;\n accent-color: var(--pp-dev-info-color-primary);\n cursor: pointer;\n\n &:disabled {\n cursor: not-allowed;\n }\n }\n\n .pp-dev-info__users-who {\n display: flex;\n flex-direction: column;\n min-width: 0;\n flex: 1;\n }\n\n .pp-dev-info__users-name {\n font-size: 12px;\n font-weight: 600;\n color: var(--pp-dev-info-color-dark);\n overflow: hidden;\n text-overflow: ellipsis;\n }\n\n .pp-dev-info__users-meta {\n font-size: 10px;\n line-height: 13px;\n color: var(--pp-dev-info-color-secondary);\n overflow: hidden;\n text-overflow: ellipsis;\n }\n\n .needs-login .pp-dev-info__users-meta {\n color: var(--pp-dev-info-color-danger);\n }\n\n .pp-dev-info__users-action {\n flex-shrink: 0;\n padding: 2px 7px;\n border: 1px solid rgba(34, 34, 34, 0.25);\n border-radius: 3px;\n background: transparent;\n cursor: pointer;\n font-size: 11px;\n line-height: 16px;\n color: var(--pp-dev-info-color-primary);\n\n &:hover {\n border-color: var(--pp-dev-info-color-primary);\n }\n\n &--accent {\n border-color: var(--pp-dev-info-color-primary);\n background-color: var(--pp-dev-info-color-primary);\n color: var(--pp-dev-info-color-white);\n }\n }\n\n .pp-dev-info__users-remove {\n flex-shrink: 0;\n width: 20px;\n height: 20px;\n padding: 0;\n border: none;\n border-radius: 3px;\n background: transparent;\n cursor: pointer;\n font-size: 15px;\n line-height: 20px;\n color: var(--pp-dev-info-color-secondary);\n\n &:hover {\n color: var(--pp-dev-info-color-danger);\n background-color: rgba(172, 43, 43, 0.08);\n }\n }\n\n .pp-dev-info__users-input {\n width: 100%;\n box-sizing: border-box;\n padding: 5px 8px;\n border: 1px solid rgba(34, 34, 34, 0.25);\n border-radius: 3px;\n background: transparent;\n font-size: 12px;\n color: var(--pp-dev-info-color-dark);\n\n &:focus {\n outline: none;\n border-color: var(--pp-dev-info-color-primary);\n }\n }\n\n .pp-dev-info__users-error,\n .pp-dev-info__users-unavailable {\n font-size: 11px;\n line-height: 15px;\n color: var(--pp-dev-info-color-danger);\n }\n\n .pp-dev-info__users-unavailable {\n color: var(--pp-dev-info-color-secondary);\n }\n\n [hidden] {\n display: none !important;\n }\n }\n\n &__wrap-btn {\n box-sizing: border-box;\n display: flex;\n align-items: center;\n justify-content: center;\n width: 28px;\n height: 28px;\n padding: 6px;\n cursor: pointer;\n border-radius: 3px;\n transition: background-color 0.2s ease;\n\n &:hover {\n background-color: rgba(7, 91, 126, 0.06);\n animation: arrowHover 0.8s linear infinite;\n }\n\n &-arrow {\n width: 16px;\n height: 16px;\n color: var(--pp-dev-info-color-dark);\n fill: none;\n transition: transform 0.2s ease;\n }\n }\n\n &.closed {\n z-index: 100002;\n\n & .pp-dev-info__wrap-btn {\n &:hover {\n animation: arrowHover 1.2s linear infinite;\n }\n }\n }\n\n // Corner variants: placement, center-facing radius/shadow, direction-aware\n // minimize (.closed) and auto-hide slides. The corner class is always rendered\n // on the panel root (server default: bottom-right).\n @each $name, $c in $pp-dev-corners {\n &.pp-dev-info--#{$name} {\n #{map.get($c, v)}: 0;\n #{map.get($c, h)}: 0;\n border-radius: map.get($c, radius);\n box-shadow: map.get($c, shadow);\n #{map.get($c, edge)}: 1px solid rgba(34, 34, 34, 0.08);\n\n .pp-dev-info__settings {\n #{map.get($c, v)}: calc(100% + 8px);\n #{map.get($c, h)}: 0;\n }\n\n @if map.get($c, sx) == 1 {\n &.closed {\n transform: translateX(calc(100% - $pp-dev-height));\n\n .pp-dev-info__wrap-btn-arrow {\n transform: rotate(180deg);\n }\n }\n\n &.pp-dev-info--auto-hide:not(.is-peeking):not(.is-dragging) {\n transform: translateX(calc(100% - $pp-dev-peek));\n box-shadow:\n map.get($c, shadow),\n inset $pp-dev-peek 0 0 0 var(--pp-dev-info-color-primary);\n\n // Extended invisible hover zone on the inner side of the 4px strip\n &::after {\n content: '';\n position: absolute;\n top: -6px;\n bottom: -6px;\n left: -8px;\n width: 8px;\n }\n }\n } @else {\n .pp-dev-info__wrap-btn {\n order: 99;\n\n &-arrow {\n transform: rotate(180deg);\n }\n }\n\n &.closed {\n transform: translateX(calc(-100% + $pp-dev-height));\n\n .pp-dev-info__wrap-btn-arrow {\n transform: rotate(0deg);\n }\n }\n\n &.pp-dev-info--auto-hide:not(.is-peeking):not(.is-dragging) {\n transform: translateX(calc(-100% + $pp-dev-peek));\n box-shadow:\n map.get($c, shadow),\n inset (-$pp-dev-peek) 0 0 0 var(--pp-dev-info-color-primary);\n\n &::after {\n content: '';\n position: absolute;\n top: -6px;\n bottom: -6px;\n right: -8px;\n width: 8px;\n }\n }\n }\n }\n }\n\n &.pp-dev-info--auto-hide {\n z-index: 100002;\n }\n\n &.is-dragging {\n transition: none;\n user-select: none;\n cursor: grabbing;\n\n .pp-dev-info__drag-handle {\n cursor: grabbing;\n }\n }\n\n &.pp-dev-info--hidden {\n display: none;\n }\n\n .sync-button {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 28px;\n height: 28px;\n padding: 6px;\n background-color: transparent;\n border-radius: 3px;\n cursor: pointer;\n color: var(--pp-dev-info-color-primary);\n transition: all 0.2s ease;\n position: relative;\n overflow: hidden;\n\n &::before {\n content: '';\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n background-color: currentColor;\n opacity: 0;\n transition: opacity 0.2s ease;\n border-radius: 3px;\n }\n\n svg {\n width: 16px;\n height: 16px;\n position: relative;\n z-index: 1;\n transition: transform 0.3s ease;\n }\n\n &:hover:not(.disabled) {\n color: var(--pp-dev-info-color-primary);\n background-color: rgba(7, 91, 126, 0.06);\n\n svg {\n transform: scale(1.1) rotate(45deg);\n transition: transform 0.2s ease;\n }\n }\n\n &:active:not(.disabled) {\n svg {\n transform: scale(0.9) rotate(45deg);\n }\n }\n\n &.disabled {\n opacity: 0.5;\n cursor: not-allowed;\n pointer-events: none;\n color: var(--pp-dev-info-color-secondary);\n }\n\n &.syncing {\n svg {\n animation: rotate 1.5s linear infinite;\n }\n }\n }\n\n &__sep {\n display: inline-flex;\n flex-shrink: 0;\n width: 1px;\n height: 16px;\n background-color: rgba(34, 34, 34, 0.24);\n\n html[data-pp-dev-theme='dark'] & {\n background-color: rgba(255, 255, 255, 0.2);\n }\n\n @media screen and (prefers-color-scheme: dark) {\n background-color: rgba(255, 255, 255, 0.2);\n\n html[data-pp-dev-theme='light'] & {\n background-color: rgba(34, 34, 34, 0.24);\n }\n }\n }\n\n @keyframes rotate {\n from {\n transform: rotate(0deg);\n }\n to {\n transform: rotate(360deg);\n }\n }\n\n .margin-5 {\n margin-right: 5px;\n }\n\n .margin-10 {\n margin-right: 10px;\n }\n\n .custom-font {\n font-family: 'Inter', sans-serif;\n font-size: 13px;\n font-weight: 400;\n vertical-align: middle;\n line-height: 18px;\n color: var(--pp-dev-info-color-dark);\n\n &:not(.not-colored) a {\n color: var(--pp-dev-info-color-primary) !important;\n }\n }\n\n @keyframes rotation {\n 0% {\n transform: rotate(0deg);\n }\n 100% {\n transform: rotate(360deg);\n }\n }\n\n @keyframes arrowHover {\n 0% {\n transform: translateX(2px);\n }\n 50% {\n transform: translateX(-2px);\n }\n 100% {\n transform: translateX(2px);\n }\n }\n\n // Explicit Dark pick (html[data-pp-dev-theme='dark'], set via the settings popover's Theme\n // row — see src/client/theme.ts) — always wins over both the light default above and the\n // Auto media query below, on selector specificity alone.\n html[data-pp-dev-theme='dark'] & {\n @include pp-dev-theme-colors($pp-dev-dark-colors);\n @include pp-dev-dark-tints;\n }\n\n // Auto (OS/browser preference) — same values, only takes effect when no explicit\n // html[data-pp-dev-theme] override is set (those always outrank a plain :root/& selector).\n @media screen and (prefers-color-scheme: dark) {\n @include pp-dev-theme-colors($pp-dev-dark-colors);\n @include pp-dev-dark-tints;\n }\n }\n\n @media (prefers-reduced-motion: reduce) {\n &.pp-dev-info,\n .pp-dev-info,\n .pp-dev-info__popup,\n .pp-dev-info__settings {\n transition: none;\n }\n }\n}\n"]}
1
+ {"version":3,"sourceRoot":"","sources":["client.scss"],"names":[],"mappings":"AAqGA,uBAtBI,wcA2BF,qDA3BE,wcA+BF,2CACE,eACA,SACA,WACA,YACA,6BACA,YACA,gDACA,uCACA,kBACA,aACA,sBACA,qBACA,QACA,aACA,gDACA,gBACA,eACA,uDACA,wBACA,UACA,sBACA,6DAEA,oDACE,wCACA,UAGF,mDACE,wCACA,UAGF,iDACE,aACA,uBACA,8BACA,QACA,UACA,aACA,WACA,sBACA,gBACA,cAEA,sDACE,aACA,mBACA,uBACA,WACA,YACA,cACA,sCAGF,sDACE,OACA,cACA,eACA,gBACA,iBACA,gBACA,sBAGF,uDACE,eACA,eACA,gBACA,aACA,mBACA,uBACA,UACA,SACA,gBACA,aACA,yBACA,kBACA,0BACA,cAEA,6DACE,oCAGF,qEACE,kDACA,mBAGF,2DACE,8BAKN,mDACE,iBACA,UACA,wBACA,gBACA,sBACA,YACA,+BACA,wBACA,eACA,gBACA,iBACA,gBACA,sBACA,qBAGF,oDACE,6DACA,mDAGF,mDACE,4DACA,kDAGF,iDACE,0DACA,gDAGF,oDACE,6DACA,mDAIF,iDACE,WACA,UACA,iBACA,uDAEA,mHAEE,yCAKN,yGAEE,eACA,QACA,eACA,aACA,mBACA,uBACA,iCACA,0BACA,kCAGF,6CACE,8BACA,kBACA,iCACA,gDACA,8CACA,gBAEA,mDACE,kBACA,eACA,gBACA,wCACA,oCACA,iCAGF,qDACE,aACA,qBACA,oCACA,eACA,gBAGF,qDACE,aACA,sBACA,QACA,oBAGF,iDACE,WACA,iBACA,kBACA,+BACA,eACA,gBACA,eACA,wBAEA,yDACE,oCACA,+BACA,4BAGF,0DACE,qCACA,kDAGF,uDACE,WAKN,uEAEE,eACA,eACA,mBACA,kBACA,OA9UY,KA+UZ,kBACA,YACA,QACA,gDACA,oCACA,sBACA,aACA,mBACA,gDACA,iBACA,0BACA,kCAMA,2EACE,oCACA,+BACA,eACA,gBACA,iBACA,iBACA,qBACA,uBACA,uBACA,sBACA,SACA,UACA,SACA,UACA,eACA,gBACA,mCACA,kCAGF,yFACE,aACA,mBACA,QAGF,yFACE,aACA,mBACA,kBACA,cAEA,yIACE,WACA,kBACA,QACA,QACA,2BACA,YACA,UACA,oCAEA,mMACE,sCAGF,+CAdF,yIAeI,sCAEA,qMACE,qCAKN,iHACE,eAGF,+GACE,gBAIJ,mFACE,aACA,mBACA,QACA,eACA,gBACA,iBAGF,qFACE,yCACA,gBAGF,qFACE,gBAGF,mFACE,uCACA,qBACA,0BACA,aACA,mBACA,QAEA,+FACE,uCACA,0BAQJ,yFACE,kDAGF,yFACE,kDAGF,mFACE,+CAGF,yFACE,kDAGF,uFACE,iDAGF,2EACE,uCACA,qBACA,sBACA,iBAEA,uFACE,qBAGF,gLAEE,UAIJ,qFACE,YACA,YACA,gBACA,eACA,aAGF,oLA/CE,gBAoDF,iGACE,aACA,mBACA,uBACA,cACA,WACA,YACA,YACA,kBACA,kBACA,yCACA,YACA,WACE,2CAGF,yGACE,oBACA,kBAGF,6GACE,UACA,oCAIJ,mGACE,aACA,mBACA,uBACA,WACA,YACA,YACA,kBACA,eACA,yCACA,wBAEA,2GACE,WACA,YACA,UACA,8BAGF,+GACE,uCACA,oCAEA,uHACE,wBAKN,2FACE,kBACA,gBACA,aACA,aACA,sBACA,SACA,gDACA,oCACA,kBACA,wCACA,eACA,eACA,mBACA,gBAEA,qJACE,eACA,gBACA,yCACA,yBACA,oBAGF,iJACE,aACA,mBACA,8BACA,SAGF,uJACE,gBACA,4BACA,WACA,YACA,8CACA,eACA,cAGF,+IACE,aACA,sCACA,QAGF,6IACE,WACA,YACA,+CACA,kBACA,eACA,kBACA,yBACA,iCAEA,2JACE,WACA,kBACA,WACA,WACA,kBACA,oDACA,qCAGF,+KACE,QACA,SAGF,iLACE,QACA,UAGF,qLACE,WACA,SAGF,uLACE,WACA,UAGF,yJACE,8CAGF,2JACE,8CAEA,yKACE,kDAKN,6IACE,aACA,QAGF,2IACE,gBACA,+CACA,kBACA,eACA,yBACA,eACA,iBACA,yCACA,WACE,+DAIF,uJACE,8CACA,uCAGF,yJACE,8CACA,kDACA,qCAGF,uKACE,mDACA,mBAIJ,2JACE,WACA,iBACA,mCACA,kBACA,eACA,gBACA,sCACA,qCAEA,uKACE,qCAIJ,qJACE,aACA,sBACA,QACA,iBACA,wCAGF,iJACE,aACA,sBACA,QAGF,yIACE,WACA,iBACA,kBACA,eACA,gBACA,kBACA,uCACA,mCACA,mCACA,WACE,gDAGF,qJACE,oCACA,+BAGF,qKACE,mDACA,mBAIJ,mJACE,eACA,iBACA,yCAEA,6JACE,sBACA,eAIJ,qJACE,sBACA,eACA,uCACA,eAEA,iKACE,0BAMN,6FACE,aACA,mBACA,uBACA,WACA,YACA,YACA,kBACA,eACA,yCACA,wBAEA,qGACE,WACA,YACA,cAGF,yGACE,uCACA,oCAKJ,mKACE,WACA,yBAEA,+KACE,yBAIJ,iIACE,YACA,6BACA,sBAEA,iLACE,eACA,iBACA,oCAIF,6LACE,mBACA,iBACA,cACA,qBACA,yBACA,yCAGF,qLACE,gBACA,cACA,8BACA,kBACA,cACA,gBACA,iBACA,mBACA,mCAGF,mLACE,aACA,sBACA,QACA,iBACA,gBAGF,iLACE,aACA,mBACA,QACA,gBACA,kBACA,oCAEA,uMACE,0BACA,sCAIJ,yLACE,gBACA,yBACA,WACA,YACA,SACA,cACA,8CACA,eAEA,2MACE,mBAIJ,iLACE,aACA,sBACA,YACA,OAGF,mLACE,eACA,gBACA,oCACA,gBACA,uBAGF,mLACE,eACA,iBACA,yCACA,gBACA,uBAGF,6MACE,sCAGF,uLACE,cACA,gBACA,+CACA,kBACA,yBACA,eACA,eACA,iBACA,uCAEA,mMACE,8CAGF,uMACE,8CACA,kDACA,qCAKJ,mLACE,sBACA,oBACA,QACA,SAGF,mMACE,aACA,yBACA,QAGF,uLACE,cACA,WACA,YACA,UACA,YACA,kBACA,yBACA,eACA,eACA,iBACA,yCAEA,mMACE,sCACA,qCAIJ,qLACE,WACA,sBACA,gBACA,+CACA,kBACA,yBACA,eACA,oCAEA,iMACE,aACA,8CAIJ,sXAEE,eACA,iBACA,sCAGF,iMACE,yCAGF,mJACE,wBAIJ,2FACE,sBACA,aACA,mBACA,uBACA,WACA,YACA,YACA,eACA,kBACA,qCAEA,uGACE,oCACA,yCAGF,uGACE,WACA,YACA,oCACA,UACA,8BAIJ,qFACE,eAGE,+IACE,0CASJ,mHACE,MACA,OACA,wBACA,4CACA,2CAEA,iKACE,qBACA,OA6BA,iKACE,SAEA,6KACE,yBAIJ,iIACE,yCAEA,2LACE,uBAIJ,uOACE,wCACA,WACE,mFAGF,qPACE,WACA,kBACA,SACA,YACA,WACA,UAlER,qHACE,MACA,QACA,wBACA,6CACA,2CAEA,mKACE,qBACA,QAIA,mIACE,wCAEA,6LACE,yBAIJ,yOACE,uCACA,WACE,mFAIF,uPACE,WACA,kBACA,SACA,YACA,UACA,UAlCR,yHACE,SACA,OACA,wBACA,6CACA,wCAEA,uKACE,wBACA,OA6BA,uKACE,SAEA,mLACE,yBAIJ,uIACE,yCAEA,iMACE,uBAIJ,6OACE,wCACA,WACE,oFAGF,2PACE,WACA,kBACA,SACA,YACA,WACA,UAlER,2HACE,SACA,QACA,wBACA,8CACA,wCAEA,yKACE,wBACA,QAIA,yIACE,wCAEA,mMACE,yBAIJ,+OACE,uCACA,WACE,oFAIF,6PACE,WACA,kBACA,SACA,YACA,UACA,UAuCV,qHACE,eAGF,+FACE,gBACA,iBACA,gBAEA,mJACE,gBAIJ,+GACE,aAGF,iGACE,aACA,mBACA,uBACA,WACA,YACA,YACA,+BACA,kBACA,eACA,uCACA,wBACA,kBACA,gBAEA,iHACE,WACA,kBACA,MACA,OACA,WACA,YACA,8BACA,UACA,4BACA,kBAGF,yGACE,WACA,YACA,kBACA,UACA,8BAGF,2IACE,uCACA,oCAEA,mJACE,mCACA,8BAKF,qJACE,mCAIJ,mHACE,WACA,mBACA,oBACA,yCAIA,yHACE,sCAKN,iFACE,oBACA,cACA,UACA,YACA,oCAEA,2IACE,sCAGF,+CAXF,iFAYI,sCAEA,6IACE,qCAKN,kBACE,KACE,uBAEF,GACE,0BAIJ,2FACE,iBAGF,6FACE,kBAGF,iGACE,+BACA,eACA,gBACA,sBACA,iBACA,oCAEA,yIACE,kDAIJ,oBACE,GACE,uBAEF,KACE,0BAIJ,sBACE,GACE,0BAEF,IACE,2BAEF,KACE,2BAOJ,iIAhqCA,6cAOF,+KACE,sCACA,kCAEA,2LACE,sCACA,kCAIJ,2LACE,uCAqpCA,+CA56BF,uEA3PE,6cAOF,qHACE,sCACA,kCAEA,iIACE,sCACA,kCAIJ,iIACE,wCA2pCF,uCACE,gKAIE","file":"client.css","sourcesContent":["@use 'sass:map';\n\n$pp-dev-height: 32px;\n$pp-dev-peek: 4px;\n\n// Corner map: v/h — anchored edges; sx — horizontal slide direction for minimize/auto-hide\n// (1 → slides right, -1 → slides left); radius/shadow face the viewport center;\n// edge — hairline border on the exposed horizontal edge.\n$pp-dev-corners: (\n 'top-left': (\n v: top,\n h: left,\n sx: -1,\n radius: 0 0 4px 0,\n shadow: 2px 2px 8px 0 rgba(34, 34, 34, 0.08),\n edge: border-bottom,\n ),\n 'top-right': (\n v: top,\n h: right,\n sx: 1,\n radius: 0 0 0 4px,\n shadow: -2px 2px 8px 0 rgba(34, 34, 34, 0.08),\n edge: border-bottom,\n ),\n 'bottom-left': (\n v: bottom,\n h: left,\n sx: -1,\n radius: 0 4px 0 0,\n shadow: 2px -2px 8px 0 rgba(34, 34, 34, 0.08),\n edge: border-top,\n ),\n 'bottom-right': (\n v: bottom,\n h: right,\n sx: 1,\n radius: 4px 0 0 0,\n shadow: -2px -2px 8px 0 rgba(34, 34, 34, 0.08),\n edge: border-top,\n ),\n);\n\n// Light/dark color maps, shared by the default (light) rules, the prefers-color-scheme:dark\n// media query (Auto), and the explicit data-pp-dev-theme override (Dark/Light picked in the\n// panel's settings popover — see src/client/theme.ts). One place to edit either palette.\n$pp-dev-light-colors: (\n primary: #075b7e,\n secondary: rgba(34, 34, 34, 0.64),\n success: #077e45,\n info: #17a2b8,\n warning: #ffb000,\n danger: #ac2b2b,\n light: #f8f9fa,\n dark: #343a40,\n 'white': #ffffff,\n 'black': #000000,\n // Outline of inputs and outlined buttons.\n line: rgba(34, 34, 34, 0.25),\n // The \"this tab\" badge in the Users popover.\n tab: #7a3fb8,\n);\n$pp-dev-dark-colors: (\n primary: #2a8db5,\n secondary: rgba(220, 220, 220, 0.64),\n success: #0fad5e,\n info: #2dccff,\n warning: #ffb302,\n danger: #e05252,\n light: #343a40,\n dark: #f8f9fa,\n 'white': #000000,\n 'black': #ffffff,\n line: rgba(220, 220, 220, 0.3),\n tab: #b48ae6,\n);\n\n@mixin pp-dev-theme-colors($colors) {\n @each $name, $value in $colors {\n --pp-dev-info-color-#{$name}: #{$value};\n }\n}\n\n// rgba() literals don't follow the --pp-dev-info-color-primary reassignment above,\n// so the button fill/border are re-tinted here to match the dark-mode primary.\n@mixin pp-dev-dark-tints {\n .pp-dev-info__vars-btn {\n background-color: rgba(42, 141, 181, 0.14);\n border-color: rgba(42, 141, 181, 0.45);\n\n &:hover {\n background-color: rgba(42, 141, 181, 0.24);\n border-color: rgba(42, 141, 181, 0.65);\n }\n }\n\n .pp-dev-info__settings-group {\n border-top-color: rgba(255, 255, 255, 0.15);\n }\n}\n\n.pp-dev-info-namespace {\n // Define theme colors — light by default; overridden for Auto (media query, below) or an\n // explicit Dark/Light pick (html[data-pp-dev-theme], below — always wins on specificity).\n @include pp-dev-theme-colors($pp-dev-light-colors);\n\n html[data-pp-dev-theme='light'] & {\n @include pp-dev-theme-colors($pp-dev-light-colors);\n }\n\n .pp-dev-info__popup {\n position: fixed;\n top: 10px;\n right: 10px;\n width: 280px;\n max-width: calc(100vw - 20px);\n height: auto;\n background-color: var(--pp-dev-info-color-white);\n box-shadow: 0 2px 4px rgba(34, 34, 34, 0.2);\n border-radius: 2px;\n display: flex;\n flex-direction: column;\n align-items: flex-end;\n gap: 4px;\n padding: 12px;\n transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);\n overflow: hidden;\n z-index: 100001;\n border-left: 2px solid var(--pp-dev-info-color-primary);\n transform: translateX(0);\n opacity: 1;\n box-sizing: border-box;\n --pp-dev-info-popup-accent: var(--pp-dev-info-color-primary);\n\n &.entering {\n transform: translateX(calc(100% + 10px));\n opacity: 0;\n }\n\n &.exiting {\n transform: translateX(calc(100% + 10px));\n opacity: 0;\n }\n\n &-title {\n display: flex;\n align-items: flex-start;\n justify-content: space-between;\n gap: 8px;\n padding: 0;\n min-height: 0;\n width: 100%;\n box-sizing: border-box;\n border-bottom: 0;\n color: #040404;\n\n &-icon {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 16px;\n height: 16px;\n flex-shrink: 0;\n color: var(--pp-dev-info-popup-accent);\n }\n\n &-text {\n flex: 1;\n color: #040404;\n font-size: 12px;\n font-weight: 600;\n line-height: 18px;\n text-align: left;\n word-break: break-word;\n }\n\n &-close {\n cursor: pointer;\n min-width: 24px;\n min-height: 24px;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 0;\n border: 0;\n background: none;\n font: inherit;\n color: rgba(34, 34, 34, 0.64);\n border-radius: 2px;\n transition: color 0.2s ease;\n flex-shrink: 0;\n\n &:hover {\n color: var(--pp-dev-info-color-dark);\n }\n\n &:focus-visible {\n outline: 2px solid var(--pp-dev-info-popup-accent);\n outline-offset: 1px;\n }\n\n svg {\n transition: transform 0.2s ease;\n }\n }\n }\n\n &-content {\n margin-left: 24px;\n padding: 0;\n width: calc(100% - 24px);\n overflow-y: auto;\n box-sizing: border-box;\n flex-grow: 0;\n background-color: transparent;\n color: rgba(34, 34, 34, 0.8);\n font-size: 12px;\n font-weight: 400;\n line-height: 18px;\n text-align: left;\n word-break: break-word;\n white-space: pre-line;\n }\n\n &--success {\n --pp-dev-info-popup-accent: var(--pp-dev-info-color-success);\n border-left-color: var(--pp-dev-info-color-success);\n }\n\n &--danger {\n --pp-dev-info-popup-accent: var(--pp-dev-info-color-danger);\n border-left-color: var(--pp-dev-info-color-danger);\n }\n\n &--info {\n --pp-dev-info-popup-accent: var(--pp-dev-info-color-info);\n border-left-color: var(--pp-dev-info-color-info);\n }\n\n &--warning {\n --pp-dev-info-popup-accent: var(--pp-dev-info-color-warning);\n border-left-color: var(--pp-dev-info-color-warning);\n }\n\n // Popups anchored to the left screen edge (panel in a left corner)\n &--left {\n right: auto;\n left: 10px;\n border-left: none;\n border-right: 2px solid var(--pp-dev-info-popup-accent);\n\n &.entering,\n &.exiting {\n transform: translateX(calc(-100% - 10px));\n }\n }\n }\n\n &.pp-dev-info__confirm-overlay,\n .pp-dev-info__confirm-overlay {\n position: fixed;\n inset: 0;\n z-index: 100003;\n display: flex;\n align-items: center;\n justify-content: center;\n background-color: rgba(0, 0, 0, 0.35);\n backdrop-filter: blur(2px);\n -webkit-backdrop-filter: blur(2px);\n }\n\n .pp-dev-info__confirm {\n width: min(408px, calc(100vw - 32px));\n border-radius: 3px;\n border: 1px solid rgba(0, 0, 0, 0.15);\n background-color: var(--pp-dev-info-color-white);\n box-shadow: 0px 8px 32px 0px rgba(34, 34, 34, 0.4);\n overflow: hidden;\n\n &-title {\n padding: 14px 16px;\n font-size: 15px;\n font-weight: 600;\n border-bottom: 1px solid rgba(0, 0, 0, 0.08);\n color: var(--pp-dev-info-color-dark);\n background-color: rgba(0, 0, 0, 0.03);\n }\n\n &-content {\n padding: 16px;\n white-space: pre-wrap;\n color: var(--pp-dev-info-color-dark);\n font-size: 13px;\n line-height: 1.5;\n }\n\n &-actions {\n display: flex;\n flex-direction: column;\n gap: 8px;\n padding: 0 16px 16px;\n }\n\n &-btn {\n width: 100%;\n padding: 8px 12px;\n border-radius: 6px;\n border: 1px solid transparent;\n font-size: 13px;\n font-weight: 600;\n cursor: pointer;\n transition: all 0.2s ease;\n\n &--cancel {\n color: var(--pp-dev-info-color-dark);\n background-color: transparent;\n border-color: rgba(0, 0, 0, 0.2);\n }\n\n &--confirm {\n color: var(--pp-dev-info-color-white);\n background-color: var(--pp-dev-info-color-primary);\n }\n\n &:hover {\n opacity: 0.9;\n }\n }\n }\n\n &.pp-dev-info,\n .pp-dev-info {\n position: fixed;\n z-index: 100000;\n white-space: nowrap;\n width: fit-content;\n height: $pp-dev-height;\n text-align: center;\n padding: 2px;\n gap: 8px;\n background-color: var(--pp-dev-info-color-white);\n color: var(--pp-dev-info-color-dark);\n box-sizing: border-box;\n display: flex;\n align-items: center;\n transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);\n overflow: visible;\n backdrop-filter: blur(8px);\n -webkit-backdrop-filter: blur(8px);\n\n // Setup default values for styles that can be overlapped by other page styles\n // (e.g. color, font-family, font-size etc.)\n // This is needed to make sure that the extension styles are not overlapped by the page styles\n // and the extension is still usable\n * {\n color: var(--pp-dev-info-color-dark);\n font-family: 'Inter', sans-serif;\n font-size: 12px;\n font-weight: 400;\n line-height: 18px;\n letter-spacing: 0;\n text-decoration: none;\n text-transform: inherit;\n vertical-align: inherit;\n box-sizing: border-box;\n margin: 0;\n padding: 0;\n border: 0;\n outline: 0;\n background: 0 0;\n list-style: none;\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n }\n\n &__content {\n display: flex;\n align-items: center;\n gap: 0px;\n }\n\n &__section {\n display: flex;\n align-items: center;\n position: relative;\n padding: 0 8px;\n\n &:not(:last-child)::after {\n content: '';\n position: absolute;\n right: 0;\n top: 50%;\n transform: translateY(-50%);\n height: 16px;\n width: 1px;\n background-color: rgba(34, 34, 34, 0.24);\n\n html[data-pp-dev-theme='dark'] & {\n background-color: rgba(255, 255, 255, 0.2);\n }\n\n @media screen and (prefers-color-scheme: dark) {\n background-color: rgba(255, 255, 255, 0.2);\n\n html[data-pp-dev-theme='light'] & {\n background-color: rgba(34, 34, 34, 0.24);\n }\n }\n }\n\n &:first-child {\n padding-left: 0;\n }\n\n &:last-child {\n padding-right: 0;\n }\n }\n\n &__item {\n display: flex;\n align-items: center;\n gap: 8px;\n font-size: 12px;\n font-weight: 400;\n line-height: 18px;\n }\n\n &__label {\n color: var(--pp-dev-info-color-secondary);\n font-weight: 400;\n }\n\n &__value {\n font-weight: 600;\n }\n\n &__link {\n color: var(--pp-dev-info-color-primary);\n text-decoration: none;\n transition: color 0.2s ease;\n display: flex;\n align-items: center;\n gap: 8px;\n\n &:hover {\n color: var(--pp-dev-info-color-primary);\n text-decoration: underline;\n }\n }\n\n @mixin bold {\n font-weight: 700;\n }\n\n .primary {\n color: var(--pp-dev-info-color-primary) !important;\n }\n\n .success {\n color: var(--pp-dev-info-color-success) !important;\n }\n\n .info {\n color: var(--pp-dev-info-color-info) !important;\n }\n\n .warning {\n color: var(--pp-dev-info-color-warning) !important;\n }\n\n .danger {\n color: var(--pp-dev-info-color-danger) !important;\n }\n\n a {\n color: var(--pp-dev-info-color-primary);\n text-decoration: none;\n vertical-align: middle;\n line-height: 18px;\n\n &:hover {\n text-decoration: none;\n }\n\n &:active,\n &:hover {\n outline: 0;\n }\n }\n\n button {\n padding: 1px;\n border: none;\n background: none;\n cursor: pointer;\n outline: none;\n }\n\n .bold,\n .status-text {\n @include bold;\n }\n\n &__drag-handle {\n display: flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n width: 14px;\n height: 28px;\n cursor: grab;\n touch-action: none;\n border-radius: 3px;\n color: var(--pp-dev-info-color-secondary);\n opacity: 0.55;\n transition:\n opacity 0.2s ease,\n background-color 0.2s ease;\n\n svg {\n pointer-events: none;\n fill: currentColor;\n }\n\n &:hover {\n opacity: 1;\n background-color: rgba(7, 91, 126, 0.06);\n }\n }\n\n &__settings-btn {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 28px;\n height: 28px;\n padding: 6px;\n border-radius: 3px;\n cursor: pointer;\n color: var(--pp-dev-info-color-secondary);\n transition: all 0.2s ease;\n\n svg {\n width: 16px;\n height: 16px;\n fill: none;\n transition: transform 0.3s ease;\n }\n\n &:hover {\n color: var(--pp-dev-info-color-primary);\n background-color: rgba(7, 91, 126, 0.06);\n\n svg {\n transform: rotate(45deg);\n }\n }\n }\n\n &__settings {\n position: absolute;\n min-width: 216px;\n padding: 12px;\n display: flex;\n flex-direction: column;\n gap: 10px;\n background-color: var(--pp-dev-info-color-white);\n border: 1px solid rgba(34, 34, 34, 0.15);\n border-radius: 3px;\n box-shadow: 0 4px 16px rgba(34, 34, 34, 0.2);\n z-index: 100002;\n cursor: default;\n white-space: normal;\n text-align: left;\n\n .pp-dev-info__settings-title {\n font-size: 10px;\n font-weight: 600;\n color: var(--pp-dev-info-color-secondary);\n text-transform: uppercase;\n letter-spacing: 0.4px;\n }\n\n .pp-dev-info__settings-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n }\n\n .pp-dev-info__settings-toggle {\n appearance: auto;\n -webkit-appearance: checkbox;\n width: 14px;\n height: 14px;\n accent-color: var(--pp-dev-info-color-primary);\n cursor: pointer;\n flex-shrink: 0;\n }\n\n .pp-dev-info__corner-grid {\n display: grid;\n grid-template-columns: repeat(2, 30px);\n gap: 4px;\n }\n\n .pp-dev-info__corner-btn {\n width: 30px;\n height: 22px;\n border: 1px solid var(--pp-dev-info-color-line);\n border-radius: 3px;\n cursor: pointer;\n position: relative;\n background: transparent;\n transition: border-color 0.2s ease;\n\n &::after {\n content: '';\n position: absolute;\n width: 10px;\n height: 6px;\n border-radius: 1px;\n background-color: var(--pp-dev-info-color-secondary);\n transition: background-color 0.2s ease;\n }\n\n &--top-left::after {\n top: 2px;\n left: 2px;\n }\n\n &--top-right::after {\n top: 2px;\n right: 2px;\n }\n\n &--bottom-left::after {\n bottom: 2px;\n left: 2px;\n }\n\n &--bottom-right::after {\n bottom: 2px;\n right: 2px;\n }\n\n &:hover {\n border-color: var(--pp-dev-info-color-primary);\n }\n\n &.active {\n border-color: var(--pp-dev-info-color-primary);\n\n &::after {\n background-color: var(--pp-dev-info-color-primary);\n }\n }\n }\n\n .pp-dev-info__theme-grid {\n display: flex;\n gap: 4px;\n }\n\n .pp-dev-info__theme-btn {\n padding: 3px 8px;\n border: 1px solid var(--pp-dev-info-color-line);\n border-radius: 3px;\n cursor: pointer;\n background: transparent;\n font-size: 11px;\n line-height: 16px;\n color: var(--pp-dev-info-color-secondary);\n transition:\n border-color 0.2s ease,\n color 0.2s ease,\n background-color 0.2s ease;\n\n &:hover {\n border-color: var(--pp-dev-info-color-primary);\n color: var(--pp-dev-info-color-primary);\n }\n\n &.active {\n border-color: var(--pp-dev-info-color-primary);\n background-color: var(--pp-dev-info-color-primary);\n color: var(--pp-dev-info-color-white);\n }\n\n &:focus-visible {\n outline: 2px solid var(--pp-dev-info-color-primary);\n outline-offset: 1px;\n }\n }\n\n .pp-dev-info__settings-hide-btn {\n width: 100%;\n padding: 6px 10px;\n border: 1px solid rgba(34, 34, 34, 0.2);\n border-radius: 4px;\n cursor: pointer;\n font-weight: 600;\n color: var(--pp-dev-info-color-danger);\n transition: background-color 0.2s ease;\n\n &:hover {\n background-color: rgba(172, 43, 43, 0.06);\n }\n }\n\n .pp-dev-info__settings-group {\n display: flex;\n flex-direction: column;\n gap: 6px;\n padding-top: 10px;\n border-top: 1px solid rgba(34, 34, 34, 0.12);\n }\n\n .pp-dev-info__vars-buttons {\n display: flex;\n flex-direction: column;\n gap: 6px;\n }\n\n .pp-dev-info__vars-btn {\n width: 100%;\n padding: 6px 10px;\n border-radius: 4px;\n cursor: pointer;\n font-weight: 600;\n text-align: center;\n color: var(--pp-dev-info-color-primary);\n background-color: rgba(7, 91, 126, 0.1);\n border: 1px solid rgba(7, 91, 126, 0.4);\n transition:\n background-color 0.2s ease,\n border-color 0.2s ease;\n\n &:hover {\n background-color: rgba(7, 91, 126, 0.18);\n border-color: rgba(7, 91, 126, 0.6);\n }\n\n &:focus-visible {\n outline: 2px solid var(--pp-dev-info-color-primary);\n outline-offset: 1px;\n }\n }\n\n .pp-dev-info__settings-hint {\n font-size: 10px;\n line-height: 14px;\n color: var(--pp-dev-info-color-secondary);\n\n code {\n font-family: monospace;\n font-size: 10px;\n }\n }\n\n .pp-dev-info__settings-reset {\n align-self: flex-start;\n font-size: 11px;\n color: var(--pp-dev-info-color-primary);\n cursor: pointer;\n\n &:hover {\n text-decoration: underline;\n }\n }\n }\n\n // ── Users (PP-4597) ──────────────────────────────────────────────────────\n &__users-btn {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 28px;\n height: 28px;\n padding: 6px;\n border-radius: 3px;\n cursor: pointer;\n color: var(--pp-dev-info-color-secondary);\n transition: all 0.2s ease;\n\n svg {\n width: 16px;\n height: 16px;\n flex-shrink: 0;\n }\n\n &:hover {\n color: var(--pp-dev-info-color-primary);\n background-color: rgba(7, 91, 126, 0.06);\n }\n }\n\n // A saved user's tab: the button stands out, so the tab is never taken for your own.\n &.pp-dev-info--user-tab .pp-dev-info__users-btn {\n color: #ffffff;\n background-color: #7a3fb8;\n\n &:hover {\n background-color: #8d52cc;\n }\n }\n\n &__users.pp-dev-info__settings {\n width: 320px;\n max-width: calc(100vw - 16px);\n box-sizing: border-box;\n\n .pp-dev-info__users-tab {\n font-size: 12px;\n line-height: 16px;\n color: var(--pp-dev-info-color-dark);\n }\n\n // Over the radios: they pick who localhost works as, not this tab.\n .pp-dev-info__users-list-head {\n margin-bottom: -4px;\n padding-left: 4px;\n font-size: 9px;\n letter-spacing: 0.04em;\n text-transform: uppercase;\n color: var(--pp-dev-info-color-secondary);\n }\n\n .pp-dev-info__users-badge {\n margin-left: 6px;\n padding: 0 5px;\n border: 1px solid currentColor;\n border-radius: 3px;\n font-size: 9px;\n font-weight: 400;\n line-height: 13px;\n vertical-align: 1px;\n color: var(--pp-dev-info-color-tab);\n }\n\n .pp-dev-info__users-list {\n display: flex;\n flex-direction: column;\n gap: 2px;\n max-height: 240px;\n overflow-y: auto;\n }\n\n .pp-dev-info__users-row {\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 5px 6px;\n border-radius: 3px;\n border-left: 2px solid transparent;\n\n &.is-current {\n border-left-color: #7a3fb8;\n background-color: rgba(122, 63, 184, 0.08);\n }\n }\n\n .pp-dev-info__users-default {\n appearance: auto;\n -webkit-appearance: radio;\n width: 13px;\n height: 13px;\n margin: 0;\n flex-shrink: 0;\n accent-color: var(--pp-dev-info-color-primary);\n cursor: pointer;\n\n &:disabled {\n cursor: not-allowed;\n }\n }\n\n .pp-dev-info__users-who {\n display: flex;\n flex-direction: column;\n min-width: 0;\n flex: 1;\n }\n\n .pp-dev-info__users-name {\n font-size: 12px;\n font-weight: 600;\n color: var(--pp-dev-info-color-dark);\n overflow: hidden;\n text-overflow: ellipsis;\n }\n\n .pp-dev-info__users-meta {\n font-size: 10px;\n line-height: 13px;\n color: var(--pp-dev-info-color-secondary);\n overflow: hidden;\n text-overflow: ellipsis;\n }\n\n .needs-login .pp-dev-info__users-meta {\n color: var(--pp-dev-info-color-danger);\n }\n\n .pp-dev-info__users-action {\n flex-shrink: 0;\n padding: 2px 7px;\n border: 1px solid var(--pp-dev-info-color-line);\n border-radius: 3px;\n background: transparent;\n cursor: pointer;\n font-size: 11px;\n line-height: 16px;\n color: var(--pp-dev-info-color-primary);\n\n &:hover {\n border-color: var(--pp-dev-info-color-primary);\n }\n\n &--accent {\n border-color: var(--pp-dev-info-color-primary);\n background-color: var(--pp-dev-info-color-primary);\n color: var(--pp-dev-info-color-white);\n }\n }\n\n // A token user's row while it is being edited: name, new token, Save and Cancel.\n .pp-dev-info__users-edit {\n flex-direction: column;\n align-items: stretch;\n gap: 6px;\n margin: 0;\n }\n\n .pp-dev-info__users-edit-actions {\n display: flex;\n justify-content: flex-end;\n gap: 6px;\n }\n\n .pp-dev-info__users-remove {\n flex-shrink: 0;\n width: 20px;\n height: 20px;\n padding: 0;\n border: none;\n border-radius: 3px;\n background: transparent;\n cursor: pointer;\n font-size: 15px;\n line-height: 20px;\n color: var(--pp-dev-info-color-secondary);\n\n &:hover {\n color: var(--pp-dev-info-color-danger);\n background-color: rgba(172, 43, 43, 0.08);\n }\n }\n\n .pp-dev-info__users-input {\n width: 100%;\n box-sizing: border-box;\n padding: 5px 8px;\n border: 1px solid var(--pp-dev-info-color-line);\n border-radius: 3px;\n background: transparent;\n font-size: 12px;\n color: var(--pp-dev-info-color-dark);\n\n &:focus {\n outline: none;\n border-color: var(--pp-dev-info-color-primary);\n }\n }\n\n .pp-dev-info__users-error,\n .pp-dev-info__users-unavailable {\n font-size: 11px;\n line-height: 15px;\n color: var(--pp-dev-info-color-danger);\n }\n\n .pp-dev-info__users-unavailable {\n color: var(--pp-dev-info-color-secondary);\n }\n\n [hidden] {\n display: none !important;\n }\n }\n\n &__wrap-btn {\n box-sizing: border-box;\n display: flex;\n align-items: center;\n justify-content: center;\n width: 28px;\n height: 28px;\n padding: 6px;\n cursor: pointer;\n border-radius: 3px;\n transition: background-color 0.2s ease;\n\n &:hover {\n background-color: rgba(7, 91, 126, 0.06);\n animation: arrowHover 0.8s linear infinite;\n }\n\n &-arrow {\n width: 16px;\n height: 16px;\n color: var(--pp-dev-info-color-dark);\n fill: none;\n transition: transform 0.2s ease;\n }\n }\n\n &.closed {\n z-index: 100002;\n\n & .pp-dev-info__wrap-btn {\n &:hover {\n animation: arrowHover 1.2s linear infinite;\n }\n }\n }\n\n // Corner variants: placement, center-facing radius/shadow, direction-aware\n // minimize (.closed) and auto-hide slides. The corner class is always rendered\n // on the panel root (server default: bottom-right).\n @each $name, $c in $pp-dev-corners {\n &.pp-dev-info--#{$name} {\n #{map.get($c, v)}: 0;\n #{map.get($c, h)}: 0;\n border-radius: map.get($c, radius);\n box-shadow: map.get($c, shadow);\n #{map.get($c, edge)}: 1px solid rgba(34, 34, 34, 0.08);\n\n .pp-dev-info__settings {\n #{map.get($c, v)}: calc(100% + 8px);\n #{map.get($c, h)}: 0;\n }\n\n @if map.get($c, sx) == 1 {\n &.closed {\n transform: translateX(calc(100% - $pp-dev-height));\n\n .pp-dev-info__wrap-btn-arrow {\n transform: rotate(180deg);\n }\n }\n\n &.pp-dev-info--auto-hide:not(.is-peeking):not(.is-dragging) {\n transform: translateX(calc(100% - $pp-dev-peek));\n box-shadow:\n map.get($c, shadow),\n inset $pp-dev-peek 0 0 0 var(--pp-dev-info-color-primary);\n\n // Extended invisible hover zone on the inner side of the 4px strip\n &::after {\n content: '';\n position: absolute;\n top: -6px;\n bottom: -6px;\n left: -8px;\n width: 8px;\n }\n }\n } @else {\n .pp-dev-info__wrap-btn {\n order: 99;\n\n &-arrow {\n transform: rotate(180deg);\n }\n }\n\n &.closed {\n transform: translateX(calc(-100% + $pp-dev-height));\n\n .pp-dev-info__wrap-btn-arrow {\n transform: rotate(0deg);\n }\n }\n\n &.pp-dev-info--auto-hide:not(.is-peeking):not(.is-dragging) {\n transform: translateX(calc(-100% + $pp-dev-peek));\n box-shadow:\n map.get($c, shadow),\n inset (-$pp-dev-peek) 0 0 0 var(--pp-dev-info-color-primary);\n\n &::after {\n content: '';\n position: absolute;\n top: -6px;\n bottom: -6px;\n right: -8px;\n width: 8px;\n }\n }\n }\n }\n }\n\n &.pp-dev-info--auto-hide {\n z-index: 100002;\n }\n\n &.is-dragging {\n transition: none;\n user-select: none;\n cursor: grabbing;\n\n .pp-dev-info__drag-handle {\n cursor: grabbing;\n }\n }\n\n &.pp-dev-info--hidden {\n display: none;\n }\n\n .sync-button {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 28px;\n height: 28px;\n padding: 6px;\n background-color: transparent;\n border-radius: 3px;\n cursor: pointer;\n color: var(--pp-dev-info-color-primary);\n transition: all 0.2s ease;\n position: relative;\n overflow: hidden;\n\n &::before {\n content: '';\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n background-color: currentColor;\n opacity: 0;\n transition: opacity 0.2s ease;\n border-radius: 3px;\n }\n\n svg {\n width: 16px;\n height: 16px;\n position: relative;\n z-index: 1;\n transition: transform 0.3s ease;\n }\n\n &:hover:not(.disabled) {\n color: var(--pp-dev-info-color-primary);\n background-color: rgba(7, 91, 126, 0.06);\n\n svg {\n transform: scale(1.1) rotate(45deg);\n transition: transform 0.2s ease;\n }\n }\n\n &:active:not(.disabled) {\n svg {\n transform: scale(0.9) rotate(45deg);\n }\n }\n\n &.disabled {\n opacity: 0.5;\n cursor: not-allowed;\n pointer-events: none;\n color: var(--pp-dev-info-color-secondary);\n }\n\n &.syncing {\n svg {\n animation: rotate 1.5s linear infinite;\n }\n }\n }\n\n &__sep {\n display: inline-flex;\n flex-shrink: 0;\n width: 1px;\n height: 16px;\n background-color: rgba(34, 34, 34, 0.24);\n\n html[data-pp-dev-theme='dark'] & {\n background-color: rgba(255, 255, 255, 0.2);\n }\n\n @media screen and (prefers-color-scheme: dark) {\n background-color: rgba(255, 255, 255, 0.2);\n\n html[data-pp-dev-theme='light'] & {\n background-color: rgba(34, 34, 34, 0.24);\n }\n }\n }\n\n @keyframes rotate {\n from {\n transform: rotate(0deg);\n }\n to {\n transform: rotate(360deg);\n }\n }\n\n .margin-5 {\n margin-right: 5px;\n }\n\n .margin-10 {\n margin-right: 10px;\n }\n\n .custom-font {\n font-family: 'Inter', sans-serif;\n font-size: 13px;\n font-weight: 400;\n vertical-align: middle;\n line-height: 18px;\n color: var(--pp-dev-info-color-dark);\n\n &:not(.not-colored) a {\n color: var(--pp-dev-info-color-primary) !important;\n }\n }\n\n @keyframes rotation {\n 0% {\n transform: rotate(0deg);\n }\n 100% {\n transform: rotate(360deg);\n }\n }\n\n @keyframes arrowHover {\n 0% {\n transform: translateX(2px);\n }\n 50% {\n transform: translateX(-2px);\n }\n 100% {\n transform: translateX(2px);\n }\n }\n\n // Explicit Dark pick (html[data-pp-dev-theme='dark'], set via the settings popover's Theme\n // row — see src/client/theme.ts) — always wins over both the light default above and the\n // Auto media query below, on selector specificity alone.\n html[data-pp-dev-theme='dark'] & {\n @include pp-dev-theme-colors($pp-dev-dark-colors);\n @include pp-dev-dark-tints;\n }\n\n // Auto (OS/browser preference) — same values, only takes effect when no explicit\n // html[data-pp-dev-theme] override is set (those always outrank a plain :root/& selector).\n @media screen and (prefers-color-scheme: dark) {\n @include pp-dev-theme-colors($pp-dev-dark-colors);\n @include pp-dev-dark-tints;\n }\n }\n\n @media (prefers-reduced-motion: reduce) {\n &.pp-dev-info,\n .pp-dev-info,\n .pp-dev-info__popup,\n .pp-dev-info__settings {\n transition: none;\n }\n }\n}\n"]}
@@ -58,8 +58,11 @@ function createPPDevHotContext() {
58
58
  scheduleReconnect();
59
59
  return;
60
60
  }
61
+ let opened = false;
61
62
  socket.addEventListener('open', () => {
63
+ opened = true;
62
64
  reconnectDelay = 1_000;
65
+ dispatch('vite:ws:connect', {});
63
66
  flushOutbox();
64
67
  });
65
68
  socket.addEventListener('message', (ev) => {
@@ -76,6 +79,12 @@ function createPPDevHotContext() {
76
79
  });
77
80
  socket.addEventListener('close', () => {
78
81
  socket = null;
82
+ // Same events as Vite's own HMR client, so the panel can tell a lost connection apart
83
+ // (e.g. a sync whose response will never arrive). Only for a socket that was open: a
84
+ // failed reconnect attempt is not a new disconnect.
85
+ if (opened) {
86
+ dispatch('vite:ws:disconnect', {});
87
+ }
79
88
  scheduleReconnect();
80
89
  });
81
90
  socket.addEventListener('error', () => {
@@ -652,6 +661,8 @@ function initPanelSettings($panel, controller, hooks) {
652
661
  const USERS_API = '/@api/users';
653
662
  const REFRESH_INTERVAL = 3000;
654
663
  const USER_HOST_REGEXP = /^([a-z](?:[a-z0-9-]*[a-z0-9])?)\.localhost$/;
664
+ /** Marks the user this tab works as, apart from the radio, which picks localhost's user. */
665
+ const THIS_TAB_BADGE = '<span class="pp-dev-info__users-badge">this tab</span>';
655
666
  const STATE_LABELS = {
656
667
  pending: 'not logged in yet',
657
668
  active: 'logged in',
@@ -714,6 +725,8 @@ function initUsersPanel($panel, hooks) {
714
725
  let data = null;
715
726
  // What the list shows now, so a refresh with the same data leaves it (and the focus) alone.
716
727
  let renderedData = '';
728
+ // The token user being edited, whose row shows the edit form instead.
729
+ let editing = null;
717
730
  const onDocClick = (ev) => {
718
731
  const target = ev.target;
719
732
  if ($popover && !$popover.contains(target) && !$btn.contains(target)) {
@@ -729,6 +742,7 @@ function initUsersPanel($panel, hooks) {
729
742
  if (!$popover) {
730
743
  return;
731
744
  }
745
+ editing = null;
732
746
  window.clearInterval(refreshTimer);
733
747
  $popover.remove();
734
748
  $popover = null;
@@ -762,11 +776,25 @@ function initUsersPanel($panel, hooks) {
762
776
  return null;
763
777
  }
764
778
  };
779
+ /**
780
+ * Who this tab works as: on a user's host that user, on localhost the default user, and on an IP
781
+ * address or a custom domain you (`undefined`). Before the users are loaded, a user's host only.
782
+ */
783
+ const getCurrentUser = () => tabUser ?? (window.location.hostname === 'localhost' ? data?.defaultUser : null) ?? undefined;
765
784
  const renderList = () => {
766
785
  const $list = $popover?.querySelector('.pp-dev-info__users-list');
767
786
  if (!$popover || !$list || !data) {
768
787
  return;
769
788
  }
789
+ // The user being edited is gone (removed, logged out): nothing to edit any more.
790
+ if (editing && !data.users.some((user) => user.name === editing && user.kind === 'token')) {
791
+ editing = null;
792
+ }
793
+ else if (editing && $list.querySelector('.pp-dev-info__users-edit')?.dataset.user === editing) {
794
+ // A refresh must not wipe what is being typed into the edit form. (Edit on another user
795
+ // redraws it for that one.)
796
+ return;
797
+ }
770
798
  const snapshot = JSON.stringify(data);
771
799
  if (snapshot === renderedData) {
772
800
  return;
@@ -777,9 +805,7 @@ function initUsersPanel($panel, hooks) {
777
805
  ? document.activeElement.dataset.focusKey
778
806
  : undefined;
779
807
  const defaultUser = data.defaultUser;
780
- // The main host (localhost) works as the default user, a user's host as that user, and an IP
781
- // address or a custom domain as you.
782
- const currentUser = tabUser ?? (window.location.hostname === 'localhost' ? defaultUser : null) ?? undefined;
808
+ const currentUser = getCurrentUser();
783
809
  const $tab = $popover.querySelector('.pp-dev-info__users-tab strong');
784
810
  if ($tab) {
785
811
  $tab.textContent = currentUser ?? 'you';
@@ -789,7 +815,7 @@ function initUsersPanel($panel, hooks) {
789
815
  <input type="radio" name="pp-dev-default-user" class="pp-dev-info__users-default" value="" data-focus-key=":default"
790
816
  title="Make the main host (localhost) work as you" ${defaultUser ? '' : 'checked'} />
791
817
  <div class="pp-dev-info__users-who">
792
- <span class="pp-dev-info__users-name">You</span>
818
+ <span class="pp-dev-info__users-name">You${currentUser ? '' : THIS_TAB_BADGE}</span>
793
819
  <span class="pp-dev-info__users-meta">original user · the dev panel always works as you</span>
794
820
  </div>
795
821
  ${
@@ -804,6 +830,19 @@ function initUsersPanel($panel, hooks) {
804
830
  const needsLogin = user.kind === 'session' && user.state !== 'active';
805
831
  const needsAttention = needsLogin || user.state === 'expired';
806
832
  const name = escapeHtml(user.name);
833
+ if (user.kind === 'token' && user.name === editing) {
834
+ return `<form class="pp-dev-info__users-row pp-dev-info__users-edit" data-user="${name}">
835
+ <input class="pp-dev-info__users-input pp-dev-info__users-edit-name" value="${name}" maxlength="32"
836
+ autocomplete="off" spellcheck="false" aria-label="Name" data-focus-key="${name}:edit-name"
837
+ title="Lowercase latin letters, digits and -, starting with a letter. The user opens on <name>.localhost." />
838
+ <input class="pp-dev-info__users-input pp-dev-info__users-edit-token" type="password" autocomplete="off"
839
+ placeholder="New API token (empty keeps the current one)" aria-label="New API token" data-focus-key="${name}:edit-token" />
840
+ <div class="pp-dev-info__users-edit-actions">
841
+ <button type="submit" class="pp-dev-info__users-action pp-dev-info__users-action--accent" data-focus-key="${name}:save">Save</button>
842
+ <button type="button" class="pp-dev-info__users-action" data-cancel-edit data-focus-key="${name}:cancel">Cancel</button>
843
+ </div>
844
+ </form>`;
845
+ }
807
846
  let defaultTitle = `Make the main host (localhost) work as ${name}`;
808
847
  if (!canBeDefault) {
809
848
  defaultTitle =
@@ -814,12 +853,15 @@ function initUsersPanel($panel, hooks) {
814
853
  title="${defaultTitle}"
815
854
  ${user.name === defaultUser ? 'checked' : ''} ${canBeDefault ? '' : 'disabled'} />
816
855
  <div class="pp-dev-info__users-who">
817
- <span class="pp-dev-info__users-name">${name}</span>
856
+ <span class="pp-dev-info__users-name">${name}${user.name === currentUser ? THIS_TAB_BADGE : ''}</span>
818
857
  <span class="pp-dev-info__users-meta">${escapeHtml(describeUser(user))}</span>
819
858
  </div>
820
859
  ${needsLogin && user.loginUrl
821
860
  ? `<button type="button" class="pp-dev-info__users-action pp-dev-info__users-action--accent" data-open="${escapeHtml(user.loginUrl)}" data-raw="true" data-focus-key="${name}:login">Log in</button>`
822
861
  : ''}
862
+ ${user.kind === 'token'
863
+ ? `<button type="button" class="pp-dev-info__users-action" data-edit="${name}" data-focus-key="${name}:edit" title="Rename ${name} or replace its API token">Edit</button>`
864
+ : ''}
823
865
  <button type="button" class="pp-dev-info__users-action" data-open="${escapeHtml(user.origin)}" data-focus-key="${name}:open" title="Open this page as ${name}">Open</button>
824
866
  <button type="button" class="pp-dev-info__users-remove" data-remove="${name}" data-focus-key="${name}:remove" title="Remove ${name}" aria-label="Remove ${name}">×</button>
825
867
  </div>`;
@@ -838,6 +880,46 @@ function initUsersPanel($panel, hooks) {
838
880
  window.open($open.dataset.raw ? target : samePageOn(target), '_blank');
839
881
  });
840
882
  });
883
+ const startEditing = (name) => {
884
+ editing = name;
885
+ renderedData = '';
886
+ renderList();
887
+ $list.querySelector('.pp-dev-info__users-edit-name')?.focus();
888
+ };
889
+ $list.querySelectorAll('[data-edit]').forEach(($edit) => {
890
+ $edit.addEventListener('click', (ev) => {
891
+ ev.preventDefault();
892
+ startEditing($edit.dataset.edit);
893
+ });
894
+ });
895
+ $list.querySelector('[data-cancel-edit]')?.addEventListener('click', (ev) => {
896
+ ev.preventDefault();
897
+ setError('');
898
+ startEditing(null);
899
+ });
900
+ $list.querySelector('.pp-dev-info__users-edit')?.addEventListener('submit', async (ev) => {
901
+ ev.preventDefault();
902
+ const name = editing;
903
+ const $form = ev.currentTarget;
904
+ const newName = $form.querySelector('.pp-dev-info__users-edit-name').value.trim();
905
+ const token = $form.querySelector('.pp-dev-info__users-edit-token').value.trim();
906
+ const response = await request('PATCH', `${USERS_API}/${encodeURIComponent(name)}`, {
907
+ name: newName,
908
+ ...(token ? { token } : {}),
909
+ });
910
+ if (!response) {
911
+ return;
912
+ }
913
+ const { user } = (await response.json());
914
+ // This tab's own user got a new host: go on there (the old one would only send it on).
915
+ if (tabUser === name && user.name !== name) {
916
+ window.location.href = samePageOn(user.origin);
917
+ return;
918
+ }
919
+ editing = null;
920
+ renderedData = '';
921
+ await refresh();
922
+ });
841
923
  $list.querySelectorAll('[data-remove]').forEach(($remove) => {
842
924
  $remove.addEventListener('click', async (ev) => {
843
925
  ev.preventDefault();
@@ -946,10 +1028,13 @@ function initUsersPanel($panel, hooks) {
946
1028
  $popover.innerHTML = `
947
1029
  <div class="pp-dev-info__settings-title">Users</div>
948
1030
  <div class="pp-dev-info__users-tab">
949
- This tab works as <strong>${escapeHtml(tabUser ?? 'you')}</strong>.
950
- <span>localhost works as the checked user; each user's own host as that user.</span>
1031
+ This tab works as <strong>${escapeHtml(getCurrentUser() ?? 'you')}</strong>.
1032
+ </div>
1033
+ <div class="pp-dev-info__users-list-head" aria-hidden="true">localhost</div>
1034
+ <div class="pp-dev-info__users-list" role="radiogroup" aria-label="Who localhost works as">
1035
+ <span class="pp-dev-info__users-meta">Loading users…</span>
951
1036
  </div>
952
- <div class="pp-dev-info__users-list" role="radiogroup" aria-label="Default user"></div>
1037
+ <div class="pp-dev-info__settings-hint">The radio picks who localhost works as; each user's own host always works as that user.</div>
953
1038
  <div class="pp-dev-info__users-unavailable" hidden></div>
954
1039
  <form class="pp-dev-info__settings-group pp-dev-info__users-add" hidden>
955
1040
  <span class="pp-dev-info__settings-label">Add user</span>
@@ -983,10 +1068,15 @@ function initUsersPanel($panel, hooks) {
983
1068
  document.addEventListener('click', onDocClick, true);
984
1069
  document.addEventListener('keydown', onKeyDown, true);
985
1070
  hooks?.onOpenChange?.(true);
1071
+ // Shown right away from what was loaded with the panel, then brought up to date.
1072
+ renderList();
1073
+ renderAvailability();
986
1074
  refresh();
987
1075
  // Picks up logins and expired sessions while the popover is open.
988
1076
  refreshTimer = window.setInterval(refresh, REFRESH_INTERVAL);
989
1077
  };
1078
+ // Loaded with the panel, so the popover opens with its list.
1079
+ refresh();
990
1080
  $btn.addEventListener('click', (ev) => {
991
1081
  ev.preventDefault();
992
1082
  if ($popover) {
@@ -1059,6 +1149,115 @@ function createPopupElement(opts) {
1059
1149
  return $popup;
1060
1150
  }
1061
1151
 
1152
+ /**
1153
+ * `template:sync:response` code for a sync requested while another one runs. Kept in sync with
1154
+ * `SYNC_IN_PROGRESS_CODE` in `src/lib/client.service.ts` (server side, not importable here).
1155
+ */
1156
+ const SYNC_IN_PROGRESS_CODE = 'SYNC_IN_PROGRESS';
1157
+ const TITLE_AVAILABLE = 'Sync template';
1158
+ const TITLE_UNAVAILABLE = 'Sync is unavailable on this instance';
1159
+ /**
1160
+ * Wires the dev panel's "Sync template" button to the dev server.
1161
+ *
1162
+ * The button is disabled while a sync runs, so a double click can't start a second build, and is
1163
+ * enabled again by whatever ends the sync: its response, or a lost connection. It stays disabled
1164
+ * only when the server says sync is unavailable on this instance (`config.canSync === false`);
1165
+ * a failed sync (MI unreachable, a build error) can simply be retried.
1166
+ */
1167
+ function initSyncButton(hot, $button, deps) {
1168
+ const reload = deps.reload ?? (() => window.location.reload());
1169
+ let available = !$button.disabled && !$button.classList.contains('disabled');
1170
+ let syncing = false;
1171
+ const applyState = () => {
1172
+ $button.disabled = syncing || !available;
1173
+ $button.classList.toggle('syncing', syncing);
1174
+ $button.classList.toggle('disabled', !available);
1175
+ $button.title = available ? TITLE_AVAILABLE : TITLE_UNAVAILABLE;
1176
+ if (syncing) {
1177
+ $button.setAttribute('aria-busy', 'true');
1178
+ }
1179
+ else {
1180
+ $button.removeAttribute('aria-busy');
1181
+ }
1182
+ };
1183
+ const endSync = () => {
1184
+ syncing = false;
1185
+ applyState();
1186
+ };
1187
+ hot.on('client:config:update', (data) => {
1188
+ if (typeof data?.config?.canSync !== 'boolean') {
1189
+ return;
1190
+ }
1191
+ // The server sends `canSync: true` at the start of every sync: it must not enable the
1192
+ // button while that sync runs, which `applyState` takes care of.
1193
+ available = data.config.canSync;
1194
+ applyState();
1195
+ });
1196
+ hot.on('template:sync:action-required', async (payload) => {
1197
+ // The spinner keeps running while a confirmation modal is shown: the sync is still in
1198
+ // progress and only ends on `template:sync:response`.
1199
+ const approved = await deps.confirmModal({
1200
+ title: payload.title,
1201
+ content: payload.content,
1202
+ confirmText: payload.confirmText,
1203
+ cancelText: payload.cancelText,
1204
+ });
1205
+ hot.send('template:sync:action-response', { requestId: payload.requestId, approved });
1206
+ });
1207
+ hot.on('template:sync:response', (payload) => {
1208
+ deps.closeAllConfirmModals();
1209
+ if ('error' in payload && payload.config?.canSync === false) {
1210
+ available = false;
1211
+ }
1212
+ endSync();
1213
+ if ('cancelled' in payload && payload.cancelled) {
1214
+ deps.infoPopup({ title: 'Sync cancelled', content: payload.message, type: 'warning' });
1215
+ return;
1216
+ }
1217
+ if ('error' in payload && typeof payload.error !== 'undefined') {
1218
+ if (payload.code === SYNC_IN_PROGRESS_CODE) {
1219
+ deps.infoPopup({ title: 'Sync already running', content: payload.error, type: 'warning' });
1220
+ return;
1221
+ }
1222
+ deps.infoPopup({ title: 'Sync error', content: payload.error, type: 'danger' });
1223
+ if (payload.refresh) {
1224
+ setTimeout(reload);
1225
+ }
1226
+ return;
1227
+ }
1228
+ if ('syncedAt' in payload && typeof payload.syncedAt !== 'undefined') {
1229
+ deps.infoPopup({
1230
+ title: 'Sync success',
1231
+ content: `Synced at ${new Date(payload.syncedAt).toLocaleString()}.\nBackup filename: ${payload.backupFilename}`,
1232
+ type: 'success',
1233
+ });
1234
+ }
1235
+ });
1236
+ // A response sent to a socket that has closed never arrives. Without this, the button would
1237
+ // spin, disabled, until the page is reloaded.
1238
+ hot.on('vite:ws:disconnect', () => {
1239
+ if (!syncing) {
1240
+ return;
1241
+ }
1242
+ deps.closeAllConfirmModals();
1243
+ endSync();
1244
+ deps.infoPopup({
1245
+ title: 'Sync result unknown',
1246
+ content: 'The connection to the dev server was lost during the sync. Check its terminal for the result before syncing again.',
1247
+ type: 'warning',
1248
+ });
1249
+ });
1250
+ $button.addEventListener('click', (ev) => {
1251
+ ev.preventDefault();
1252
+ if (syncing || !available) {
1253
+ return;
1254
+ }
1255
+ syncing = true;
1256
+ applyState();
1257
+ hot.send('template:sync', {});
1258
+ });
1259
+ }
1260
+
1062
1261
  /// <reference types="vite/client" />
1063
1262
  // Applied unconditionally (even if the panel itself is hidden) — this is what makes the
1064
1263
  // override affect the whole injected panel, not just its own markup.
@@ -1291,82 +1490,9 @@ if (hot) {
1291
1490
  hot.on('redirect', (data) => {
1292
1491
  window.location.href = data.url;
1293
1492
  });
1294
- hot.on('client:config:update', (data) => {
1295
- if (typeof data.config?.canSync === 'boolean') {
1296
- if (data.config.canSync) {
1297
- const $syncButton = document.getElementById('sync-template');
1298
- if ($syncButton) {
1299
- $syncButton.disabled = false;
1300
- $syncButton.classList.remove('disabled');
1301
- $syncButton.title = 'Sync template';
1302
- }
1303
- }
1304
- else {
1305
- const $syncButton = document.getElementById('sync-template');
1306
- if ($syncButton) {
1307
- $syncButton.disabled = true;
1308
- $syncButton.classList.add('disabled');
1309
- $syncButton.title = 'Sync is unavailable on this instance';
1310
- }
1311
- }
1312
- }
1313
- });
1314
1493
  const $syncButton = document.getElementById('sync-template');
1315
1494
  if ($syncButton) {
1316
- hot.on('template:sync:action-required', async (payload) => {
1317
- // Keep the sync spinner running while a confirmation modal is shown — the sync
1318
- // process is still in progress and only ends on `template:sync:response`.
1319
- const approved = await confirmModal({
1320
- title: payload.title,
1321
- content: payload.content,
1322
- confirmText: payload.confirmText,
1323
- cancelText: payload.cancelText,
1324
- });
1325
- hot.send('template:sync:action-response', {
1326
- requestId: payload.requestId,
1327
- approved,
1328
- });
1329
- });
1330
- hot.on('template:sync:response', (payload) => {
1331
- closeAllConfirmModals();
1332
- $syncButton.classList.remove('syncing');
1333
- if ('cancelled' in payload && payload.cancelled) {
1334
- infoPopup({
1335
- title: 'Sync cancelled',
1336
- content: payload.message,
1337
- type: 'warning',
1338
- });
1339
- }
1340
- else if ('error' in payload && typeof payload.error !== 'undefined') {
1341
- infoPopup({
1342
- title: 'Sync error',
1343
- content: payload.error,
1344
- type: 'danger',
1345
- });
1346
- if (payload.refresh) {
1347
- setTimeout(() => {
1348
- window.location.reload();
1349
- });
1350
- }
1351
- else {
1352
- $syncButton.disabled = true;
1353
- $syncButton.classList.add('disabled');
1354
- $syncButton.title = 'Sync is unavailable on this instance';
1355
- }
1356
- }
1357
- else if ('syncedAt' in payload && typeof payload.syncedAt !== 'undefined') {
1358
- infoPopup({
1359
- title: 'Sync success',
1360
- content: `Synced at ${new Date(payload.syncedAt).toLocaleString()}.\nBackup filename: ${payload.backupFilename}`,
1361
- type: 'success',
1362
- });
1363
- }
1364
- });
1365
- $syncButton.addEventListener('click', (ev) => {
1366
- ev.preventDefault();
1367
- $syncButton.classList.add('syncing');
1368
- hot.send('template:sync', {});
1369
- });
1495
+ initSyncButton(hot, $syncButton, { infoPopup, confirmModal, closeAllConfirmModals });
1370
1496
  }
1371
1497
  hot.on('page-variables:reload:response', (payload) => {
1372
1498
  pendingReloadVariablesPopup?.close();