@skyhook-io/k8s-ui 1.17.0 → 1.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (250) hide show
  1. package/README.md +41 -0
  2. package/package.json +6 -6
  3. package/src/components/charts/AreaChart.interaction.test.tsx +118 -0
  4. package/src/components/charts/AreaChart.test.tsx +25 -0
  5. package/src/components/charts/AreaChart.tsx +141 -12
  6. package/src/components/charts/index.ts +1 -0
  7. package/src/components/checks/ChecksView.tsx +18 -1
  8. package/src/components/cnpg/CNPGBackupSummary.tsx +57 -9
  9. package/src/components/cnpg/CNPGClusterHASection.test.tsx +90 -0
  10. package/src/components/cnpg/CNPGClusterHASection.tsx +331 -0
  11. package/src/components/cnpg/CNPGClusterSummary.test.tsx +275 -0
  12. package/src/components/cnpg/CNPGClusterSummary.tsx +310 -96
  13. package/src/components/cnpg/CNPGConnectSection.test.tsx +64 -0
  14. package/src/components/cnpg/CNPGConnectSection.tsx +178 -0
  15. package/src/components/cnpg/CNPGDeclarativeSummary.tsx +157 -26
  16. package/src/components/cnpg/CNPGImageCatalogSummary.tsx +7 -4
  17. package/src/components/cnpg/CNPGLogicalPath.test.tsx +22 -0
  18. package/src/components/cnpg/CNPGLogicalPath.tsx +150 -0
  19. package/src/components/cnpg/CNPGObjectStoreSummary.tsx +9 -6
  20. package/src/components/cnpg/CNPGObjectSummary.test.tsx +193 -2
  21. package/src/components/cnpg/CNPGPoolerScheduling.test.tsx +36 -0
  22. package/src/components/cnpg/CNPGPoolerSummary.tsx +241 -14
  23. package/src/components/cnpg/CNPGSharedSummary.tsx +15 -8
  24. package/src/components/cnpg/CNPGWALArchivingFact.test.tsx +39 -0
  25. package/src/components/cnpg/backupRuns.test.ts +17 -0
  26. package/src/components/cnpg/backupRuns.ts +19 -0
  27. package/src/components/cnpg/connect.test.ts +124 -0
  28. package/src/components/cnpg/connect.ts +155 -0
  29. package/src/components/cnpg/databaseRole.test.ts +38 -0
  30. package/src/components/cnpg/databaseRole.ts +80 -0
  31. package/src/components/cnpg/ha.test.ts +446 -0
  32. package/src/components/cnpg/ha.ts +621 -0
  33. package/src/components/cnpg/index.ts +18 -0
  34. package/src/components/cnpg/logicalReplication.test.ts +121 -0
  35. package/src/components/cnpg/logicalReplication.ts +252 -0
  36. package/src/components/cnpg/pooler.test.ts +84 -0
  37. package/src/components/cnpg/pooler.ts +225 -0
  38. package/src/components/cnpg/primitives.tsx +21 -117
  39. package/src/components/cnpg/problems.ts +12 -0
  40. package/src/components/cnpg/relations.test.ts +63 -13
  41. package/src/components/cnpg/relations.ts +61 -52
  42. package/src/components/cnpg/schedule.ts +35 -0
  43. package/src/components/cnpg/workspace-disk.test.ts +97 -0
  44. package/src/components/cnpg/workspace-fleet-metrics.test.ts +250 -0
  45. package/src/components/cnpg/workspace-problems.test.ts +175 -0
  46. package/src/components/cnpg/workspace.test.ts +380 -9
  47. package/src/components/cnpg/workspace.ts +1137 -136
  48. package/src/components/dock/DockContext.test.tsx +32 -0
  49. package/src/components/dock/DockContext.tsx +18 -4
  50. package/src/components/dock/LocalTerminalTab.test.tsx +40 -0
  51. package/src/components/dock/LocalTerminalTab.tsx +4 -3
  52. package/src/components/dock/TerminalTab.tsx +20 -8
  53. package/src/components/facts/certainty.tsx +36 -0
  54. package/src/components/facts/facts.test.tsx +24 -0
  55. package/src/components/facts/facts.tsx +62 -0
  56. package/src/components/facts/index.ts +6 -0
  57. package/src/components/facts/managed-by.test.tsx +17 -0
  58. package/src/components/facts/managed-by.tsx +36 -0
  59. package/src/components/facts/public-exports.test.tsx +35 -0
  60. package/src/components/gitops/GitOpsGraphFilterRail.tsx +7 -3
  61. package/src/components/gitops/GitOpsHealthSourceNotice.test.tsx +38 -0
  62. package/src/components/gitops/GitOpsHealthSourceNotice.tsx +13 -3
  63. package/src/components/gitops/insights/ArgoResourceDiff.test.tsx +142 -0
  64. package/src/components/gitops/insights/ArgoResourceDiff.tsx +22 -5
  65. package/src/components/gitops/insights/DriftPanel.tsx +62 -0
  66. package/src/components/gitops/insights/GitOpsInsightViews.tsx +40 -81
  67. package/src/components/gitops/insights/insights-helpers.test.ts +18 -0
  68. package/src/components/gitops/insights/insights-helpers.ts +7 -0
  69. package/src/components/gitops/tree/GitOpsTreeGraph.test.ts +91 -2
  70. package/src/components/gitops/tree/GitOpsTreeGraph.tsx +76 -76
  71. package/src/components/gitops/tree/layout.test.ts +111 -0
  72. package/src/components/gitops/tree/layout.ts +128 -0
  73. package/src/components/gitops/tree/tree-helpers.test.ts +23 -0
  74. package/src/components/gitops/tree/tree-helpers.ts +12 -2
  75. package/src/components/issues/ConditionalCleanup.test.tsx +82 -0
  76. package/src/components/issues/IssuesView.tsx +44 -6
  77. package/src/components/issues/ResourceIssuesSection.test.tsx +28 -0
  78. package/src/components/issues/ResourceIssuesSection.tsx +8 -2
  79. package/src/components/issues/diagnostic.ts +2 -0
  80. package/src/components/issues/index.ts +3 -1
  81. package/src/components/issues/issue-timing.ts +12 -1
  82. package/src/components/issues/issues.test.ts +75 -1
  83. package/src/components/issues/severity.ts +30 -2
  84. package/src/components/issues/types.ts +55 -4
  85. package/src/components/logs/LogCore.theme.test.tsx +206 -3
  86. package/src/components/logs/LogCore.tsx +180 -53
  87. package/src/components/logs/LogToolbarSelects.tsx +3 -1
  88. package/src/components/logs/LogsViewer.test.tsx +369 -0
  89. package/src/components/logs/LogsViewer.tsx +91 -13
  90. package/src/components/logs/StructuredLogLine.test.tsx +76 -0
  91. package/src/components/logs/StructuredLogLine.tsx +23 -31
  92. package/src/components/logs/WorkloadLogsViewer.test.tsx +65 -0
  93. package/src/components/logs/WorkloadLogsViewer.tsx +46 -11
  94. package/src/components/logs/useLogBuffer.reading.test.tsx +149 -0
  95. package/src/components/logs/useLogBuffer.test.ts +8 -0
  96. package/src/components/logs/useLogBuffer.ts +48 -18
  97. package/src/components/logs/useLogSearch.ts +3 -1
  98. package/src/components/logs/useLogStream.ts +17 -1
  99. package/src/components/problems/index.ts +1 -0
  100. package/src/components/problems/problems.test.tsx +63 -0
  101. package/src/components/problems/problems.tsx +165 -0
  102. package/src/components/resources/ActiveFilterBar.test.tsx +78 -0
  103. package/src/components/resources/ActiveFilterBar.tsx +86 -0
  104. package/src/components/resources/ColumnPickerList.test.tsx +46 -0
  105. package/src/components/resources/ColumnPickerList.tsx +174 -0
  106. package/src/components/resources/ResourcesSidebar.keyboard.test.tsx +42 -0
  107. package/src/components/resources/ResourcesSidebar.test.tsx +36 -2
  108. package/src/components/resources/ResourcesSidebar.tsx +92 -41
  109. package/src/components/resources/ResourcesView.kind-filter-memory.test.ts +52 -0
  110. package/src/components/resources/ResourcesView.kind-view.test.tsx +296 -0
  111. package/src/components/resources/ResourcesView.per-container.test.ts +56 -1
  112. package/src/components/resources/ResourcesView.tsx +392 -181
  113. package/src/components/resources/column-order.test.ts +104 -0
  114. package/src/components/resources/column-order.ts +64 -0
  115. package/src/components/resources/curated-column-ownership.test.ts +3 -3
  116. package/src/components/resources/renderers/ArgoApplicationRenderer.test.tsx +15 -0
  117. package/src/components/resources/renderers/ArgoApplicationRenderer.tsx +4 -1
  118. package/src/components/resources/renderers/AutoscalingReferences.test.tsx +286 -0
  119. package/src/components/resources/renderers/CAPIClusterRenderer.tsx +3 -2
  120. package/src/components/resources/renderers/CAPIExactReferenceNavigation.test.tsx +27 -0
  121. package/src/components/resources/renderers/CNPGBackupRenderer.test.tsx +23 -0
  122. package/src/components/resources/renderers/CNPGBackupRenderer.tsx +8 -19
  123. package/src/components/resources/renderers/CNPGClusterRenderer.tsx +5 -7
  124. package/src/components/resources/renderers/CNPGDeclarativeRenderer.tsx +25 -0
  125. package/src/components/resources/renderers/CNPGScheduledBackupRenderer.test.tsx +32 -0
  126. package/src/components/resources/renderers/CNPGScheduledBackupRenderer.tsx +7 -15
  127. package/src/components/resources/renderers/CompositeRenderer.navigation.test.tsx +35 -0
  128. package/src/components/resources/renderers/CompositeRenderer.tsx +3 -3
  129. package/src/components/resources/renderers/CompositionRenderer.tsx +3 -3
  130. package/src/components/resources/renderers/ContourExactReferenceNavigation.test.tsx +34 -0
  131. package/src/components/resources/renderers/ContourHTTPProxyRenderer.tsx +11 -10
  132. package/src/components/resources/renderers/CronJobRenderer.test.tsx +31 -0
  133. package/src/components/resources/renderers/CronJobRenderer.tsx +28 -20
  134. package/src/components/resources/renderers/EndpointSliceRenderer.test.tsx +39 -1
  135. package/src/components/resources/renderers/EndpointSliceRenderer.tsx +14 -13
  136. package/src/components/resources/renderers/FluxHelmReleaseRenderer.tsx +22 -9
  137. package/src/components/resources/renderers/HPARenderer.tsx +56 -16
  138. package/src/components/resources/renderers/IngressRenderer.defaultBackend.test.tsx +33 -0
  139. package/src/components/resources/renderers/IngressRenderer.tsx +30 -3
  140. package/src/components/resources/renderers/IstioDestinationRuleRenderer.tsx +9 -9
  141. package/src/components/resources/renderers/IstioExactReferenceNavigation.test.tsx +34 -0
  142. package/src/components/resources/renderers/IstioVirtualServiceRenderer.tsx +5 -8
  143. package/src/components/resources/renderers/KedaScaledObjectRenderer.tsx +13 -11
  144. package/src/components/resources/renderers/KnativeEventingRenderer.tsx +24 -31
  145. package/src/components/resources/renderers/KnativeExactReferenceNavigation.test.tsx +47 -0
  146. package/src/components/resources/renderers/KnativeFlowRenderer.tsx +6 -7
  147. package/src/components/resources/renderers/KnativeNetworkingRenderer.tsx +7 -9
  148. package/src/components/resources/renderers/KnativeReferenceLink.tsx +14 -0
  149. package/src/components/resources/renderers/KnativeReferenceLinks.test.tsx +60 -0
  150. package/src/components/resources/renderers/KnativeSourceRenderer.tsx +9 -11
  151. package/src/components/resources/renderers/ManagedResourceRenderer.tsx +2 -2
  152. package/src/components/resources/renderers/MetricsUnavailableNotice.tsx +3 -2
  153. package/src/components/resources/renderers/PodRenderer.sidecar-terminal.test.tsx +200 -0
  154. package/src/components/resources/renderers/PodRenderer.test.tsx +32 -0
  155. package/src/components/resources/renderers/PodRenderer.tsx +81 -40
  156. package/src/components/resources/renderers/SimpleRouteRenderer.tsx +1 -1
  157. package/src/components/resources/renderers/VPARenderer.tsx +17 -11
  158. package/src/components/resources/resource-utils-capi.ts +1 -1
  159. package/src/components/resources/resource-utils-cnpg.test.ts +35 -10
  160. package/src/components/resources/resource-utils-cnpg.ts +51 -20
  161. package/src/components/resources/resource-utils-contour.test.ts +44 -0
  162. package/src/components/resources/resource-utils-contour.ts +16 -0
  163. package/src/components/resources/resource-utils-istio.ts +12 -8
  164. package/src/components/resources/resource-utils-knative.ts +18 -0
  165. package/src/components/resources/resource-utils.ts +96 -7
  166. package/src/components/resources/rules-column-width.test.ts +1 -1
  167. package/src/components/resources/summarize-scheduler-message.test.ts +31 -0
  168. package/src/components/shared/ActionConfirmDialog.test.tsx +154 -0
  169. package/src/components/shared/ActionConfirmDialog.tsx +246 -0
  170. package/src/components/shared/CreateResourceDialog.test.tsx +330 -0
  171. package/src/components/shared/CreateResourceDialog.tsx +126 -80
  172. package/src/components/shared/DetailShell.test.tsx +80 -0
  173. package/src/components/shared/DetailShell.tsx +85 -10
  174. package/src/components/shared/DrainPlanDialog.test.tsx +7 -0
  175. package/src/components/shared/DrainPlanDialog.tsx +6 -2
  176. package/src/components/shared/EditableYamlView.test.tsx +7 -0
  177. package/src/components/shared/EditableYamlView.tsx +1 -1
  178. package/src/components/shared/ResourceActionsBar.tsx +14 -7
  179. package/src/components/shared/ResourceRendererDispatch.tsx +15 -10
  180. package/src/components/shared/index.ts +2 -0
  181. package/src/components/timeline/DiffViewer.test.tsx +69 -0
  182. package/src/components/timeline/DiffViewer.tsx +113 -4
  183. package/src/components/timeline/TimelineSwimlanes.tsx +1 -1
  184. package/src/components/timeline/shared.test.tsx +48 -0
  185. package/src/components/timeline/shared.tsx +14 -19
  186. package/src/components/timeline/timeline-filters.ts +1 -0
  187. package/src/components/topology/K8sResourceNode.test.ts +18 -1
  188. package/src/components/topology/K8sResourceNode.tsx +44 -2
  189. package/src/components/topology/TopologyFilterSidebar.tsx +1 -0
  190. package/src/components/topology/TopologyGraph.tsx +3 -71
  191. package/src/components/topology/TopologySearch.tsx +1 -0
  192. package/src/components/topology/internet-groups.test.ts +35 -0
  193. package/src/components/topology/internet-groups.ts +55 -0
  194. package/src/components/topology/layout.ts +1 -1
  195. package/src/components/ui/Badge.tsx +1 -0
  196. package/src/components/ui/FoldSection.tsx +68 -0
  197. package/src/components/ui/ForceDeleteConfirmDialog.test.tsx +62 -0
  198. package/src/components/ui/ForceDeleteConfirmDialog.tsx +24 -12
  199. package/src/components/ui/RefLink.tsx +20 -0
  200. package/src/components/ui/RowActionMenu.tsx +8 -3
  201. package/src/components/ui/Toast.tsx +15 -4
  202. package/src/components/ui/crd-definition-navigation.test.tsx +160 -0
  203. package/src/components/ui/drawer-components.test.tsx +110 -1
  204. package/src/components/ui/drawer-components.tsx +62 -9
  205. package/src/components/ui/index.ts +4 -2
  206. package/src/components/ui/monacoEditorFeatures.ts +77 -0
  207. package/src/components/ui/monacoRuntime.ts +14 -19
  208. package/src/components/ui/status-tone.test.ts +10 -1
  209. package/src/components/ui/status-tone.tsx +29 -0
  210. package/src/components/workload/WorkloadView.deleted.test.tsx +43 -0
  211. package/src/components/workload/WorkloadView.relationships.test.tsx +37 -0
  212. package/src/components/workload/WorkloadView.spec.test.tsx +149 -0
  213. package/src/components/workload/WorkloadView.tsx +193 -71
  214. package/src/index.ts +14 -1
  215. package/src/monaco-deep.d.ts +19 -12
  216. package/src/theme/components.css +13 -0
  217. package/src/types/capacity.ts +11 -2
  218. package/src/types/core.ts +53 -2
  219. package/src/types/gitops-flux-status.test.ts +32 -1
  220. package/src/types/gitops-insights.ts +20 -2
  221. package/src/types/gitops-tree.ts +12 -0
  222. package/src/types/gitops.ts +26 -6
  223. package/src/utils/api-resources.ts +7 -0
  224. package/src/utils/application-history.ts +1 -0
  225. package/src/utils/cnpg-backup.test.ts +26 -0
  226. package/src/utils/cnpg-backup.ts +70 -0
  227. package/src/utils/format.test.ts +9 -0
  228. package/src/utils/format.ts +4 -0
  229. package/src/utils/gitops-owner.ts +3 -2
  230. package/src/utils/grant.test.ts +19 -0
  231. package/src/utils/grant.ts +35 -0
  232. package/src/utils/index.ts +4 -0
  233. package/src/utils/k8s-errors.ts +7 -7
  234. package/src/utils/log-format.test.ts +41 -0
  235. package/src/utils/log-format.ts +71 -15
  236. package/src/utils/log-level.ts +12 -8
  237. package/src/utils/navigation.test.ts +57 -0
  238. package/src/utils/navigation.ts +86 -8
  239. package/src/utils/object-reference-navigation.test.ts +43 -0
  240. package/src/utils/resource-hierarchy.test.ts +37 -0
  241. package/src/utils/resource-hierarchy.ts +15 -7
  242. package/src/utils/resource-icons.ts +2 -0
  243. package/src/utils/skeleton-yaml.test.ts +21 -0
  244. package/src/utils/skeleton-yaml.ts +16 -0
  245. package/src/utils/url-path.test.ts +35 -0
  246. package/src/utils/url-path.ts +21 -0
  247. package/src/utils/validators.test.ts +4 -13
  248. package/src/utils/validators.ts +5 -19
  249. package/src/utils/view-transition.test.ts +35 -1
  250. package/src/utils/view-transition.ts +16 -1
