feat: add post-deployment success panel with explorer link#1613
feat: add post-deployment success panel with explorer link#1613Dark-Brain07 wants to merge 1 commit intogenlayerlabs:mainfrom
Conversation
Closes genlayerlabs#1610 Added a new DeploymentSuccessPanel component that renders automatically upon successful contract deployment. It provides: - The deployed contract address (with quick-copy) - The deployment transaction hash (with quick-copy) - A View on Explorer button that deep-links to the transaction on the connected network - An Interact Now button that smoothly scrolls to the Read/Write methods - Additionally, the contract address is now displayed under the file name in the sidebar for deployed contracts.
📝 WalkthroughWalkthroughThis PR introduces a post-deployment success experience by adding a new Changes
Sequence DiagramsequenceDiagram
participant User
participant RunDebugView
participant WebSocket as WebSocket/Listener
participant contractsStore as Contracts Store
participant DeploymentSuccessPanel
User->>RunDebugView: Deploys contract
WebSocket->>contractsStore: deployed_contract event received
contractsStore->>contractsStore: addDeployedContract(contractId, address, deployTxHash)
contractsStore->>RunDebugView: deployedContracts updated
RunDebugView->>RunDebugView: Detects isDeployed transition (false→true)
RunDebugView->>RunDebugView: Looks up deployedContract & txHash
RunDebugView->>DeploymentSuccessPanel: Show panel with contractAddress & txHash
alt User action
User->>DeploymentSuccessPanel: Copy address or hash
DeploymentSuccessPanel->>User: Clipboard updated
else Explorer link
User->>DeploymentSuccessPanel: Click "View on Explorer"
DeploymentSuccessPanel->>WebSocket: Opens explorer URL in new tab
else Interact
User->>DeploymentSuccessPanel: Click "Interact Now"
DeploymentSuccessPanel->>RunDebugView: Emit interact event
RunDebugView->>RunDebugView: Scroll to Read/Write methods
else Dismiss
User->>DeploymentSuccessPanel: Click dismiss
DeploymentSuccessPanel->>RunDebugView: Emit close event
RunDebugView->>RunDebugView: Hide panel
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Suggested labels
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
frontend/src/hooks/useContractListener.ts (1)
22-27:⚠️ Potential issue | 🔴 CriticalCritical:
deployTxHashis silently dropped by the store and never persisted.The type
DeployedContractincludesdeployTxHash?:0x${string}``, anduseContractListener.tscorrectly passes it when calling `addDeployedContract`, but the store function at lines 106–121 of `frontend/src/stores/contracts.ts` only destructures `{ contractId, address, defaultState }` and constructs `newItem` without `deployTxHash`. As a result:
RunDebugView.vueline ~69 —deployedContract?.deployTxHashis alwaysundefined, sonewlyDeployedTxHashbecomes''.DeploymentSuccessPanel.vueline ~57 — the explorer link becomes${explorerUrl}/tx/(broken), and the copy button copies an empty string.The store function must be updated to destructure and persist
deployTxHashfor the success panel and explorer link to work.🛠️ Proposed fix in
frontend/src/stores/contracts.tsfunction addDeployedContract({ contractId, address, defaultState, + deployTxHash, }: DeployedContract): void { const index = deployedContracts.value.findIndex( (c) => c.contractId === contractId, ); - const newItem = { contractId, address, defaultState }; + const newItem = { contractId, address, defaultState, deployTxHash }; if (index === -1) { deployedContracts.value.push(newItem); } else { deployedContracts.value.splice(index, 1, newItem); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/hooks/useContractListener.ts` around lines 22 - 27, The store currently drops deployTxHash when handling added contracts: update the contracts store's addDeployedContract handler (the function that destructures { contractId, address, defaultState } and constructs newItem) to also destructure deployTxHash and include it on newItem so the DeployedContract type is honored; ensure the same key (deployTxHash) is persisted into the store's state so consumers like RunDebugView and DeploymentSuccessPanel receive the tx hash.
🧹 Nitpick comments (3)
frontend/src/views/Simulator/RunDebugView.vue (1)
64-65: Optional: tightennewlyDeployedTxHashtyping.
DeployedContract.deployTxHashis\0x${string}`; considerref<`0x${string}` | ''>('')(orref('')is fine if the panel prop staysstring`). Not a correctness issue today, but it would catch any future drift between store/panel typings.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/views/Simulator/RunDebugView.vue` around lines 64 - 65, Tighten the ref typing for the deployment hash: change the declaration of newlyDeployedTxHash to use a more specific generic so it matches DeployedContract.deployTxHash (e.g., ref<`0x${string}` | ''>('') or ref<string>('') if you prefer); update any consumers/props that read newlyDeployedTxHash to accept that union type so the component and store typings stay aligned (refer to newlyDeployedTxHash and DeployedContract.deployTxHash to locate places to update).frontend/src/components/Simulator/DeploymentSuccessPanel.vue (1)
36-50: Optional: truncate the displayed address/hash per spec.The issue mentions "truncated contract address and tx hash" for display; the panel currently renders both in full with
break-all. Consider using the existinguseShortAddress().shorten(...)helper for visual display while still copying the full value viaCopyTextButton.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/components/Simulator/DeploymentSuccessPanel.vue` around lines 36 - 50, Render shortened visuals for contractAddress and txHash using the existing useShortAddress().shorten helper while keeping the full values for copying: replace the displayed text inside the two <span> elements (currently showing {{ contractAddress }} and {{ txHash }}) with the shortened versions from useShortAddress().shorten(contractAddress) and useShortAddress().shorten(txHash), but keep CopyTextButton :text bound to the full contractAddress and txHash values so the clipboard gets the full strings; ensure you import/use the useShortAddress composable in the component and leave the copy buttons unchanged.frontend/src/components/Simulator/ContractItem.vue (1)
11-14: Optional: consolidate the twovueimports.Minor cleanup —
computedcan be merged into the existingvueimport on line 11.♻️ Proposed cleanup
-import { ref, onMounted, nextTick } from 'vue'; +import { ref, onMounted, nextTick, computed } from 'vue'; import { notify } from '@kyvg/vue3-notification'; -import { computed } from 'vue'; import { useShortAddress } from '@/hooks';🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/components/Simulator/ContractItem.vue` around lines 11 - 14, The import list unnecessarily imports from 'vue' twice; consolidate into a single import by merging computed into the existing import that already brings in ref, onMounted, and nextTick (i.e., replace the two separate imports with one: import { ref, onMounted, nextTick, computed } from 'vue'), leaving the other imports (notify and useShortAddress) unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@frontend/src/components/Simulator/DeploymentSuccessPanel.vue`:
- Around line 53-61: The explorer link currently renders when explorerUrl exists
but may create a broken URL if txHash is empty; update the v-if condition on the
<a> that constructs `${explorerUrl}/tx/${txHash}` to require a non-empty txHash
as well (e.g., change the conditional to check both explorerUrl and txHash) so
the link only renders when both explorerUrl and txHash are present, or
alternatively render the "not available" hint when txHash is falsy; locate the
anchor in DeploymentSuccessPanel.vue (the element using
:href="`${explorerUrl}/tx/${txHash}`") and apply the conditional change.
- Line 14: The explorer link currently always shows because getExplorerUrl()
falls back to localhost; update getExplorerUrl to accept (or read) the active
network identifier (VITE_GENLAYER_NETWORK) and return null/empty for 'localnet'
and 'studionet' so it does not produce a usable URL for local networks, then
change the computed property explorerUrl in DeploymentSuccessPanel.vue to call
getExplorerUrl(activeNetwork) (or rely on the updated behavior) and update the
template to guard with v-if="explorerUrl && txHash" and render a fallback text
"Explorer not available for local networks" when explorerUrl is falsy; ensure
references to getExplorerUrl, explorerUrl, txHash and the VITE_GENLAYER_NETWORK
env var are used so the logic only shows the "View on Explorer" button for
supported networks.
In `@frontend/src/views/Simulator/RunDebugView.vue`:
- Around line 67-80: The watcher that reacts to isDeployed and contract.id
currently opens the success panel even when the found deployedContract has no
deployTxHash (due to the store dropping it), because newlyDeployedTxHash is set
with a silent '' fallback; change the logic in the watcher (the function
watching [() => isDeployed.value, () => contract.value?.id]) to only set
showDeploymentSuccessPanel.value = true and assign newlyDeployedTxHash.value
when a deployedContract is found AND deployedContract.deployTxHash is a
non-empty string; otherwise ensure showDeploymentSuccessPanel.value = false and
newlyDeployedTxHash.value is cleared, and add a warning log (e.g., console.warn)
indicating the lookup miss (reference contractsStore.deployedContracts and
useContractListener note) so QA can see missing hashes.
---
Outside diff comments:
In `@frontend/src/hooks/useContractListener.ts`:
- Around line 22-27: The store currently drops deployTxHash when handling added
contracts: update the contracts store's addDeployedContract handler (the
function that destructures { contractId, address, defaultState } and constructs
newItem) to also destructure deployTxHash and include it on newItem so the
DeployedContract type is honored; ensure the same key (deployTxHash) is
persisted into the store's state so consumers like RunDebugView and
DeploymentSuccessPanel receive the tx hash.
---
Nitpick comments:
In `@frontend/src/components/Simulator/ContractItem.vue`:
- Around line 11-14: The import list unnecessarily imports from 'vue' twice;
consolidate into a single import by merging computed into the existing import
that already brings in ref, onMounted, and nextTick (i.e., replace the two
separate imports with one: import { ref, onMounted, nextTick, computed } from
'vue'), leaving the other imports (notify and useShortAddress) unchanged.
In `@frontend/src/components/Simulator/DeploymentSuccessPanel.vue`:
- Around line 36-50: Render shortened visuals for contractAddress and txHash
using the existing useShortAddress().shorten helper while keeping the full
values for copying: replace the displayed text inside the two <span> elements
(currently showing {{ contractAddress }} and {{ txHash }}) with the shortened
versions from useShortAddress().shorten(contractAddress) and
useShortAddress().shorten(txHash), but keep CopyTextButton :text bound to the
full contractAddress and txHash values so the clipboard gets the full strings;
ensure you import/use the useShortAddress composable in the component and leave
the copy buttons unchanged.
In `@frontend/src/views/Simulator/RunDebugView.vue`:
- Around line 64-65: Tighten the ref typing for the deployment hash: change the
declaration of newlyDeployedTxHash to use a more specific generic so it matches
DeployedContract.deployTxHash (e.g., ref<`0x${string}` | ''>('') or
ref<string>('') if you prefer); update any consumers/props that read
newlyDeployedTxHash to accept that union type so the component and store typings
stay aligned (refer to newlyDeployedTxHash and DeployedContract.deployTxHash to
locate places to update).
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: a1b3e995-0a69-48eb-aab3-4569e96b0e1e
📒 Files selected for processing (5)
frontend/src/components/Simulator/ContractItem.vuefrontend/src/components/Simulator/DeploymentSuccessPanel.vuefrontend/src/hooks/useContractListener.tsfrontend/src/types/store.tsfrontend/src/views/Simulator/RunDebugView.vue
|
|
||
| const emit = defineEmits(['close', 'interact']); | ||
|
|
||
| const explorerUrl = computed(() => getExplorerUrl()); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Confirm explorer URL helper has no local-network gating and inspect any network store/util that could drive the resolution.
fd -t f 'explorerUrl.ts' frontend/src -x cat {}
echo '---'
rg -nP '(VITE_GENLAYER_NETWORK|asimov|bradbury|studionet)' frontend/src --type=ts --type=vue -C2Repository: genlayerlabs/genlayer-studio
Length of output: 917
🏁 Script executed:
cat -n frontend/src/components/Simulator/DeploymentSuccessPanel.vueRepository: genlayerlabs/genlayer-studio
Length of output: 4149
🏁 Script executed:
fd -t f 'runtimeConfig.ts' frontend/src -x cat {}Repository: genlayerlabs/genlayer-studio
Length of output: 2093
🏁 Script executed:
rg -n 'network|Network' frontend/src/stores -t ts --max-count=20Repository: genlayerlabs/genlayer-studio
Length of output: 54
🏁 Script executed:
find frontend/src/stores -name '*[Ss]imulator*' -o -name '*[Nn]etwork*' | head -20Repository: genlayerlabs/genlayer-studio
Length of output: 54
🏁 Script executed:
rg -n 'SimulatorStore' frontend/src --type ts --type vue -l | head -5Repository: genlayerlabs/genlayer-studio
Length of output: 100
🏁 Script executed:
rg -n 'VITE_GENLAYER_NETWORK' frontend/src --type ts -C3Repository: genlayerlabs/genlayer-studio
Length of output: 1204
🏁 Script executed:
rg -n 'local.*network|studionet' frontend/src --type ts --type vue -i -C2Repository: genlayerlabs/genlayer-studio
Length of output: 100
🏁 Script executed:
cat -n frontend/src/hooks/useGenlayer.ts | head -60Repository: genlayerlabs/genlayer-studio
Length of output: 2142
🏁 Script executed:
rg -n 'chains\s*=' frontend/src/hooks -A 15Repository: genlayerlabs/genlayer-studio
Length of output: 54
🏁 Script executed:
rg -n 'localnet|studionet|explorer.*not.*available' frontend/src -iRepository: genlayerlabs/genlayer-studio
Length of output: 1289
🏁 Script executed:
rg -n 'github.com|gitlab.com|issue|#[0-9]+' frontend/src/components/Simulator/DeploymentSuccessPanel.vueRepository: genlayerlabs/genlayer-studio
Length of output: 54
🏁 Script executed:
git log --oneline -20 -- frontend/src/components/Simulator/DeploymentSuccessPanel.vue 2>/dev/null | head -10Repository: genlayerlabs/genlayer-studio
Length of output: 139
🏁 Script executed:
rg -n 'isStudio|is_studio|isstudio' frontend/src --type ts -C2Repository: genlayerlabs/genlayer-studio
Length of output: 1042
Major: explorer link is shown for local networks, contradicting the intended design.
getExplorerUrl() always returns a non-empty string — it falls back to http://localhost:3001 when neither VITE_EXPLORER_URL nor a *.genlayer.com host is present. Therefore v-if="explorerUrl" is always truthy, and on local/studionet the "View on Explorer" button will appear and link to http://localhost:3001/tx/....
The intended behavior (per component design) requires:
- Hide the explorer link for local/studionet deployments
- Show "Explorer not available for local networks" placeholder instead
The current implementation doesn't consult the active network at all. Although VITE_GENLAYER_NETWORK is available (providing 'localnet', 'studionet', 'testnetAsimov'), getExplorerUrl() derives the explorer purely from window.location.hostname, so a Studio session on studio.genlayer.com connected to local/studionet would still expose the wrong link.
Suggested fix:
- Update
getExplorerUrl()to accept or access the active network identifier and returnnull/''for local/studionet. - Guard the template with
v-if="explorerUrl && txHash"to also handle empty txHash cases. - Show "Explorer not available for local networks" as a fallback in the template.
Template sketch
<a
- v-if="explorerUrl"
+ v-if="explorerUrl && txHash"
:href="`${explorerUrl}/tx/${txHash}`"
target="_blank"
class="..."
>
<ExternalLink class="h-3.5 w-3.5" />
View on Explorer
</a>
+ <span
+ v-else
+ class="inline-flex items-center text-xs text-slate-500 dark:text-zinc-400"
+ >
+ Explorer not available for local networks
+ </span>Also applies to: 52-61
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@frontend/src/components/Simulator/DeploymentSuccessPanel.vue` at line 14, The
explorer link currently always shows because getExplorerUrl() falls back to
localhost; update getExplorerUrl to accept (or read) the active network
identifier (VITE_GENLAYER_NETWORK) and return null/empty for 'localnet' and
'studionet' so it does not produce a usable URL for local networks, then change
the computed property explorerUrl in DeploymentSuccessPanel.vue to call
getExplorerUrl(activeNetwork) (or rely on the updated behavior) and update the
template to guard with v-if="explorerUrl && txHash" and render a fallback text
"Explorer not available for local networks" when explorerUrl is falsy; ensure
references to getExplorerUrl, explorerUrl, txHash and the VITE_GENLAYER_NETWORK
env var are used so the logic only shows the "View on Explorer" button for
supported networks.
| <a | ||
| v-if="explorerUrl" | ||
| :href="`${explorerUrl}/tx/${txHash}`" | ||
| target="_blank" | ||
| class="inline-flex items-center gap-1.5 rounded-lg bg-white px-4 py-2 text-xs font-medium text-slate-700 shadow-sm ring-1 ring-inset ring-slate-300 transition-colors hover:bg-slate-50 dark:bg-zinc-800 dark:text-zinc-200 dark:ring-zinc-600 dark:hover:bg-zinc-700" | ||
| > | ||
| <ExternalLink class="h-3.5 w-3.5" /> | ||
| View on Explorer | ||
| </a> |
There was a problem hiding this comment.
Guard the explorer link against an empty txHash.
If txHash is ever empty (which is the current state — see the store/listener comment), the link resolves to ${explorerUrl}/tx/ and silently navigates to a broken page. Add && txHash to the v-if (or fall through to the "not available" hint suggested above).
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@frontend/src/components/Simulator/DeploymentSuccessPanel.vue` around lines 53
- 61, The explorer link currently renders when explorerUrl exists but may create
a broken URL if txHash is empty; update the v-if condition on the <a> that
constructs `${explorerUrl}/tx/${txHash}` to require a non-empty txHash as well
(e.g., change the conditional to check both explorerUrl and txHash) so the link
only renders when both explorerUrl and txHash are present, or alternatively
render the "not available" hint when txHash is falsy; locate the anchor in
DeploymentSuccessPanel.vue (the element using
:href="`${explorerUrl}/tx/${txHash}`") and apply the conditional change.
| watch( | ||
| [() => isDeployed.value, () => contract.value?.id], | ||
| ([newIsDeployed, newId], [oldIsDeployed, oldId]) => { | ||
| if (newIsDeployed && !oldIsDeployed && newId === oldId) { | ||
| showDeploymentSuccessPanel.value = true; | ||
| const deployedContract = contractsStore.deployedContracts.find( | ||
| (c) => c.contractId === newId, | ||
| ); | ||
| newlyDeployedTxHash.value = deployedContract?.deployTxHash || ''; | ||
| } else if (newId !== oldId) { | ||
| showDeploymentSuccessPanel.value = false; | ||
| } | ||
| }, | ||
| ); |
There was a problem hiding this comment.
Watcher swallows the missing-hash case behind an empty-string fallback.
newlyDeployedTxHash.value = deployedContract?.deployTxHash || ''; will silently set an empty hash whenever the lookup fails (which today is always, due to the store dropping deployTxHash — see comment in useContractListener.ts). The panel still opens, leading to an empty Transaction Hash row, an empty-clipboard copy, and a broken /tx/ explorer link.
Consider not opening the panel when no hash is available, or at least logging the lookup miss so it surfaces during QA:
🛡️ Defensive variant
- if (newIsDeployed && !oldIsDeployed && newId === oldId) {
- showDeploymentSuccessPanel.value = true;
- const deployedContract = contractsStore.deployedContracts.find(
- (c) => c.contractId === newId,
- );
- newlyDeployedTxHash.value = deployedContract?.deployTxHash || '';
- } else if (newId !== oldId) {
+ if (newIsDeployed && !oldIsDeployed && newId === oldId) {
+ const deployedContract = contractsStore.deployedContracts.find(
+ (c) => c.contractId === newId,
+ );
+ if (!deployedContract?.deployTxHash) {
+ // Avoid surfacing a panel with an empty tx hash / broken explorer link.
+ return;
+ }
+ newlyDeployedTxHash.value = deployedContract.deployTxHash;
+ showDeploymentSuccessPanel.value = true;
+ } else if (newId !== oldId) {
showDeploymentSuccessPanel.value = false;
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| watch( | |
| [() => isDeployed.value, () => contract.value?.id], | |
| ([newIsDeployed, newId], [oldIsDeployed, oldId]) => { | |
| if (newIsDeployed && !oldIsDeployed && newId === oldId) { | |
| showDeploymentSuccessPanel.value = true; | |
| const deployedContract = contractsStore.deployedContracts.find( | |
| (c) => c.contractId === newId, | |
| ); | |
| newlyDeployedTxHash.value = deployedContract?.deployTxHash || ''; | |
| } else if (newId !== oldId) { | |
| showDeploymentSuccessPanel.value = false; | |
| } | |
| }, | |
| ); | |
| watch( | |
| [() => isDeployed.value, () => contract.value?.id], | |
| ([newIsDeployed, newId], [oldIsDeployed, oldId]) => { | |
| if (newIsDeployed && !oldIsDeployed && newId === oldId) { | |
| const deployedContract = contractsStore.deployedContracts.find( | |
| (c) => c.contractId === newId, | |
| ); | |
| if (!deployedContract?.deployTxHash) { | |
| // Avoid surfacing a panel with an empty tx hash / broken explorer link. | |
| return; | |
| } | |
| newlyDeployedTxHash.value = deployedContract.deployTxHash; | |
| showDeploymentSuccessPanel.value = true; | |
| } else if (newId !== oldId) { | |
| showDeploymentSuccessPanel.value = false; | |
| } | |
| }, | |
| ); |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@frontend/src/views/Simulator/RunDebugView.vue` around lines 67 - 80, The
watcher that reacts to isDeployed and contract.id currently opens the success
panel even when the found deployedContract has no deployTxHash (due to the store
dropping it), because newlyDeployedTxHash is set with a silent '' fallback;
change the logic in the watcher (the function watching [() => isDeployed.value,
() => contract.value?.id]) to only set showDeploymentSuccessPanel.value = true
and assign newlyDeployedTxHash.value when a deployedContract is found AND
deployedContract.deployTxHash is a non-empty string; otherwise ensure
showDeploymentSuccessPanel.value = false and newlyDeployedTxHash.value is
cleared, and add a warning log (e.g., console.warn) indicating the lookup miss
(reference contractsStore.deployedContracts and useContractListener note) so QA
can see missing hashes.
Resolves #1610
What this does
This PR improves the post-deployment experience in GenLayer Studio by giving developers an immediate, clear path forward once a contract successfully deploys.
Previously, users had to manually find the Explorer, search for the transaction hash, and figure out how to interact with their contract. This creates a friction point, especially during onboarding.
This PR introduces a Deployment Success Panel that appears automatically when a deployment transaction reaches
FINALIZED+SUCCESS.Changes
DeploymentSuccessPanel.vueshows the deployed contract address and transaction hash with one-click copy buttons.useContractListenernow extracts and stores thedeployTxHashwhen a deployment finalizes, allowing the UI to link directly to the transaction.Acceptance Criteria Met
Testing
Run & Debugview once finalized.Interact Nowbutton smoothly scrolls to the method execution section.Filessidebar to see the shortened address under the contract file name.Summary by CodeRabbit