Skip to content

Conversation

@llanesluis
Copy link
Collaborator

@llanesluis llanesluis commented Nov 9, 2025

What does this PR do?

Uses the URL as the source of truth for custom preview tab. This way, exteral websites can add an "Open in tweakcn" button and redirect to tweakcn adding the preview URL to load in the preview.

Additions:

  • Open in tweakcn button, ready to copy & paste to be used in external websites to connnect to tweakcn custom preview feature.

Changes:

  • Structure of the Zustand store: still exists, when switching tabs, the preview URL is gone, only exists in the "custom tab", and it's reconstructed from the local storage value. When the URL is the soruce of truth, the local sotrage value is ignored, it's only used to reconstruct the URL param.
  • Input is now uncontrolled, it's a local state, ephimeral that syncs with the URL and Zustand store only on submit

Summary by CodeRabbit

  • New Features

    • Added an "Open in tweakcn" button to launch website previews directly in the TweakCN editor.
  • Refactor

    • Optimized state management for website preview functionality.

@vercel
Copy link

vercel bot commented Nov 9, 2025

@llanesluis is attempting to deploy a commit to the Vercel Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai
Copy link

coderabbitai bot commented Nov 9, 2025

Walkthrough

This PR refactors website preview state management by migrating input URL state from persisted store to local component state, introduces a new OpenInTweakcnButton component for linking to an external editor, updates the useWebsitePreview hook's public API, and removes inputUrl-related methods from the WebsitePreviewStore.

Changes

Cohort / File(s) Summary
State Management Refactoring
store/website-preview-store.ts
Removed inputUrl property and setInputUrl() method from WebsitePreviewStore interface; reset now only clears currentUrl.
Hook API Updates
hooks/use-website-preview.ts
Replaced persisted inputUrl with local inputValue state; added URL parameter synchronization via useQueryState; introduced loadUrlIntoIframe helper with cache-busting; public API now returns inputValue/setInputValue instead of inputUrl/setInputUrl.
Component State Renaming
components/dynamic-website-preview.tsx
Renamed internal state identifier from inputUrl/setInputUrl to inputValue/setInputValue throughout Controls component; updated input binding, reset logic, enter-key handling, and conditional rendering.
New Component
components/open-in-tweakcn-button.tsx
New React component rendering a Button wrapping an anchor link to TweakCN editor with URL-encoded query parameter; includes inline SVG icon, accessibility labels, and disabled styling support.

Sequence Diagram

sequenceDiagram
    participant User
    participant Controls
    participant Hook as useWebsitePreview
    participant URLParam as useQueryState
    participant IFrame
    
    User->>Controls: Enter custom URL + press Enter
    Controls->>Hook: setInputValue(url)
    Hook->>Hook: setInputValueHandler updates local state
    Hook->>Hook: Validation & normalization
    activate Hook
    Note over Hook: isCustomTab = true
    Hook->>URLParam: Update url param (custom tab)
    Hook->>Hook: currentUrl = normalized URL
    deactivate Hook
    Hook->>IFrame: loadUrlIntoIframe (cache buster)
    IFrame->>User: Preview updates
    
    User->>Controls: Click Reset
    Controls->>Hook: reset()
    Hook->>Hook: Clear inputValue
    Hook->>URLParam: Clear url param
    Hook->>Hook: currentUrl = ""
    IFrame->>User: Preview cleared
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • hooks/use-website-preview.ts requires careful attention to the new synchronization effect logic between inputValue, currentUrl, and URL parameters; validate that the isCustomTab derived state correctly determines when to persist URL params.
  • State management flow across store, hook, and component should be traced to ensure the local state migration doesn't introduce stale references or race conditions.
  • URL validation and normalization in the refactored loadUrl handler should be verified for edge cases (invalid URLs, empty strings, special characters).

Possibly related PRs

Poem

🐰 State hops from store to local state,
A button links to TweakCN's gate,
Input values sync and flow,
Cache busters make previews glow,
Local reign beats persistence's fate! 🌐

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'URL as source of truth for preview and "Open in tweakcn" button' directly captures the main architectural change: making URL the source of truth and introducing the 'Open in tweakcn' button, which aligns with all four modified files (the store refactor, hook state management, button component, and UI binding changes).
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 26028eb and 192ecfc.

📒 Files selected for processing (4)
  • components/dynamic-website-preview.tsx (4 hunks)
  • components/open-in-tweakcn-button.tsx (1 hunks)
  • hooks/use-website-preview.ts (4 hunks)
  • store/website-preview-store.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
components/open-in-tweakcn-button.tsx (1)
lib/utils.ts (1)
  • cn (6-8)
hooks/use-website-preview.ts (1)
store/website-preview-store.ts (1)
  • useWebsitePreviewStore (10-21)

Comment on lines +19 to +76
<a
href={openInTweakcnUrl}
target="_blank"
rel="noreferrer"
className={cn("gap-1", disabled && "pointer-events-none opacity-50")}
>
Open in{" "}
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 256 256"
className="size-5 text-current"
aria-hidden="true"
>
<rect width="256" height="256" fill="none" />

<line
x1="208"
y1="128"
x2="207.8"
y2="128.2"
stroke="currentColor"
stroke-linecap="round"
stroke-width="24"
/>
<line
x1="168.2"
y1="167.8"
x2="128"
y2="208"
stroke="currentColor"
stroke-linecap="round"
stroke-width="24"
/>

