From 3b9077cb0ca9d0ffa3219007051bd9069a590fb7 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Tue, 6 Oct 2026 11:16:45 +0000
Subject: [PATCH 1/4] Initial plan
From 28667dc83abbe1a0ea224015149fdb2f7c6eb176 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Tue, 6 Oct 2026 11:25:19 +0000
Subject: [PATCH 2/4] feat: display widget version in all widgets
Co-authored-by: sirpy <671095+sirpy@users.noreply.github.com>
---
docs/widget-author-instructions.md | 19 +++++++++++++++
.../ai-credits-widget/src/AiCreditsWidget.tsx | 3 +++
.../ai-credits-widget/tsconfig.build.json | 4 ++--
packages/ai-credits-widget/tsconfig.json | 4 ++--
.../src/CitizenClaimWidget.tsx | 3 +++
.../citizen-claim-widget/tsconfig.build.json | 4 ++--
packages/citizen-claim-widget/tsconfig.json | 4 ++--
.../src/ClaimWidget.tsx | 3 +++
.../tsconfig.build.json | 4 ++--
.../claim-widget-theme-demo/tsconfig.json | 4 ++--
.../gooddata-widget/src/GoodDataWidget.tsx | 4 +++-
packages/gooddata-widget/tsconfig.build.json | 4 ++--
packages/gooddata-widget/tsconfig.json | 4 ++--
.../src/GoodReserveWidget.tsx | 3 +++
.../goodreserve-widget/tsconfig.build.json | 4 ++--
packages/goodreserve-widget/tsconfig.json | 4 ++--
.../src/GovernanceOnboardingWidget.tsx | 4 +++-
.../src/GovernanceWidget.tsx | 3 +++
.../governance-widget/tsconfig.build.json | 4 ++--
packages/governance-widget/tsconfig.json | 4 ++--
.../src/StakingMigrationWidget.tsx | 3 +++
.../tsconfig.build.json | 4 ++--
.../staking-migration-widget/tsconfig.json | 4 ++--
.../streaming-widget/src/StreamingWidget.tsx | 4 ++++
packages/streaming-widget/tsconfig.build.json | 4 ++--
packages/streaming-widget/tsconfig.json | 4 ++--
.../src/SuperfluidCampaignWidget.tsx | 3 +++
.../tsconfig.build.json | 4 ++--
.../superfluid-campaign-widget/tsconfig.json | 4 ++--
packages/ui/src/components/WidgetVersion.tsx | 24 +++++++++++++++++++
packages/ui/src/index.ts | 2 ++
31 files changed, 112 insertions(+), 38 deletions(-)
create mode 100644 packages/ui/src/components/WidgetVersion.tsx
diff --git a/docs/widget-author-instructions.md b/docs/widget-author-instructions.md
index fdf109aa..78097052 100644
--- a/docs/widget-author-instructions.md
+++ b/docs/widget-author-instructions.md
@@ -298,6 +298,24 @@ inline. It becomes hard to review and hard to theme correctly.
---
+## Widget Version Label
+
+Every widget must show its own version, read from the widget package's `package.json`, as a
+small muted label in the bottom-right corner. Render the shared `WidgetVersion` primitive as the
+last child of the widget's root (inside `GoodWidgetProvider`) so it never overlaps content:
+
+```tsx
+import { WidgetVersion } from '@goodwidget/ui'
+import packageJson from '../package.json'
+
+
+```
+
+The widget's `tsconfig.json` and `tsconfig.build.json` must use `"rootDir": "."` and include
+`package.json` so the import type-checks.
+
+---
+
## Review Checklist
Before requesting review:
@@ -310,6 +328,7 @@ Before requesting review:
- Light and dark states were checked when the component supports both.
- Mobile width was checked against the design reference.
- Storybook screenshots were refreshed when visual states changed.
+- The widget renders ``.
- Manual visual review was done against Figma/Stitch/reference screenshots.
If a design seems impossible to express through the current system, do not work around it by
diff --git a/packages/ai-credits-widget/src/AiCreditsWidget.tsx b/packages/ai-credits-widget/src/AiCreditsWidget.tsx
index fb46271a..3dd68c59 100644
--- a/packages/ai-credits-widget/src/AiCreditsWidget.tsx
+++ b/packages/ai-credits-widget/src/AiCreditsWidget.tsx
@@ -20,6 +20,8 @@ import {
createToast,
updateToast,
} from '@goodwidget/ui'
+import packageJson from '../package.json'
+import { WidgetVersion } from '@goodwidget/ui'
import { needsWalletConnection, useAiCreditsAdapter } from './adapter'
import { useAiCreditsHistory } from './useAiCreditsHistory'
import {
@@ -649,6 +651,7 @@ export function AiCreditsWidget({
showWalletControls={showWalletControls}
/>
+
)
diff --git a/packages/ai-credits-widget/tsconfig.build.json b/packages/ai-credits-widget/tsconfig.build.json
index 54871c40..60a46961 100644
--- a/packages/ai-credits-widget/tsconfig.build.json
+++ b/packages/ai-credits-widget/tsconfig.build.json
@@ -2,10 +2,10 @@
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"outDir": "dist",
- "rootDir": "src",
+ "rootDir": ".",
"paths": {
"react-native": ["./node_modules/react-native-web"]
}
},
- "include": ["src"]
+ "include": ["src", "package.json"]
}
diff --git a/packages/ai-credits-widget/tsconfig.json b/packages/ai-credits-widget/tsconfig.json
index 006b6f40..5f693f93 100644
--- a/packages/ai-credits-widget/tsconfig.json
+++ b/packages/ai-credits-widget/tsconfig.json
@@ -2,7 +2,7 @@
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"outDir": "dist",
- "rootDir": "src",
+ "rootDir": ".",
"paths": {
"@goodwidget/core": ["../core/src/index.ts"],
"@goodwidget/core/*": ["../core/src/*"],
@@ -10,5 +10,5 @@
"react-native": ["./node_modules/react-native-web"]
}
},
- "include": ["src"]
+ "include": ["src", "package.json"]
}
diff --git a/packages/citizen-claim-widget/src/CitizenClaimWidget.tsx b/packages/citizen-claim-widget/src/CitizenClaimWidget.tsx
index ce73257e..9b347785 100644
--- a/packages/citizen-claim-widget/src/CitizenClaimWidget.tsx
+++ b/packages/citizen-claim-widget/src/CitizenClaimWidget.tsx
@@ -20,6 +20,8 @@ import {
YStack,
WidgetTabs,
} from '@goodwidget/ui'
+import packageJson from '../package.json'
+import { WidgetVersion } from '@goodwidget/ui'
import { SupportedChains } from '@goodsdks/citizen-sdk'
import { getChainDisplayName, useCitizenClaimAdapter } from './adapter'
import type {
@@ -687,6 +689,7 @@ export function CitizenClaimWidget({
onClaimError={onClaimError}
initialTab={initialTab}
/>
+
)
}
diff --git a/packages/citizen-claim-widget/tsconfig.build.json b/packages/citizen-claim-widget/tsconfig.build.json
index 54871c40..60a46961 100644
--- a/packages/citizen-claim-widget/tsconfig.build.json
+++ b/packages/citizen-claim-widget/tsconfig.build.json
@@ -2,10 +2,10 @@
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"outDir": "dist",
- "rootDir": "src",
+ "rootDir": ".",
"paths": {
"react-native": ["./node_modules/react-native-web"]
}
},
- "include": ["src"]
+ "include": ["src", "package.json"]
}
diff --git a/packages/citizen-claim-widget/tsconfig.json b/packages/citizen-claim-widget/tsconfig.json
index 006b6f40..5f693f93 100644
--- a/packages/citizen-claim-widget/tsconfig.json
+++ b/packages/citizen-claim-widget/tsconfig.json
@@ -2,7 +2,7 @@
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"outDir": "dist",
- "rootDir": "src",
+ "rootDir": ".",
"paths": {
"@goodwidget/core": ["../core/src/index.ts"],
"@goodwidget/core/*": ["../core/src/*"],
@@ -10,5 +10,5 @@
"react-native": ["./node_modules/react-native-web"]
}
},
- "include": ["src"]
+ "include": ["src", "package.json"]
}
diff --git a/packages/claim-widget-theme-demo/src/ClaimWidget.tsx b/packages/claim-widget-theme-demo/src/ClaimWidget.tsx
index f6db42da..e8f47222 100644
--- a/packages/claim-widget-theme-demo/src/ClaimWidget.tsx
+++ b/packages/claim-widget-theme-demo/src/ClaimWidget.tsx
@@ -19,6 +19,8 @@ import {
XStack,
YStack,
} from '@goodwidget/ui'
+import packageJson from '../package.json'
+import { WidgetVersion } from '@goodwidget/ui'
const ClaimCard = createComponent(Card, {
name: 'ClaimCard',
@@ -261,6 +263,7 @@ export function ClaimWidget({
defaultTheme={defaultTheme}
>
+
)
}
diff --git a/packages/claim-widget-theme-demo/tsconfig.build.json b/packages/claim-widget-theme-demo/tsconfig.build.json
index c2f740fe..87d73765 100644
--- a/packages/claim-widget-theme-demo/tsconfig.build.json
+++ b/packages/claim-widget-theme-demo/tsconfig.build.json
@@ -2,11 +2,11 @@
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"outDir": "dist",
- "rootDir": "src",
+ "rootDir": ".",
"paths": {
"@goodwidget/ui": ["../ui/src/index.ts"],
"react-native": ["./node_modules/react-native-web"]
}
},
- "include": ["src"]
+ "include": ["src", "package.json"]
}
diff --git a/packages/claim-widget-theme-demo/tsconfig.json b/packages/claim-widget-theme-demo/tsconfig.json
index 66f66cf7..5ab7b0d2 100644
--- a/packages/claim-widget-theme-demo/tsconfig.json
+++ b/packages/claim-widget-theme-demo/tsconfig.json
@@ -2,7 +2,7 @@
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"outDir": "dist",
- "rootDir": "src",
+ "rootDir": ".",
"paths": {
"@goodwidget/core": ["../core/src/index.ts"],
"@goodwidget/core/*": ["../core/src/*"],
@@ -11,5 +11,5 @@
"react-native": ["./node_modules/react-native-web"]
}
},
- "include": ["src"]
+ "include": ["src", "package.json"]
}
diff --git a/packages/gooddata-widget/src/GoodDataWidget.tsx b/packages/gooddata-widget/src/GoodDataWidget.tsx
index 6c686812..0da42325 100644
--- a/packages/gooddata-widget/src/GoodDataWidget.tsx
+++ b/packages/gooddata-widget/src/GoodDataWidget.tsx
@@ -10,7 +10,8 @@
*/
import React from 'react'
import { BrowserRouter, Link, Route, Routes, useParams } from 'react-router-dom'
-import { Heading, Text, YStack } from '@goodwidget/ui'
+import { Heading, Text, WidgetVersion, YStack } from '@goodwidget/ui'
+import packageJson from '../package.json'
import { getDashboard, listDashboards } from './dashboards/registry'
const CONTENT_MAX_WIDTH = 1120
@@ -103,6 +104,7 @@ export function GoodDataWidget({ basename }: GoodDataWidgetProps = {}) {
} />
+
)
diff --git a/packages/gooddata-widget/tsconfig.build.json b/packages/gooddata-widget/tsconfig.build.json
index 54871c40..60a46961 100644
--- a/packages/gooddata-widget/tsconfig.build.json
+++ b/packages/gooddata-widget/tsconfig.build.json
@@ -2,10 +2,10 @@
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"outDir": "dist",
- "rootDir": "src",
+ "rootDir": ".",
"paths": {
"react-native": ["./node_modules/react-native-web"]
}
},
- "include": ["src"]
+ "include": ["src", "package.json"]
}
diff --git a/packages/gooddata-widget/tsconfig.json b/packages/gooddata-widget/tsconfig.json
index 1821cb56..bcceced4 100644
--- a/packages/gooddata-widget/tsconfig.json
+++ b/packages/gooddata-widget/tsconfig.json
@@ -2,12 +2,12 @@
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"outDir": "dist",
- "rootDir": "src",
+ "rootDir": ".",
"paths": {
"@goodwidget/embed": ["../embed/src/index.ts"],
"@goodwidget/ui": ["../ui/src/index.ts"],
"react-native": ["./node_modules/react-native-web"]
}
},
- "include": ["src"]
+ "include": ["src", "package.json"]
}
diff --git a/packages/goodreserve-widget/src/GoodReserveWidget.tsx b/packages/goodreserve-widget/src/GoodReserveWidget.tsx
index 52368652..93f844ed 100644
--- a/packages/goodreserve-widget/src/GoodReserveWidget.tsx
+++ b/packages/goodreserve-widget/src/GoodReserveWidget.tsx
@@ -1,6 +1,8 @@
import React, { useEffect, useRef } from 'react'
import { GoodWidgetProvider } from '@goodwidget/core'
import { YStack } from '@goodwidget/ui'
+import packageJson from '../package.json'
+import { WidgetVersion } from '@goodwidget/ui'
import type { EIP1193Provider } from '@goodwidget/core'
import { ReserveSwapView } from './ReserveSwapView'
import { useGoodReserveAdapter } from './useGoodReserveAdapter'
@@ -75,6 +77,7 @@ export function GoodReserveWidget({
mockState={mockState}
preferredChainId={preferredChainId}
/>
+
)
diff --git a/packages/goodreserve-widget/tsconfig.build.json b/packages/goodreserve-widget/tsconfig.build.json
index 54871c40..60a46961 100644
--- a/packages/goodreserve-widget/tsconfig.build.json
+++ b/packages/goodreserve-widget/tsconfig.build.json
@@ -2,10 +2,10 @@
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"outDir": "dist",
- "rootDir": "src",
+ "rootDir": ".",
"paths": {
"react-native": ["./node_modules/react-native-web"]
}
},
- "include": ["src"]
+ "include": ["src", "package.json"]
}
diff --git a/packages/goodreserve-widget/tsconfig.json b/packages/goodreserve-widget/tsconfig.json
index 66f66cf7..5ab7b0d2 100644
--- a/packages/goodreserve-widget/tsconfig.json
+++ b/packages/goodreserve-widget/tsconfig.json
@@ -2,7 +2,7 @@
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"outDir": "dist",
- "rootDir": "src",
+ "rootDir": ".",
"paths": {
"@goodwidget/core": ["../core/src/index.ts"],
"@goodwidget/core/*": ["../core/src/*"],
@@ -11,5 +11,5 @@
"react-native": ["./node_modules/react-native-web"]
}
},
- "include": ["src"]
+ "include": ["src", "package.json"]
}
diff --git a/packages/governance-widget/src/GovernanceOnboardingWidget.tsx b/packages/governance-widget/src/GovernanceOnboardingWidget.tsx
index 4439c7a8..36fe0989 100644
--- a/packages/governance-widget/src/GovernanceOnboardingWidget.tsx
+++ b/packages/governance-widget/src/GovernanceOnboardingWidget.tsx
@@ -1,5 +1,6 @@
import { useMemo } from 'react'
-import { PageWizardProvider } from '@goodwidget/ui'
+import { PageWizardProvider, WidgetVersion } from '@goodwidget/ui'
+import packageJson from '../package.json'
import { GovernanceOnboardingFlow } from './onboarding/GovernanceOnboardingFlow'
import { DEFAULT_FINAL_ACTIONS, DEFAULT_TRANSACTION_STEPS, ONBOARDING_STEPS } from './onboarding/constants'
import { HOUSE_COPY } from './onboarding/copy'
@@ -63,6 +64,7 @@ export function GovernanceOnboardingWidget({
onFinalActionPress={onFinalActionPress}
dataTestId={dataTestId}
/>
+
)
}
diff --git a/packages/governance-widget/src/GovernanceWidget.tsx b/packages/governance-widget/src/GovernanceWidget.tsx
index 77318584..cbe8b268 100644
--- a/packages/governance-widget/src/GovernanceWidget.tsx
+++ b/packages/governance-widget/src/GovernanceWidget.tsx
@@ -12,6 +12,8 @@ import {
XStack,
YStack,
} from '@goodwidget/ui'
+import packageJson from '../package.json'
+import { WidgetVersion } from '@goodwidget/ui'
import { useTheme } from 'tamagui'
import { AlignmentVotingProposalCard } from './AlignmentVotingProposalCard'
import { BalanceCard } from './BalanceCard'
@@ -759,6 +761,7 @@ export function GovernanceWidget({
) : (
)}
+
)
}
diff --git a/packages/governance-widget/tsconfig.build.json b/packages/governance-widget/tsconfig.build.json
index 5a24989c..7e89cd4c 100644
--- a/packages/governance-widget/tsconfig.build.json
+++ b/packages/governance-widget/tsconfig.build.json
@@ -2,7 +2,7 @@
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"outDir": "dist",
- "rootDir": "src"
+ "rootDir": "."
},
- "include": ["src"]
+ "include": ["src", "package.json"]
}
diff --git a/packages/governance-widget/tsconfig.json b/packages/governance-widget/tsconfig.json
index c2f740fe..87d73765 100644
--- a/packages/governance-widget/tsconfig.json
+++ b/packages/governance-widget/tsconfig.json
@@ -2,11 +2,11 @@
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"outDir": "dist",
- "rootDir": "src",
+ "rootDir": ".",
"paths": {
"@goodwidget/ui": ["../ui/src/index.ts"],
"react-native": ["./node_modules/react-native-web"]
}
},
- "include": ["src"]
+ "include": ["src", "package.json"]
}
diff --git a/packages/staking-migration-widget/src/StakingMigrationWidget.tsx b/packages/staking-migration-widget/src/StakingMigrationWidget.tsx
index 55e43e60..28fd9e22 100644
--- a/packages/staking-migration-widget/src/StakingMigrationWidget.tsx
+++ b/packages/staking-migration-widget/src/StakingMigrationWidget.tsx
@@ -2,6 +2,8 @@ import React, { useCallback, useMemo } from 'react'
import { GoodWidgetProvider } from '@goodwidget/core'
import type { EIP1193Provider } from '@goodwidget/core'
import { Text, ToastContainer, YStack } from '@goodwidget/ui'
+import packageJson from '../package.json'
+import { WidgetVersion } from '@goodwidget/ui'
import { MigrationProgressTimeline } from './MigrationProgressTimeline'
import { MigrationSummaryCard } from './MigrationSummaryCard'
import { MigrationSecondaryCard } from './migrationWidgetComponents'
@@ -155,6 +157,7 @@ export function StakingMigrationWidget({
adapterFactory={adapterFactory}
/>
+
)
}
diff --git a/packages/staking-migration-widget/tsconfig.build.json b/packages/staking-migration-widget/tsconfig.build.json
index 54871c40..60a46961 100644
--- a/packages/staking-migration-widget/tsconfig.build.json
+++ b/packages/staking-migration-widget/tsconfig.build.json
@@ -2,10 +2,10 @@
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"outDir": "dist",
- "rootDir": "src",
+ "rootDir": ".",
"paths": {
"react-native": ["./node_modules/react-native-web"]
}
},
- "include": ["src"]
+ "include": ["src", "package.json"]
}
diff --git a/packages/staking-migration-widget/tsconfig.json b/packages/staking-migration-widget/tsconfig.json
index 006b6f40..5f693f93 100644
--- a/packages/staking-migration-widget/tsconfig.json
+++ b/packages/staking-migration-widget/tsconfig.json
@@ -2,7 +2,7 @@
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"outDir": "dist",
- "rootDir": "src",
+ "rootDir": ".",
"paths": {
"@goodwidget/core": ["../core/src/index.ts"],
"@goodwidget/core/*": ["../core/src/*"],
@@ -10,5 +10,5 @@
"react-native": ["./node_modules/react-native-web"]
}
},
- "include": ["src"]
+ "include": ["src", "package.json"]
}
diff --git a/packages/streaming-widget/src/StreamingWidget.tsx b/packages/streaming-widget/src/StreamingWidget.tsx
index fbc25e39..5e9d2c1a 100644
--- a/packages/streaming-widget/src/StreamingWidget.tsx
+++ b/packages/streaming-widget/src/StreamingWidget.tsx
@@ -2,6 +2,8 @@ import React from 'react'
import { GoodWidgetProvider } from '@goodwidget/core'
import type { EIP1193Provider } from '@goodwidget/core'
import { ToastContainer } from '@goodwidget/ui'
+import packageJson from '../package.json'
+import { WidgetVersion } from '@goodwidget/ui'
import { useStreamingAdapter } from './adapter'
import { StreamingWidgetView } from './components/StreamingWidgetView'
import type {
@@ -47,6 +49,7 @@ export function StreamingWidgetPreview({
initialStreamsFormOpen={initialStreamsFormOpen}
/>
+
)
}
@@ -68,6 +71,7 @@ export function StreamingWidget({
>
+
)
}
diff --git a/packages/streaming-widget/tsconfig.build.json b/packages/streaming-widget/tsconfig.build.json
index 54871c40..60a46961 100644
--- a/packages/streaming-widget/tsconfig.build.json
+++ b/packages/streaming-widget/tsconfig.build.json
@@ -2,10 +2,10 @@
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"outDir": "dist",
- "rootDir": "src",
+ "rootDir": ".",
"paths": {
"react-native": ["./node_modules/react-native-web"]
}
},
- "include": ["src"]
+ "include": ["src", "package.json"]
}
diff --git a/packages/streaming-widget/tsconfig.json b/packages/streaming-widget/tsconfig.json
index 006b6f40..5f693f93 100644
--- a/packages/streaming-widget/tsconfig.json
+++ b/packages/streaming-widget/tsconfig.json
@@ -2,7 +2,7 @@
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"outDir": "dist",
- "rootDir": "src",
+ "rootDir": ".",
"paths": {
"@goodwidget/core": ["../core/src/index.ts"],
"@goodwidget/core/*": ["../core/src/*"],
@@ -10,5 +10,5 @@
"react-native": ["./node_modules/react-native-web"]
}
},
- "include": ["src"]
+ "include": ["src", "package.json"]
}
diff --git a/packages/superfluid-campaign-widget/src/SuperfluidCampaignWidget.tsx b/packages/superfluid-campaign-widget/src/SuperfluidCampaignWidget.tsx
index 7218b043..4c9668f4 100644
--- a/packages/superfluid-campaign-widget/src/SuperfluidCampaignWidget.tsx
+++ b/packages/superfluid-campaign-widget/src/SuperfluidCampaignWidget.tsx
@@ -3,6 +3,8 @@ import { GoodWidgetProvider, useWallet } from '@goodwidget/core'
import type { EIP1193Provider } from '@goodwidget/core'
import { CitizenClaimWidget } from '@goodwidget/citizen-claim-widget'
import { Card, Heading, Text, ToastContainer, YStack } from '@goodwidget/ui'
+import packageJson from '../package.json'
+import { WidgetVersion } from '@goodwidget/ui'
import { CampaignHeader } from './components/CampaignHeader'
import { FaqAccordion } from './components/FaqAccordion'
import { LeaderboardSummary } from './components/LeaderboardSummary'
@@ -308,6 +310,7 @@ export function SuperfluidCampaignWidgetWithClient({
/>
+
)
}
diff --git a/packages/superfluid-campaign-widget/tsconfig.build.json b/packages/superfluid-campaign-widget/tsconfig.build.json
index 54871c40..60a46961 100644
--- a/packages/superfluid-campaign-widget/tsconfig.build.json
+++ b/packages/superfluid-campaign-widget/tsconfig.build.json
@@ -2,10 +2,10 @@
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"outDir": "dist",
- "rootDir": "src",
+ "rootDir": ".",
"paths": {
"react-native": ["./node_modules/react-native-web"]
}
},
- "include": ["src"]
+ "include": ["src", "package.json"]
}
diff --git a/packages/superfluid-campaign-widget/tsconfig.json b/packages/superfluid-campaign-widget/tsconfig.json
index a720cb71..793b9eca 100644
--- a/packages/superfluid-campaign-widget/tsconfig.json
+++ b/packages/superfluid-campaign-widget/tsconfig.json
@@ -2,7 +2,7 @@
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"outDir": "dist",
- "rootDir": "src",
+ "rootDir": ".",
"paths": {
"@goodwidget/core": ["../core/src/index.ts"],
"@goodwidget/ui": ["../ui/src/index.ts"],
@@ -10,5 +10,5 @@
"react-native": ["./node_modules/react-native-web"]
}
},
- "include": ["src"]
+ "include": ["src", "package.json"]
}
diff --git a/packages/ui/src/components/WidgetVersion.tsx b/packages/ui/src/components/WidgetVersion.tsx
new file mode 100644
index 00000000..e77456e8
--- /dev/null
+++ b/packages/ui/src/components/WidgetVersion.tsx
@@ -0,0 +1,24 @@
+import { YStack } from 'tamagui'
+import { Text } from './Text'
+
+export interface WidgetVersionProps {
+ /** Widget package version, taken from the widget's own package.json. */
+ version: string
+ /** Optional test id override. */
+ testId?: string
+}
+
+/**
+ * Small, muted version label rendered at the bottom-right corner of a widget.
+ * It sits in normal flow below the widget content (right-aligned) so it never
+ * overlaps or shifts the widget layout.
+ */
+export function WidgetVersion({ version, testId = 'widget-version' }: WidgetVersionProps) {
+ return (
+
+
+ {`v${version}`}
+
+
+ )
+}
diff --git a/packages/ui/src/index.ts b/packages/ui/src/index.ts
index 4d1e88fb..d0671280 100644
--- a/packages/ui/src/index.ts
+++ b/packages/ui/src/index.ts
@@ -44,6 +44,8 @@ export { ScrollArea } from './components-test/ScrollArea'
// Typography
export { Heading } from './components/Heading'
export { Text } from './components/Text'
+export { WidgetVersion } from './components/WidgetVersion'
+export type { WidgetVersionProps } from './components/WidgetVersion'
export { Anchor } from './components/Anchor'
// Inputs
From 85089b2e3c4f37f138a085fcf6934bb4e53c4030 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Tue, 6 Oct 2026 11:25:29 +0000
Subject: [PATCH 3/4] Apply remaining changes
Co-authored-by: sirpy <671095+sirpy@users.noreply.github.com>
---
AGENTS.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/AGENTS.md b/AGENTS.md
index 284c8dc6..f8c548c8 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -92,6 +92,7 @@ GoodWidget/
- Treat public component names and theme targets as API surface (renaming is a breaking
change for integrators).
- Mirror the issue checklist in the PR body.
+- Render `` (from `@goodwidget/ui`) in every widget; see `docs/widget-author-instructions.md`.
- Preserve existing code comments when refactoring files unless explicitly asked to remove
them.
From f68e641716255c028ba4151f0304308ea7171381 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 7 Oct 2026 12:57:53 +0000
Subject: [PATCH 4/4] Import named version from package.json and add version UI
tests
Co-authored-by: sirpy <671095+sirpy@users.noreply.github.com>
---
AGENTS.md | 2 +-
docs/widget-author-instructions.md | 6 +++---
packages/ai-credits-widget/src/AiCreditsWidget.tsx | 4 ++--
.../citizen-claim-widget/src/CitizenClaimWidget.tsx | 4 ++--
.../claim-widget-theme-demo/src/ClaimWidget.tsx | 4 ++--
packages/gooddata-widget/src/GoodDataWidget.tsx | 4 ++--
.../goodreserve-widget/src/GoodReserveWidget.tsx | 4 ++--
.../src/GovernanceOnboardingWidget.tsx | 4 ++--
packages/governance-widget/src/GovernanceWidget.tsx | 4 ++--
.../src/StakingMigrationWidget.tsx | 4 ++--
packages/streaming-widget/src/StreamingWidget.tsx | 6 +++---
.../src/SuperfluidCampaignWidget.tsx | 4 ++--
tests/widgets/ai-credits-widget/version.spec.ts | 13 +++++++++++++
tests/widgets/citizen-claim-widget/version.spec.ts | 13 +++++++++++++
tests/widgets/goodreserve-widget/version.spec.ts | 11 +++++++++++
tests/widgets/governance-widget/version.spec.ts | 13 +++++++++++++
.../staking-migration-widget/version.spec.ts | 13 +++++++++++++
tests/widgets/streaming-widget/version.spec.ts | 13 +++++++++++++
.../superfluid-campaign-widget/version.spec.ts | 13 +++++++++++++
19 files changed, 114 insertions(+), 25 deletions(-)
create mode 100644 tests/widgets/ai-credits-widget/version.spec.ts
create mode 100644 tests/widgets/citizen-claim-widget/version.spec.ts
create mode 100644 tests/widgets/goodreserve-widget/version.spec.ts
create mode 100644 tests/widgets/governance-widget/version.spec.ts
create mode 100644 tests/widgets/staking-migration-widget/version.spec.ts
create mode 100644 tests/widgets/streaming-widget/version.spec.ts
create mode 100644 tests/widgets/superfluid-campaign-widget/version.spec.ts
diff --git a/AGENTS.md b/AGENTS.md
index f8c548c8..4fe02350 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -92,7 +92,7 @@ GoodWidget/
- Treat public component names and theme targets as API surface (renaming is a breaking
change for integrators).
- Mirror the issue checklist in the PR body.
-- Render `` (from `@goodwidget/ui`) in every widget; see `docs/widget-author-instructions.md`.
+- Render `` (from `@goodwidget/ui`) in every widget; see `docs/widget-author-instructions.md`.
- Preserve existing code comments when refactoring files unless explicitly asked to remove
them.
diff --git a/docs/widget-author-instructions.md b/docs/widget-author-instructions.md
index 78097052..60943d8c 100644
--- a/docs/widget-author-instructions.md
+++ b/docs/widget-author-instructions.md
@@ -306,9 +306,9 @@ last child of the widget's root (inside `GoodWidgetProvider`) so it never overla
```tsx
import { WidgetVersion } from '@goodwidget/ui'
-import packageJson from '../package.json'
+import { version } from '../package.json'
-
+
```
The widget's `tsconfig.json` and `tsconfig.build.json` must use `"rootDir": "."` and include
@@ -328,7 +328,7 @@ Before requesting review:
- Light and dark states were checked when the component supports both.
- Mobile width was checked against the design reference.
- Storybook screenshots were refreshed when visual states changed.
-- The widget renders ``.
+- The widget renders ``.
- Manual visual review was done against Figma/Stitch/reference screenshots.
If a design seems impossible to express through the current system, do not work around it by
diff --git a/packages/ai-credits-widget/src/AiCreditsWidget.tsx b/packages/ai-credits-widget/src/AiCreditsWidget.tsx
index 3dd68c59..c9b7f6d9 100644
--- a/packages/ai-credits-widget/src/AiCreditsWidget.tsx
+++ b/packages/ai-credits-widget/src/AiCreditsWidget.tsx
@@ -20,7 +20,7 @@ import {
createToast,
updateToast,
} from '@goodwidget/ui'
-import packageJson from '../package.json'
+import { version } from '../package.json'
import { WidgetVersion } from '@goodwidget/ui'
import { needsWalletConnection, useAiCreditsAdapter } from './adapter'
import { useAiCreditsHistory } from './useAiCreditsHistory'
@@ -651,7 +651,7 @@ export function AiCreditsWidget({
showWalletControls={showWalletControls}
/>
-
+
)
diff --git a/packages/citizen-claim-widget/src/CitizenClaimWidget.tsx b/packages/citizen-claim-widget/src/CitizenClaimWidget.tsx
index 9b347785..89782d65 100644
--- a/packages/citizen-claim-widget/src/CitizenClaimWidget.tsx
+++ b/packages/citizen-claim-widget/src/CitizenClaimWidget.tsx
@@ -20,7 +20,7 @@ import {
YStack,
WidgetTabs,
} from '@goodwidget/ui'
-import packageJson from '../package.json'
+import { version } from '../package.json'
import { WidgetVersion } from '@goodwidget/ui'
import { SupportedChains } from '@goodsdks/citizen-sdk'
import { getChainDisplayName, useCitizenClaimAdapter } from './adapter'
@@ -689,7 +689,7 @@ export function CitizenClaimWidget({
onClaimError={onClaimError}
initialTab={initialTab}
/>
-
+
)
}
diff --git a/packages/claim-widget-theme-demo/src/ClaimWidget.tsx b/packages/claim-widget-theme-demo/src/ClaimWidget.tsx
index e8f47222..146bcd2f 100644
--- a/packages/claim-widget-theme-demo/src/ClaimWidget.tsx
+++ b/packages/claim-widget-theme-demo/src/ClaimWidget.tsx
@@ -19,7 +19,7 @@ import {
XStack,
YStack,
} from '@goodwidget/ui'
-import packageJson from '../package.json'
+import { version } from '../package.json'
import { WidgetVersion } from '@goodwidget/ui'
const ClaimCard = createComponent(Card, {
@@ -263,7 +263,7 @@ export function ClaimWidget({
defaultTheme={defaultTheme}
>
-
+
)
}
diff --git a/packages/gooddata-widget/src/GoodDataWidget.tsx b/packages/gooddata-widget/src/GoodDataWidget.tsx
index 0da42325..3916fbc1 100644
--- a/packages/gooddata-widget/src/GoodDataWidget.tsx
+++ b/packages/gooddata-widget/src/GoodDataWidget.tsx
@@ -11,7 +11,7 @@
import React from 'react'
import { BrowserRouter, Link, Route, Routes, useParams } from 'react-router-dom'
import { Heading, Text, WidgetVersion, YStack } from '@goodwidget/ui'
-import packageJson from '../package.json'
+import { version } from '../package.json'
import { getDashboard, listDashboards } from './dashboards/registry'
const CONTENT_MAX_WIDTH = 1120
@@ -104,7 +104,7 @@ export function GoodDataWidget({ basename }: GoodDataWidgetProps = {}) {
} />
-
+
)
diff --git a/packages/goodreserve-widget/src/GoodReserveWidget.tsx b/packages/goodreserve-widget/src/GoodReserveWidget.tsx
index 93f844ed..748830f4 100644
--- a/packages/goodreserve-widget/src/GoodReserveWidget.tsx
+++ b/packages/goodreserve-widget/src/GoodReserveWidget.tsx
@@ -1,7 +1,7 @@
import React, { useEffect, useRef } from 'react'
import { GoodWidgetProvider } from '@goodwidget/core'
import { YStack } from '@goodwidget/ui'
-import packageJson from '../package.json'
+import { version } from '../package.json'
import { WidgetVersion } from '@goodwidget/ui'
import type { EIP1193Provider } from '@goodwidget/core'
import { ReserveSwapView } from './ReserveSwapView'
@@ -77,7 +77,7 @@ export function GoodReserveWidget({
mockState={mockState}
preferredChainId={preferredChainId}
/>
-
+
)
diff --git a/packages/governance-widget/src/GovernanceOnboardingWidget.tsx b/packages/governance-widget/src/GovernanceOnboardingWidget.tsx
index 36fe0989..fc6a651e 100644
--- a/packages/governance-widget/src/GovernanceOnboardingWidget.tsx
+++ b/packages/governance-widget/src/GovernanceOnboardingWidget.tsx
@@ -1,6 +1,6 @@
import { useMemo } from 'react'
import { PageWizardProvider, WidgetVersion } from '@goodwidget/ui'
-import packageJson from '../package.json'
+import { version } from '../package.json'
import { GovernanceOnboardingFlow } from './onboarding/GovernanceOnboardingFlow'
import { DEFAULT_FINAL_ACTIONS, DEFAULT_TRANSACTION_STEPS, ONBOARDING_STEPS } from './onboarding/constants'
import { HOUSE_COPY } from './onboarding/copy'
@@ -64,7 +64,7 @@ export function GovernanceOnboardingWidget({
onFinalActionPress={onFinalActionPress}
dataTestId={dataTestId}
/>
-
+
)
}
diff --git a/packages/governance-widget/src/GovernanceWidget.tsx b/packages/governance-widget/src/GovernanceWidget.tsx
index cbe8b268..8abc6858 100644
--- a/packages/governance-widget/src/GovernanceWidget.tsx
+++ b/packages/governance-widget/src/GovernanceWidget.tsx
@@ -12,7 +12,7 @@ import {
XStack,
YStack,
} from '@goodwidget/ui'
-import packageJson from '../package.json'
+import { version } from '../package.json'
import { WidgetVersion } from '@goodwidget/ui'
import { useTheme } from 'tamagui'
import { AlignmentVotingProposalCard } from './AlignmentVotingProposalCard'
@@ -761,7 +761,7 @@ export function GovernanceWidget({
) : (
)}
-
+
)
}
diff --git a/packages/staking-migration-widget/src/StakingMigrationWidget.tsx b/packages/staking-migration-widget/src/StakingMigrationWidget.tsx
index 28fd9e22..dc8600b3 100644
--- a/packages/staking-migration-widget/src/StakingMigrationWidget.tsx
+++ b/packages/staking-migration-widget/src/StakingMigrationWidget.tsx
@@ -2,7 +2,7 @@ import React, { useCallback, useMemo } from 'react'
import { GoodWidgetProvider } from '@goodwidget/core'
import type { EIP1193Provider } from '@goodwidget/core'
import { Text, ToastContainer, YStack } from '@goodwidget/ui'
-import packageJson from '../package.json'
+import { version } from '../package.json'
import { WidgetVersion } from '@goodwidget/ui'
import { MigrationProgressTimeline } from './MigrationProgressTimeline'
import { MigrationSummaryCard } from './MigrationSummaryCard'
@@ -157,7 +157,7 @@ export function StakingMigrationWidget({
adapterFactory={adapterFactory}
/>
-
+
)
}
diff --git a/packages/streaming-widget/src/StreamingWidget.tsx b/packages/streaming-widget/src/StreamingWidget.tsx
index 5e9d2c1a..63c13bcf 100644
--- a/packages/streaming-widget/src/StreamingWidget.tsx
+++ b/packages/streaming-widget/src/StreamingWidget.tsx
@@ -2,7 +2,7 @@ import React from 'react'
import { GoodWidgetProvider } from '@goodwidget/core'
import type { EIP1193Provider } from '@goodwidget/core'
import { ToastContainer } from '@goodwidget/ui'
-import packageJson from '../package.json'
+import { version } from '../package.json'
import { WidgetVersion } from '@goodwidget/ui'
import { useStreamingAdapter } from './adapter'
import { StreamingWidgetView } from './components/StreamingWidgetView'
@@ -49,7 +49,7 @@ export function StreamingWidgetPreview({
initialStreamsFormOpen={initialStreamsFormOpen}
/>
-
+
)
}
@@ -71,7 +71,7 @@ export function StreamingWidget({
>
-
+
)
}
diff --git a/packages/superfluid-campaign-widget/src/SuperfluidCampaignWidget.tsx b/packages/superfluid-campaign-widget/src/SuperfluidCampaignWidget.tsx
index 4c9668f4..8bb42208 100644
--- a/packages/superfluid-campaign-widget/src/SuperfluidCampaignWidget.tsx
+++ b/packages/superfluid-campaign-widget/src/SuperfluidCampaignWidget.tsx
@@ -3,7 +3,7 @@ import { GoodWidgetProvider, useWallet } from '@goodwidget/core'
import type { EIP1193Provider } from '@goodwidget/core'
import { CitizenClaimWidget } from '@goodwidget/citizen-claim-widget'
import { Card, Heading, Text, ToastContainer, YStack } from '@goodwidget/ui'
-import packageJson from '../package.json'
+import { version } from '../package.json'
import { WidgetVersion } from '@goodwidget/ui'
import { CampaignHeader } from './components/CampaignHeader'
import { FaqAccordion } from './components/FaqAccordion'
@@ -310,7 +310,7 @@ export function SuperfluidCampaignWidgetWithClient({
/>
-
+
)
}
diff --git a/tests/widgets/ai-credits-widget/version.spec.ts b/tests/widgets/ai-credits-widget/version.spec.ts
new file mode 100644
index 00000000..8379a534
--- /dev/null
+++ b/tests/widgets/ai-credits-widget/version.spec.ts
@@ -0,0 +1,13 @@
+import { readFileSync } from 'node:fs'
+import { test, expect } from '@playwright/test'
+
+const { version } = JSON.parse(
+ readFileSync('packages/ai-credits-widget/package.json', 'utf-8'),
+) as { version: string }
+
+test('AiCreditsWidget displays its package version', async ({ page }) => {
+ await page.goto(
+ '/iframe.html?id=qa-aicreditswidget-runtime-fixtures--disconnected&viewMode=story',
+ )
+ await expect(page.getByTestId('widget-version')).toHaveText(`v${version}`, { timeout: 20_000 })
+})
diff --git a/tests/widgets/citizen-claim-widget/version.spec.ts b/tests/widgets/citizen-claim-widget/version.spec.ts
new file mode 100644
index 00000000..aac6b1f9
--- /dev/null
+++ b/tests/widgets/citizen-claim-widget/version.spec.ts
@@ -0,0 +1,13 @@
+import { readFileSync } from 'node:fs'
+import { test, expect } from '@playwright/test'
+
+const { version } = JSON.parse(
+ readFileSync('packages/citizen-claim-widget/package.json', 'utf-8'),
+) as { version: string }
+
+test('CitizenClaimWidget displays its package version', async ({ page }) => {
+ await page.goto(
+ '/iframe.html?id=qa-citizenclaimwidget-runtime-fixtures--custodial-local-fixture&viewMode=story',
+ )
+ await expect(page.getByTestId('widget-version')).toHaveText(`v${version}`, { timeout: 20_000 })
+})
diff --git a/tests/widgets/goodreserve-widget/version.spec.ts b/tests/widgets/goodreserve-widget/version.spec.ts
new file mode 100644
index 00000000..43dca4f9
--- /dev/null
+++ b/tests/widgets/goodreserve-widget/version.spec.ts
@@ -0,0 +1,11 @@
+import { readFileSync } from 'node:fs'
+import { test, expect } from '@playwright/test'
+
+const { version } = JSON.parse(
+ readFileSync('packages/goodreserve-widget/package.json', 'utf-8'),
+) as { version: string }
+
+test('GoodReserveWidget displays its package version', async ({ page }) => {
+ await page.goto('/iframe.html?id=widgets-goodreservewidget--interactive&viewMode=story')
+ await expect(page.getByTestId('widget-version')).toHaveText(`v${version}`, { timeout: 20_000 })
+})
diff --git a/tests/widgets/governance-widget/version.spec.ts b/tests/widgets/governance-widget/version.spec.ts
new file mode 100644
index 00000000..a0f68e2d
--- /dev/null
+++ b/tests/widgets/governance-widget/version.spec.ts
@@ -0,0 +1,13 @@
+import { readFileSync } from 'node:fs'
+import { test, expect } from '@playwright/test'
+
+const { version } = JSON.parse(
+ readFileSync('packages/governance-widget/package.json', 'utf-8'),
+) as { version: string }
+
+test('GovernanceWidget displays its package version', async ({ page }) => {
+ await page.goto(
+ '/iframe.html?id=qa-governancewidget-runtime-fixtures--disconnected-dashboard&viewMode=story',
+ )
+ await expect(page.getByTestId('widget-version')).toHaveText(`v${version}`, { timeout: 20_000 })
+})
diff --git a/tests/widgets/staking-migration-widget/version.spec.ts b/tests/widgets/staking-migration-widget/version.spec.ts
new file mode 100644
index 00000000..346f63f2
--- /dev/null
+++ b/tests/widgets/staking-migration-widget/version.spec.ts
@@ -0,0 +1,13 @@
+import { readFileSync } from 'node:fs'
+import { test, expect } from '@playwright/test'
+
+const { version } = JSON.parse(
+ readFileSync('packages/staking-migration-widget/package.json', 'utf-8'),
+) as { version: string }
+
+test('StakingMigrationWidget displays its package version', async ({ page }) => {
+ await page.goto(
+ '/iframe.html?id=qa-stakingmigrationwidget-runtime-fixtures--ready&viewMode=story',
+ )
+ await expect(page.getByTestId('widget-version')).toHaveText(`v${version}`, { timeout: 20_000 })
+})
diff --git a/tests/widgets/streaming-widget/version.spec.ts b/tests/widgets/streaming-widget/version.spec.ts
new file mode 100644
index 00000000..62f0ea56
--- /dev/null
+++ b/tests/widgets/streaming-widget/version.spec.ts
@@ -0,0 +1,13 @@
+import { readFileSync } from 'node:fs'
+import { test, expect } from '@playwright/test'
+
+const { version } = JSON.parse(readFileSync('packages/streaming-widget/package.json', 'utf-8')) as {
+ version: string
+}
+
+test('StreamingWidget displays its package version', async ({ page }) => {
+ await page.goto(
+ '/iframe.html?id=qa-streamingwidget-runtime-fixtures--populated-state&viewMode=story',
+ )
+ await expect(page.getByTestId('widget-version')).toHaveText(`v${version}`, { timeout: 20_000 })
+})
diff --git a/tests/widgets/superfluid-campaign-widget/version.spec.ts b/tests/widgets/superfluid-campaign-widget/version.spec.ts
new file mode 100644
index 00000000..77fd7323
--- /dev/null
+++ b/tests/widgets/superfluid-campaign-widget/version.spec.ts
@@ -0,0 +1,13 @@
+import { readFileSync } from 'node:fs'
+import { test, expect } from '@playwright/test'
+
+const { version } = JSON.parse(
+ readFileSync('packages/superfluid-campaign-widget/package.json', 'utf-8'),
+) as { version: string }
+
+test('SuperfluidCampaignWidget displays its package version', async ({ page }) => {
+ await page.goto(
+ '/iframe.html?id=qa-superfluidcampaignwidget-runtime-fixtures--no-wallet-content&viewMode=story',
+ )
+ await expect(page.getByTestId('widget-version')).toHaveText(`v${version}`, { timeout: 20_000 })
+})