@@ -0,0 +1,21 @@
1
+ export function stripTrailingSlashes(value: string): string {
2
+ let end = value.length
3
+ while (end > 0 && value[end - 1] === '/') end--
4
+ return value.slice(0, end)
5
+ }
6
+
7
+ export function normalizeURLForComparison(value: string): string | null {
8
+ if (!value) return ''
9
+ if (value.includes('\\')) return null
10
+ try {
11
+ const url = new URL(value)
12
+ const authorityStart = value.indexOf('://')
13
+ if (authorityStart < 0 || !url.hostname || url.username || url.password || value.includes('?') || value.includes('#')) return null
14
+ const pathStart = value.indexOf('/', authorityStart + 3)
15
+ const path = pathStart < 0 ? '' : value.slice(pathStart)
16
+ const port = url.port || (url.protocol === 'https:' ? '443' : url.protocol === 'http:' ? '80' : '')
17
+ return `${url.protocol}//${url.hostname.toLowerCase()}:${port}${stripTrailingSlashes(path)}`
18
+ } catch {
19
+ return null
20
+ }
21
+ }
@@ -82,25 +82,16 @@ describe('validateRFC1123Subdomain', () => {
82
82
  if (!r.valid) expect(r.error).toMatch(/253/)
83
83
  })
84
84
 