<line
x1="192"
y1="40"
x2="115.8"
y2="116.2"
stroke="currentColor"
stroke-linecap="round"
stroke-width="24"
/>
<line
x1="76.2"
y1="155.8"
x2="40"
y2="192"
stroke="currentColor"
stroke-linecap="round"
stroke-width="24"
/>

<circle cx="188" cy="148" r="24" fill="none" stroke="currentColor" stroke-width="24" />
<circle cx="96" cy="136" r="24" fill="none" stroke="currentColor" stroke-width="24" />
</svg>
<span className="sr-only">Open in tweakcn</span>
</a>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Disabled button still opens the link

When disabled is true we only add pointer-events-none, so the <a> keeps its href, stays focusable, and still opens via keyboard activation. Users relying on keyboard or assistive tech can’t trust the disabled state. Please strip the href (and adjust focus/ARIA) whenever the control is disabled. (fastbootstrap.com)

Apply this diff to neutralize the link when disabled:

-      <a
-        href={openInTweakcnUrl}
+      <a
+        href={disabled ? undefined : openInTweakcnUrl}
         target="_blank"
         rel="noreferrer"
-        className={cn("gap-1", disabled && "pointer-events-none opacity-50")}
+        aria-disabled={disabled ? "true" : undefined}
+        tabIndex={disabled ? -1 : undefined}
+        className={cn("gap-1", disabled && "pointer-events-none opacity-50")}
       >
📝 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.

Suggested change
<a
href={openInTweakcnUrl}
target="_blank"
rel="noreferrer"
className={cn("gap-1", disabled && "pointer-events-none opacity-50")}
>
Open in{" "}
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 256 256"
className="size-5 text-current"
aria-hidden="true"
>
<rect width="256" height="256" fill="none" />
<line
x1="208"
y1="128"
x2="207.8"
y2="128.2"
stroke="currentColor"
stroke-linecap="round"
stroke-width="24"
/>
<line
x1="168.2"
y1="167.8"
x2="128"
y2="208"
stroke="currentColor"
stroke-linecap="round"
stroke-width="24"
/>
<line
x1="192"
y1="40"
x2="115.8"
y2="116.2"
stroke="currentColor"
stroke-linecap="round"
stroke-width="24"
/>
<line
x1="76.2"
y1="155.8"
x2="40"
y2="192"
stroke="currentColor"
stroke-linecap="round"
stroke-width="24"
/>
<circle cx="188" cy="148" r="24" fill="none" stroke="currentColor" stroke-width="24" />
<circle cx="96" cy="136" r="24" fill="none" stroke="currentColor" stroke-width="24" />
</svg>
<span className="sr-only">Open in tweakcn</span>
</a>
<a
href={disabled ? undefined : openInTweakcnUrl}
target="_blank"
rel="noreferrer"
aria-disabled={disabled ? "true" : undefined}
tabIndex={disabled ? -1 : undefined}
className={cn("gap-1", disabled && "pointer-events-none opacity-50")}
>
Open in{" "}
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 256 256"
className="size-5 text-current"
aria-hidden="true"
>
<rect width="256" height="256" fill="none" />
<line
x1="208"
y1="128"
x2="207.8"
y2="128.2"
stroke="currentColor"
stroke-linecap="round"
stroke-width="24"
/>
<line
x1="168.2"
y1="167.8"
x2="128"
y2="208"
stroke="currentColor"
stroke-linecap="round"
stroke-width="24"
/>
<line
x1="192"
y1="40"
x2="115.8"
y2="116.2"
stroke="currentColor"
stroke-linecap="round"
stroke-width="24"
/>
<line
x1="76.2"
y1="155.8"
x2="40"
y2="192"
stroke="currentColor"
stroke-linecap="round"
stroke-width="24"
/>
<circle cx="188" cy="148" r="24" fill="none" stroke="currentColor" stroke-width="24" />
<circle cx="96" cy="136" r="24" fill="none" stroke="currentColor" stroke-width="24" />
</svg>
<span className="sr-only">Open in tweakcn</span>
</a>
🤖 Prompt for AI Agents
In components/open-in-tweakcn-button.tsx around lines 19 to 76, the anchor still
has an href when disabled so it remains focusable and can be activated by
keyboard; remove/omit the href when disabled (e.g., href={disabled ? undefined :
openInTweakcnUrl}), add aria-disabled="true" and tabIndex={disabled ? -1 : 0} so
assistive tech and keyboard users cannot activate it, and keep the visual
disabled styles (pointer-events-none/opacity-50) as-is.

@vercel
Copy link

vercel bot commented Nov 12, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
v0-jnsahaj-tweakcn-i1 Error Error Nov 12, 2025 7:05pm

@jnsahaj
Copy link
Owner

jnsahaj commented Nov 12, 2025

Should we advertise the "Open in tweakcn" button so people know about it?
Also maybe it should be installable via shadcn registry

@llanesluis
Copy link
Collaborator Author

Yes, I was actually gonna add it to the registry, I think that's the way.

For now I was just waiting to know what you thought about it. Also, not sure if tweakcn is part of the shadcn registry directory yet (@tweakcn/[registry-item]).

@jnsahaj
Copy link
Owner

jnsahaj commented Nov 12, 2025

It is!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants