Skip to content

Extract JSX from rgh-feature-descriptions into a Svelte component#9357

Closed
Copilot wants to merge 6 commits into
mainfrom
copilot/extract-jsx-to-svelte-component
Closed

Extract JSX from rgh-feature-descriptions into a Svelte component#9357
Copilot wants to merge 6 commits into
mainfrom
copilot/extract-jsx-to-svelte-component

Conversation

Copilot AI commented May 6, 2026

Copy link
Copy Markdown
Contributor

Moves all UI rendering logic out of rgh-feature-descriptions.tsx into a new rgh-feature-descriptions.svelte component. The .tsx file is reduced to feature registration and a single mount() call.

Since custom elements aren't available in content script context, the component is mounted imperatively via Svelte 5's mount() with anchor: infoBanner — a direct equivalent to the old infoBanner.before(...) pattern.

source/rgh-feature-descriptions.svelte (new, at source root — not inside source/features/ which only allows .tsx/.css/.gql)

  • Description box: feature ID, clipboard-copy icon, old names, {@html description}, Related issues / Report bug / See .tsx/.css file links, screenshot
  • Disabled banner: $effect checks hotfixes and options storage whenever id changes; calls createBanner() and createRghIssueLink() to build banner DOM nodes, rendered via <Dom element={...} />
  • No props — reads location.pathname via reactive $state, updated on every navigation via a navigate event listener; all URL-derived values (filename, id, meta, URLs) are $derived from pathname and recompute automatically
  • Entire render block guarded by {#if id} — renders nothing when the current URL doesn't match a feature file; the empty container is left behind
  • The feature URL regex lives entirely inside the Svelte component

source/helpers/react.svelte (new)

  • Minimal Svelte 5 wrapper for rendering octicons-plain-react function components inside Svelte templates
  • Used as <React use={CopyIcon} props={{className: '...'}} /> — renders the component via React.createElement(use, props) from dom-chef (consistent with how all other helpers in the codebase import React) and injects the result via {@html outerHTML}

source/helpers/dom.svelte (new)

  • Minimal Svelte 5 wrapper that accepts a pre-built DOM Element (e.g. the return value of createBanner()) and inserts it into the Svelte template
  • Used as <Dom element={createBanner(...)} /> — replaces an anchor <span> with the provided element via a use:action directive, and removes it on destroy
  • Allows the Svelte component to reuse all existing DOM-producing helpers without duplicating their logic

source/features/rgh-feature-descriptions.tsx (gutted)

  • Drops React, all icon imports, addDescription, getDisabledReason, addDisabledBanner, and the custom featureUrlRegex
  • add() becomes a one-liner: mount(FeatureDescriptions, { target: infoBanner.parentElement!, anchor: infoBanner })
  • include uses pageDetect.isSingleFile + pageDetect.isRepoTree instead of a custom regex — the URL narrowing is now handled inside the Svelte component

svelte.config.js

  • Removes global customElement: true — all existing .svelte files already declare their own tag via <svelte:options customElement={...}>, so behaviour is unchanged; the new component compiles as a plain Svelte component

Test URLs

Screenshot

@fregante fregante left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The reason why this would be beneficial is #6554

We can update the contents just by looking at the URL from within the svelte component

<svg class="octicon octicon-copy v-align-baseline" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" role="img" aria-hidden="true">
<path d="M0 6.75C0 5.784.784 5 1.75 5h1.5a.75.75 0 0 1 0 1.5h-1.5a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-1.5a.75.75 0 0 1 1.5 0v1.5A1.75 1.75 0 0 1 9.25 16h-7.5A1.75 1.75 0 0 1 0 14.25Z" />
<path d="M5 1.75C5 .784 5.784 0 6.75 0h7.5C15.216 0 16 .784 16 1.75v7.5A1.75 1.75 0 0 1 14.25 11h-7.5A1.75 1.75 0 0 1 5 9.25Zm1.75-.25a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-7.5a.25.25 0 0 0-.25-.25Z" />
</svg>

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We'd have to come up with a way to import octicons, this is one:

  1. create React.svelte
  2. use it as <React use={CopyIcon}/> in svelte

And the code would look like this, plus dom-chef inside this component

<script>
  import { onMount, onDestroy } from 'svelte';

  export let create;
  export let props = {};

  let container;
  let element;
  let mounted = false;

  function rerender() {
    const next = create(props);

    if (!(next instanceof HTMLElement)) {
      throw new Error('create() must return HTMLElement');
    }

    if (element) {
      container.replaceChild(next, element);
    } else {
      container.appendChild(next);
    }

    element = next;
  }

  onMount(() => {
    mounted = true;
    rerender();
  });

  $: if (mounted) {
    rerender();
  }

  onDestroy(() => {
    element?.remove();
  });
</script>

<div bind:this={container}></div>

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done in 811ca28. Created source/helpers/react.svelte — a minimal Svelte 5 wrapper that takes a use prop (the octicon component function) and injects its outerHTML via {@html}:

<React use={CopyIcon} props={{className: 'v-align-baseline'}} />
<React use={AlertIcon} props={{className: 'mr-0'}} />

I used {@html outerHTML} instead of the bind:this + direct DOM manipulation approach from the code example, since svelte/no-dom-manipulating disallows direct DOM mutation in Svelte components. The {@html} path is idiomatic Svelte and avoids the extra wrapper <div>.

Also moved the svelte component from source/features/source/rgh-feature-descriptions.svelte (the features directory only allows .tsx/.css/.gql) and removed the filename prop so the component reads location.pathname directly.

…eatures/, remove filename prop

Agent-Logs-Url: https://github.com/refined-github/refined-github/sessions/835d5082-0042-4695-8abe-af6d0e7f978f

Co-authored-by: fregante <1402241+fregante@users.noreply.github.com>
Copilot AI requested a review from fregante May 7, 2026 04:54
Comment thread source/helpers/react.svelte Outdated
Copilot AI requested a review from fregante May 7, 2026 05:21
@github-actions github-actions Bot changed the title Extract JSX from rgh-feature-descriptions into a Svelte component Extract JSX from rgh-feature-descriptions into a Svelte component May 7, 2026
@fregante

fregante commented May 7, 2026

Copy link
Copy Markdown
Member

@SunsetTechuila y u no liek

Comment thread source/rgh-feature-descriptions.svelte Outdated
Comment on lines +124 to +152
{#if disabledBanner}
<!-- Block and width classes required to avoid margin collapse -->
<div
class="flash mb-3 d-inline-block width-full"
class:flash-warn={disabledBanner.type !== 'hotfixed-old'}
>
<div class="d-sm-flex flex-items-center gap-2">
<div class="d-flex flex-auto flex-self-center flex-items-center gap-2">
{#if disabledBanner.type === 'hotfixed-old'}
<React use={InfoIcon} props={{className: 'mr-0'}} />
<span>This feature was disabled until version {disabledBanner.version} due to <a target="_blank" rel="noopener noreferrer" data-hovercard-type="issue" data-hovercard-url="https://github.com/refined-github/refined-github/issues/{disabledBanner.issue}/hovercard" href="https://github.com/refined-github/refined-github/issues/{disabledBanner.issue}">#{disabledBanner.issue}</a>.</span>
{:else if disabledBanner.type === 'hotfixed'}
<React use={AlertIcon} props={{className: 'mr-0'}} />
<span>This feature is disabled due to <a target="_blank" rel="noopener noreferrer" data-hovercard-type="issue" data-hovercard-url="https://github.com/refined-github/refined-github/issues/{disabledBanner.issue}/hovercard" href="https://github.com/refined-github/refined-github/issues/{disabledBanner.issue}">#{disabledBanner.issue}</a>.</span>
{:else}
<React use={AlertIcon} props={{className: 'mr-0'}} />
<span>You disabled this feature on GitHub.com.</span>
{/if}
</div>
{#if disabledBanner.type === 'disabled'}
<button
type="button"
class="flex-shrink-0 btn btn-sm ml-sm-3 mt-2 mt-sm-n2 mb-sm-n2 mr-sm-n1 flex-self-center"
onclick={openOptions}
>Refined GitHub Options</button>
{/if}
</div>
</div>
{/if}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think this is much worse than infoBanner.before(getDisabledReason()):

  • it's less readable
  • createBanner code is duplicated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It can definitely be improved/deduplicated. The inability to use existing components can be an issue, but I'm sure we can ask Svelte to just append an existing element and it would resolve the issue.

I will later ask copilot to create a similar PR, but using Preact instead. This would at least let us use octicons directly. I'm not married to any tool, just trying to find convenience

@fregante

fregante commented May 7, 2026

Copy link
Copy Markdown
Member

@copilot You're avoiding a lot of previously existing helpers like createBanner().

You can use these helpers to generate DOM elements and just append them in the svelte component as simply as possible. Do we need another component like React that accepts DOM nodes? Like <Dom use={createBanner()}/>?

Copilot AI commented May 7, 2026

Copy link
Copy Markdown
Contributor Author

@copilot You're avoiding a lot of previously existing helpers like createBanner().

You can use these helpers to generate DOM elements and just append them in the svelte component as simply as possibl...

Done in 862a050. Added source/helpers/dom.svelte — a <Dom element={...}/> wrapper that inserts a pre-built DOM node via a use:action directive. The banner section in rgh-feature-descriptions.svelte now delegates entirely to the existing createBanner() + createRghIssueLink() helpers:

{#if disabledBanner}
    <Dom element={getBannerElement(disabledBanner)} />
{/if}

where getBannerElement calls createBanner({ icon, classes, text, action? }) for each banner type.

@fregante

fregante commented May 7, 2026

Copy link
Copy Markdown
Member

@copilot now add a navigate event listener in the svelte component to update the state when the page changes.

If the page doesn't match any features, then it should not render anything (it's ok to leave the empty container behind)

You should also change the includes array in the tsx file to include "isrepotree" and "isrepofile", excluding "isRepoHome". Then the filename-specific regex will just be part of the svelte component.

Copilot AI commented May 7, 2026

Copy link
Copy Markdown
Contributor Author

@copilot now add a navigate event listener in the svelte component to update the state when the page changes.

If the page doesn't match any features, then it should not render anything (it's ok to...

Done in 5486b45.

Navigate listener$effect with globalThis.navigation.addEventListener('navigate', ...) updates let pathname = $state(location.pathname) on every page change. All URL-derived values (filename, id, meta, URLs, etc.) are $derived from pathname and re-compute automatically.

Non-feature pages render nothing — the entire template is wrapped in {#if id} where id is only defined when featureRegex matches pathname. On tree/file pages that aren't feature files the component stays mounted but renders nothing.

.tsx includes — replaced the custom featureUrlRegex with pageDetect.isSingleFile + pageDetect.isRepoTree. The URL narrowing is now handled entirely inside the Svelte component via the featureRegex.

@fregante

fregante commented May 7, 2026

Copy link
Copy Markdown
Member

POC only

@fregante fregante closed this May 7, 2026
@SunsetTechuila SunsetTechuila deleted the copilot/extract-jsx-to-svelte-component branch May 7, 2026 22:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

3 participants