85
- it('rejects single labels longer than 63 chars even when total is under 253', () => {
86
- // K8s' IsDNS1123Subdomain enforces ≤ 63 chars per dot-
87
- // separated label on top of the 253-char total. Without the
88
- // per-label check, a single 200-char label slipped through
89
- // client-side and was rejected only server-side.
90
- const huge = 'a'.repeat(200)
91
- const r = validateRFC1123Subdomain(huge)
92
- expect(r.valid).toBe(false)
93
- if (!r.valid) expect(r.error).toMatch(/63/)
85
+ it('accepts long undotted names under the Kubernetes total limit', () => {
86
+ expect(validateRFC1123Subdomain('a'.repeat(200)).valid).toBe(true)
94
87
  })
95
88
 
96
89
  it('accepts a 63-char label at the boundary', () => {
97
90
  expect(validateRFC1123Subdomain('a'.repeat(63)).valid).toBe(true)
98
91
  })
99
92
 
100
- it('rejects only the offending label when other labels are fine', () => {
101
- const r = validateRFC1123Subdomain(`ok.${'a'.repeat(64)}.also-ok`)
102
- expect(r.valid).toBe(false)
103
- if (!r.valid) expect(r.error).toMatch(/63/)
93
+ it('accepts long dot-separated parts under the Kubernetes total limit', () => {
94
+ expect(validateRFC1123Subdomain(`ok.${'a'.repeat(64)}.also-ok`).valid).toBe(true)
104
95
  })
105
96
  })
106
97
 
@@ -17,7 +17,7 @@ export type ValidationResult =
17
17
  | { valid: false; error: string }
18
18
 
19
19
  const RFC1123_LABEL_MAX = 63
20
- // Kubernetes namespace / pod / service names are RFC 1123 labels:
20
+ // Kubernetes namespace names are RFC 1123 labels:
21
21
  // lowercase alphanumeric or '-', start and end alphanumeric, ≤63 chars.
22
22
  // Source: k8s.io/apimachinery/pkg/util/validation.IsDNS1123Label.
23
23
  const RFC1123_LABEL_RE = /^[a-z0-9]([-a-z0-9]*[a-z0-9])?$/
@@ -34,9 +34,7 @@ const HELM_RELEASE_NAME_MAX = 53
34
34
  // https://helm.sh/docs/chart_best_practices/conventions/#chart-names
35
35
 
36
36
  /**
37
- * Validates a Kubernetes RFC 1123 label. Used for namespace names,
38
- * pod / service / configmap / secret names — anything Kubernetes
39
- * accepts as a single-segment identifier.
37
+ * Matches Kubernetes IsDNS1123Label, including namespace names.
40
38
  */
41
39
  export function validateRFC1123Label(input: string): ValidationResult {
42
40
  if (input.length === 0) {
@@ -64,9 +62,9 @@ export function validateRFC1123Label(input: string): ValidationResult {
64
62
  }
65
63
 
66
64
  /**
67
- * Validates a Kubernetes RFC 1123 subdomain (labels joined by dots).
68
- * Used for Helm chart names, image registries — anything that allows
69
- * dotted segments. NOT for pod/namespace names; use the label form.
65
+ * Matches Kubernetes IsDNS1123Subdomain: the whole name is capped at 253
66
+ * characters, without a separate 63-character cap on dot-separated parts.
67
+ * Namespace names use validateRFC1123Label instead.
70
68
  */
71
69
  export function validateRFC1123Subdomain(input: string): ValidationResult {
72
70
  if (input.length === 0) {
@@ -90,18 +88,6 @@ export function validateRFC1123Subdomain(input: string): ValidationResult {
90
88
  error: 'must contain only lowercase letters, numbers, hyphens, and dots, and each segment must start/end with a letter or number',
91
89
  }
92
90
  }
93
- // Per-label cap: K8s' IsDNS1123Subdomain enforces ≤ 63 chars
94
- // PER dot-separated label on top of the 253-char total. The
95
- // regex above doesn't catch that; without this check a single
96
- // 200-char label passes here and only fails server-side.
97
- for (const label of input.split('.')) {
98
- if (label.length > RFC1123_LABEL_MAX) {
99
- return {
100
- valid: false,
101
- error: `each dot-separated label must be at most ${RFC1123_LABEL_MAX} characters (got ${label.length} for "${label}")`,
102
- }
103
- }
104
- }
105
91
  return { valid: true }
106
92
  }
107
93
 
@@ -1,5 +1,5 @@
1
1
  import { describe, it, expect, vi } from 'vitest'
2
- import { isInvalidStateError, startViewTransitionSafe } from './view-transition'
2
+ import { isInvalidStateError, isWebKitGTK, startViewTransitionSafe } from './view-transition'
3
3
 
4
4
  // SKY-833 bug 49: rapid tab / route switches surfaced
5
5
  // "InvalidStateError: Transition was aborted because of invalid state"
@@ -79,3 +79,37 @@ describe('startViewTransitionSafe', () => {
79
79
  expect(isInvalidStateError(new TypeError('y'))).toBe(false)
80
80
  })
81
81
  })
82
+
83
+ const WEBKITGTK_UA =
84
+ 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.0 Safari/605.1.15'
85
+
86
+ describe('isWebKitGTK', () => {
87
+ it('matches WebKitGTK user agents', () => {
88
+ expect(isWebKitGTK(WEBKITGTK_UA)).toBe(true)
89
+ expect(isWebKitGTK('Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/605.1.15 (KHTML, like Gecko)')).toBe(true)
90
+ })
91
+
92
+ it('does not match other engines on Linux, or Safari', () => {
93
+ expect(isWebKitGTK('Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Safari/537.36')).toBe(false)
94
+ expect(isWebKitGTK('Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0')).toBe(false)
95
+ expect(isWebKitGTK('Mozilla/5.0 (Linux; Android 15) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Mobile Safari/537.36')).toBe(false)
96
+ expect(isWebKitGTK('Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.0 Safari/605.1.15')).toBe(false)
97
+ })
98
+ })
99
+
100
+ describe('startViewTransitionSafe under WebKitGTK', () => {
101
+ it('applies the update directly instead of starting a transition', () => {
102
+ const originalDoc = (globalThis as { document?: unknown }).document
103
+ const startSpy = vi.fn()
104
+ ;(globalThis as { document: object }).document = { startViewTransition: startSpy }
105
+ const uaSpy = vi.spyOn(navigator, 'userAgent', 'get').mockReturnValue(WEBKITGTK_UA)
106
+
107
+ const update = vi.fn()
108
+ startViewTransitionSafe(update)
109
+ expect(update).toHaveBeenCalledTimes(1)
110
+ expect(startSpy).not.toHaveBeenCalled()
111
+
112
+ uaSpy.mockRestore()
113
+ ;(globalThis as { document?: unknown }).document = originalDoc
114
+ })
115
+ })
@@ -14,6 +14,13 @@
14
14
  *
15
15
  * Other rejection types (programming errors, unsupported configs)
16
16
  * are re-thrown so we don't accidentally hide real bugs.
17
+ *
18
+ * Under WebKitGTK (the Linux desktop app's webview) the update is applied
19
+ * without a transition: when WebKitGTK has no renderer buffer transport —
20
+ * WEBKIT_DISABLE_DMABUF_RENDERER or WEBKIT_DISABLE_COMPOSITING_MODE set, a
21
+ * distro patch, or an EGL stack without GBM/surfaceless platforms — starting
22
+ * a view transition segfaults the UI process. The page cannot tell which
23
+ * case it is in, so it skips the cross-fade on every WebKitGTK host.
17
24
  */
18
25
  export function startViewTransitionSafe(update: () => void): void {
19
26
  // Some lib.dom.d.ts versions don't yet expose `startViewTransition`
@@ -27,7 +34,7 @@ export function startViewTransitionSafe(update: () => void): void {
27
34
  }
28
35
  }
29
36
 
30
- if (typeof doc.startViewTransition !== 'function') {
37
+ if (typeof doc.startViewTransition !== 'function' || isWebKitGTK(navigator.userAgent)) {
31
38
  update()
32
39
  return
33
40
  }
@@ -60,3 +67,11 @@ export function isInvalidStateError(err: unknown): boolean {
60
67
  (err as { name: unknown }).name === 'InvalidStateError'
61
68
  )
62
69
  }
70
+
71
+ /**
72
+ * WebKitGTK identifies as AppleWebKit on Linux. Chromium-based browsers on
73
+ * Linux and Android carry AppleWebKit too, so they are excluded by name.
74
+ */
75
+ export function isWebKitGTK(userAgent: string): boolean {
76
+ return /\bLinux\b/.test(userAgent) && /AppleWebKit/.test(userAgent) && !/Chrome|Chromium|Android/.test(userAgent)
77
+ }