authorgravatar for maria@kuuro.netmaria <maria@kuuro.net> 2026-10-04 08:57:42-03:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-10-04 08:57:42-03:00
logc346fb679bac106c4c162ab145c27e3c8b9640bd
treed8d657c209c938d85ed6ff7e6c303a54dc0d56e2
parent1c6269326a502874677192a844e449577df6279c
signature Signed by PGP key B5690EEEBB952194

fix(web): open provider update details from both icons (#15501)


2 files changed, 105 insertions(+), 141 deletions(-)

apps/web/src/components/settings/ProviderInstanceCard.tsx+102-138
...@@ -593,7 +593,11 @@ export function ProviderInstanceCard({...@@ -593,7 +593,11 @@ export function ProviderInstanceCard({
593 compatibility.status !== "supported" &&593 compatibility.status !== "supported" &&
594 compatibility.status !== "unknown";594 compatibility.status !== "unknown";
595 const VersionAdvisoryIcon = hasCompatibilityWarning ? AlertTriangleIcon : ArrowUpCircleIcon;595 const VersionAdvisoryIcon = hasCompatibilityWarning ? AlertTriangleIcon : ArrowUpCircleIcon;
596 const onRunVersionAction = versionAdvisory?.targetVersion ? onInstallRecommended : onRunUpdate;596 const onRunVersionAction = readOnly
597 ? undefined
598 : versionAdvisory?.targetVersion
599 ? onInstallRecommended
600 : onRunUpdate;
597 const urlAuthAction = liveProvider?.auth.action;601 const urlAuthAction = liveProvider?.auth.action;
598 const displayName =602 const displayName =
599 instance.displayName?.trim() || driverOption?.label || String(instance.driver);603 instance.displayName?.trim() || driverOption?.label || String(instance.driver);
...@@ -765,6 +769,101 @@ export function ProviderInstanceCard({...@@ -765,6 +769,101 @@ export function ProviderInstanceCard({
765 ) : null}769 ) : null}
766 </>770 </>
767 );771 );
772 const versionAdvisoryNode = versionAdvisory ? (
773 <Popover>
774 <Tooltip>
775 <TooltipTrigger
776 render={
777 <PopoverTrigger
778 render={
779 <Button
780 type="button"
781 size={mode === "list" ? "icon-micro" : "icon-xs"}
782 variant="ghost-muted"
783 className={mode === "list" ? "pointer-events-auto relative shrink-0" : undefined}
784 aria-label={`${versionAdvisory.title} — view details`}
785 >
786 <VersionAdvisoryIcon
787 className={cn(
788 mode === "list" && "size-3.5",
789 hasCompatibilityWarning && "text-warning",
790 )}
791 />
792 </Button>
793 }
794 />
795 }
796 />
797 <TooltipPopup side="top">{versionAdvisory.title}</TooltipPopup>
798 </Tooltip>
799 <PopoverPopup side="bottom" align="end" width="md" aria-label={versionAdvisory.title}>
800 <div className="grid min-w-0 gap-3">
801 <div className="grid gap-0.5">
802 <p className="text-sm font-semibold leading-tight text-foreground">
803 {versionAdvisory.title}
804 </p>
805 <p
806 className={cn(
807 "text-xs leading-snug",
808 versionAdvisory.emphasis === "strong" ? "text-warning" : "text-muted-foreground",
809 )}
810 >
811 {versionAdvisory.detail}
812 </p>
813 </div>
814 {onRunVersionAction ? (
815 <Button
816 type="button"
817 size="xs"
818 variant="outline"
819 className="w-full"
820 disabled={isUpdating}
821 onClick={onRunVersionAction}
822 >
823 {isUpdating ? <Spinner /> : <DownloadIcon />}
824 {isUpdating
825 ? "Updating"
826 : versionAdvisory.targetVersion
827 ? `Install ${getProviderVersionLabel(versionAdvisory.targetVersion)}`
828 : "Update now"}
829 </Button>
830 ) : null}
831 {onRunVersionAction && updateCommand ? (
832 <div className="flex items-center gap-2 text-3xs font-medium uppercase tracking-wider text-muted-foreground">
833 <span aria-hidden className="h-px flex-1 bg-border" />
834 or, update manually using
835 <span aria-hidden className="h-px flex-1 bg-border" />
836 </div>
837 ) : null}
838 {updateCommand ? (
839 <div className="flex min-w-0 items-center gap-1 rounded-md border border-border/70 bg-muted/40 py-0.5 pr-0.5 pl-2">
840 <code className="min-w-0 flex-1 truncate font-mono text-2xs text-foreground">
841 {updateCommand}
842 </code>
843 <Tooltip>
844 <TooltipTrigger
845 render={
846 <Button
847 type="button"
848 size="icon-xs"
849 variant="ghost-muted"
850 className="shrink-0"
851 onClick={() => copyToClipboard(updateCommand, { providerName: displayName })}
852 aria-label="Copy update command"
853 >
854 <CopyIcon className="size-3" />
855 </Button>
856 }
857 />
858 <TooltipPopup side="top">Copy command</TooltipPopup>
859 </Tooltip>
860 </div>
861 ) : null}
862 </div>
863 </PopoverPopup>
864 </Popover>
865 ) : null;
866
768 if (mode === "list") {867 if (mode === "list") {
769 return (868 return (
770 <div869 <div
...@@ -796,49 +895,7 @@ export function ProviderInstanceCard({...@@ -796,49 +895,7 @@ export function ProviderInstanceCard({
796 {versionLabel}895 {versionLabel}
797 </code>896 </code>
798 ) : null}897 ) : null}
799 {versionAdvisory ? (898 {versionAdvisoryNode}
800 hasCompatibilityWarning ? (
801 <Tooltip>
802 <TooltipTrigger
803 render={
804 <span
805 tabIndex={0}
806 role="img"
807 aria-label={versionAdvisory.title}
808 className="pointer-events-auto relative inline-flex shrink-0 text-warning"
809 >
810 <VersionAdvisoryIcon className="size-3.5" />
811 </span>
812 }
813 />
814 <TooltipPopup side="top">{versionAdvisory.detail}</TooltipPopup>
815 </Tooltip>
816 ) : updateCommand ? (
817 <Tooltip>
818 <TooltipTrigger
819 render={
820 <Button
821 type="button"
822 size="icon-micro"
823 variant="ghost-muted"
824 className="pointer-events-auto relative shrink-0"
825 aria-label={`Copy ${displayName} update command`}
826 onClick={() =>
827 copyToClipboard(updateCommand, { providerName: displayName })
828 }
829 >
830 <ArrowUpCircleIcon className="size-3.5" />
831 </Button>
832 }
833 />
834 <TooltipPopup side="top">Copy update command</TooltipPopup>
835 </Tooltip>
836 ) : (
837 <span role="img" aria-label="Update available" className="inline-flex shrink-0">
838 <ArrowUpCircleIcon className="size-3.5 text-muted-foreground" />
839 </span>
840 )
841 ) : null}
842 </span>899 </span>
843 <span className="mt-0.5 flex items-start gap-1.5 text-xs leading-normal text-muted-foreground/80">900 <span className="mt-0.5 flex items-start gap-1.5 text-xs leading-normal text-muted-foreground/80">
844 {statusDotNode ? (901 {statusDotNode ? (
...@@ -876,105 +933,12 @@ export function ProviderInstanceCard({...@@ -876,105 +933,12 @@ export function ProviderInstanceCard({
876 </Badge>933 </Badge>
877 ) : null}934 ) : null}
878 {versionCodeNode}935 {versionCodeNode}
936 {versionAdvisoryNode}
879 <span937 <span
880 inert={readOnly}938 inert={readOnly}
881 aria-disabled={readOnly || undefined}939 aria-disabled={readOnly || undefined}
882 className={cn("inline-flex items-center gap-1", readOnly && "opacity-50")}940 className={cn("inline-flex items-center gap-1", readOnly && "opacity-50")}
883 >941 >
884 {versionAdvisory ? (
885 <Popover>
886 <Tooltip>
887 <TooltipTrigger
888 render={
889 <PopoverTrigger
890 render={
891 <Button
892 type="button"
893 size="icon-xs"
894 variant="ghost-muted"
895 aria-label={`${versionAdvisory.title} — view details`}
896 >
897 <VersionAdvisoryIcon
898 className={cn(hasCompatibilityWarning && "text-warning")}
899 />
900 </Button>
901 }
902 />
903 }
904 />
905 <TooltipPopup side="top">{versionAdvisory.title}</TooltipPopup>
906 </Tooltip>
907 <PopoverPopup side="bottom" align="end" width="md">
908 <div className="grid min-w-0 gap-3">
909 <div className="grid gap-0.5">
910 <p className="text-sm font-semibold leading-tight text-foreground">
911 {versionAdvisory.title}
912 </p>
913 <p
914 className={cn(
915 "text-xs leading-snug",
916 versionAdvisory.emphasis === "strong"
917 ? "text-warning"
918 : "text-muted-foreground",
919 )}
920 >
921 {versionAdvisory.detail}
922 </p>
923 </div>
924 {onRunVersionAction ? (
925 <Button
926 type="button"
927 size="xs"
928 variant="outline"
929 className="w-full"
930 disabled={isUpdating}
931 onClick={onRunVersionAction}
932 >
933 {isUpdating ? <Spinner /> : <DownloadIcon />}
934 {isUpdating
935 ? "Updating"
936 : versionAdvisory.targetVersion
937 ? `Install ${getProviderVersionLabel(versionAdvisory.targetVersion)}`
938 : "Update now"}
939 </Button>
940 ) : null}
941 {onRunVersionAction && updateCommand ? (
942 <div className="flex items-center gap-2 text-3xs font-medium uppercase tracking-wider text-muted-foreground">
943 <span aria-hidden className="h-px flex-1 bg-border" />
944 or, update manually using
945 <span aria-hidden className="h-px flex-1 bg-border" />
946 </div>
947 ) : null}
948 {updateCommand ? (
949 <div className="flex min-w-0 items-center gap-1 rounded-md border border-border/70 bg-muted/40 py-0.5 pr-0.5 pl-2">
950 <code className="min-w-0 flex-1 truncate font-mono text-2xs text-foreground">
951 {updateCommand}
952 </code>
953 <Tooltip>
954 <TooltipTrigger
955 render={
956 <Button
957 type="button"
958 size="icon-xs"
959 variant="ghost-muted"
960 className="shrink-0"
961 onClick={() =>
962 copyToClipboard(updateCommand, { providerName: displayName })
963 }
964 aria-label="Copy update command"
965 >
966 <CopyIcon className="size-3" />
967 </Button>
968 }
969 />
970 <TooltipPopup side="top">Copy command</TooltipPopup>
971 </Tooltip>
972 </div>
973 ) : null}
974 </div>
975 </PopoverPopup>
976 </Popover>
977 ) : null}
978 {titleTailNode}942 {titleTailNode}
979 {onDelete ? (943 {onDelete ? (
980 <Button944 <Button
apps/web/src/components/settings/ProviderSettingsPanel.tsx+3-3
...@@ -1164,7 +1164,7 @@ export function EnvironmentProviderSettings({...@@ -1164,7 +1164,7 @@ export function EnvironmentProviderSettings({
1164 })1164 })
1165 }1165 }
1166 onInstallRecommended={1166 onInstallRecommended={
1167 mode === "editor" &&1167 !readOnly &&
1168 liveProvider?.compatibilityAdvisory?.message &&1168 liveProvider?.compatibilityAdvisory?.message &&
1169 liveProvider.compatibilityAdvisory.recommendedVersion &&1169 liveProvider.compatibilityAdvisory.recommendedVersion &&
1170 liveProvider.versionAdvisory?.canInstallVersion1170 liveProvider.versionAdvisory?.canInstallVersion
...@@ -1177,13 +1177,13 @@ export function EnvironmentProviderSettings({...@@ -1177,13 +1177,13 @@ export function EnvironmentProviderSettings({
1177 : undefined1177 : undefined
1178 }1178 }
1179 onRunUpdate={1179 onRunUpdate={
1180 mode === "editor" && showInlineUpdateButton && updateCandidate1180 !readOnly && showInlineUpdateButton && updateCandidate
1181 ? () => {1181 ? () => {
1182 if (canRunInlineUpdate) void runProviderUpdate(updateCandidate);1182 if (canRunInlineUpdate) void runProviderUpdate(updateCandidate);
1183 }1183 }
1184 : undefined1184 : undefined
1185 }1185 }
1186 isUpdating={mode === "editor" ? isInstanceUpdateRunning : undefined}1186 isUpdating={isInstanceUpdateRunning}
1187 />1187 />
1188 );1188 );
1189 };1189 };