Skip to content

feat(site): add the tx landing page and publish it to GitHub Pages - #36

Merged
fx merged 4 commits into
mainfrom
feat/landing-page
Aug 11, 2026
Merged

feat(site): add the tx landing page and publish it to GitHub Pages#36
fx merged 4 commits into
mainfrom
feat/landing-page

Conversation

@fx

@fx fx commented Aug 11, 2026

Copy link
Copy Markdown
Owner

Adds a single-page site under site/ and a workflow that deploys it to tx.fx.gd.

Vite 7 + React 19 + @fx/ui. The page covers what tx is, installing it, marketplaces, updating, and the types-only plugin contract — with copyable command blocks and a light/dark toggle that has no flash on load.

Deployment

.github/workflows/pages.yml builds site/dist and deploys on pushes that touch site/. site/public/CNAME carries the custom domain into the artifact. Pages is already configured on this repo for tx.fx.gd with Actions as the source, and DNS is in place, so this goes live on merge.

@fx/ui resolves from GitHub Packages, which requires a token even for public packages, so site/.npmrc reads one from the environment — no secret is committed. The workflow prefers a PACKAGES_TOKEN secret and falls back to the run's GITHUB_TOKEN.

Why the @fx/ui pin is a preview build

1.0.0 cannot render this page. Its Button emits a ButtonProps with none of its members, so children and className are type errors; and its globals.css imports tw-animate-css without depending on it, so the stylesheet fails to resolve at all. Both were fixed upstream in fx/ui (#15, #16) and this pins the preview build carrying them. It moves to a release tag once one ships.

Repo-level changes

biome.json gains css.parser.tailwindDirectives so the root Biome run can parse @source, and its ignore globs become **/-prefixed so they cover the sub-project's node_modules and dist rather than only the top level. Without that, bun run check fails on site/.

REVIEW.md records why the dev server binds 0.0.0.0, since that reads as a finding without the container/tailnet context.

Verification

  • Root bun run lint, typecheck, and build pass against a clean export of this branch — 45 files linted, no fixes.
  • site: bun install --frozen-lockfile + bun run build reproduces from a clean checkout with CNAME in dist.
  • Rendered in a real browser and inspected via the DOM: console clean, oklch tokens and JetBrains Mono applied, light and dark both correct, copy button works.
  • Zero horizontal page overflow at 375px — the command blocks scroll inside their own container rather than widening the page.
  • Dev server reachable over the tailnet hostname (200) while an arbitrary Host header is rejected, confirming allowedHosts is doing its job.

Add a single-page site under site/ built with Vite, React, and @fx/ui,
covering what tx is, how to install it, marketplaces, updating, and the
types-only plugin contract. It ships a CNAME for tx.fx.gd and deploys
from a Pages workflow on pushes that touch the site.

@fx/ui resolves from GitHub Packages, which requires a token even for
public packages, so site/.npmrc reads one from the environment. The
workflow prefers a PACKAGES_TOKEN secret and falls back to the run's
GITHUB_TOKEN.

The pin is a preview build rather than a release: 1.0.0 emits a
ButtonProps with none of its members, and its globals.css imports
tw-animate-css without depending on it, so neither the components nor
the stylesheet are usable from it. Both are fixed upstream and carried
by this build.
Copilot AI lite review requested due to automatic review settings August 11, 2026 02:50

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Adds a new site/ subproject that serves as tx’s landing page and introduces a GitHub Actions workflow to build and deploy it to GitHub Pages (custom domain via CNAME). This fits the repo by providing an official marketing/docs entry point that links back into the existing CLI/docs/plugin system.

Changes:

  • Add a Vite + React site under site/ with theming, command-copy blocks, and content describing tx’s core concepts.
  • Add a Pages workflow that builds site/dist and deploys it when site/ changes.
  • Update repo-wide tooling/docs (Biome config, .gitignore, and REVIEW.md) to support the new subproject and document the dev-server networking rationale.

Reviewed changes

Copilot reviewed 14 out of 17 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
site/vite.config.ts Vite config with Tailwind plugin and tailnet-friendly dev server settings.
site/tsconfig.json Site TypeScript configuration for React/Vite build.
site/src/main.tsx React entrypoint wiring ThemeProvider and rendering App.
site/src/index.css Imports @fx/ui globals and configures Tailwind source scanning for UI dist.
site/src/components/ThemeToggle.tsx Theme toggle button using @fx/ui theme hook.
site/src/components/CommandBlock.tsx Copyable command block UI + clipboard fallback logic.
site/src/App.tsx Main landing page layout/content and navigation links to repo docs.
site/public/favicon.svg New site favicon.
site/public/CNAME Custom domain for GitHub Pages artifact.
site/package.json Site dependencies and build scripts.
site/index.html HTML shell including pre-hydration theme class toggle to avoid flash.
site/bun.lock Lockfile for reproducible site installs/builds.
site/.npmrc GitHub Packages registry config for @fx/ui installs via token env var.
REVIEW.md Documents why the site dev server binds 0.0.0.0 and keeps allowedHosts protection.
biome.json Update ignores for nested dist/node_modules and enable Tailwind directive parsing.
.gitignore Ignore Playwright MCP working directory.
.github/workflows/pages.yml Build + deploy workflow for GitHub Pages using Bun and Pages actions.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread site/src/components/CommandBlock.tsx
Comment thread site/src/components/CommandBlock.tsx Outdated
navigator.clipboard can reject inside a secure context when permission
is denied, not just be absent outside one. Route both cases through the
selection-based copy so the button only reports success when something
was actually copied.
@fx
fx requested a lite review from Copilot August 11, 2026 02:56
copy awaits the clipboard write, so the block can be gone by the time
it resolves. Starting the reset timer then leaves it running past the
effect cleanup that exists to clear it.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 14 out of 17 changed files in this pull request and generated 1 comment.

Suppressed comments (2)

site/vite.config.ts:13

  • server.allowedHosts only allows .ts.net. When allowedHosts is set, Vite will also reject localhost/127.0.0.1 Host headers, which makes local (non-tailnet) development fail. Consider allowing localhost explicitly while keeping the .ts.net suffix restriction.
    allowedHosts: [".ts.net"],

site/index.html:27

  • Accessing localStorage can throw (e.g., storage disabled / strict privacy modes). Since this script runs before React mounts, an exception here can prevent the page from rendering at all. Wrap localStorage.getItem in a try/catch and treat failures like null.
      (() => {
        const stored = localStorage.getItem("theme");
        const dark =
          stored === "dark" ||
          ((stored === null || stored === "system") &&

Comment thread .github/workflows/pages.yml
configure-pages exists to hand a base path to a static site generator,
and nothing in this workflow reads its outputs — Vite builds against the
site root because Pages serves it from a custom domain. Keeping the step
would mean granting the build job pages: write so a no-op could call the
Pages API.
@fx
fx requested a lite review from Copilot August 11, 2026 03:01

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 14 out of 17 changed files in this pull request and generated no new comments.

Suppressed comments (2)

site/src/components/CommandBlock.tsx:46

  • commands.map uses key={command}, which will produce duplicate React keys when the same command string appears more than once (and keys should be unique among siblings). Since CommandBlock is a reusable component, it should generate a guaranteed-unique key (e.g., include the index).
        {commands.map((command) => (
          <code key={command} className="block">
            <span className="select-none text-muted-foreground">$ </span>
            {command}
          </code>

site/index.html:27

  • The inline theme bootstrap script assumes localStorage is always available. In some browser/privacy configurations, localStorage.getItem(...) can throw (e.g., blocked storage), which would abort the script and potentially prevent the page from initializing. Wrap the storage access in a try/catch and fall back to the system preference when storage is unavailable.
      (() => {
        const stored = localStorage.getItem("theme");
        const dark =
          stored === "dark" ||
          ((stored === null || stored === "system") &&
            window.matchMedia("(prefers-color-scheme: dark)").matches);
        document.documentElement.classList.toggle("dark", dark);
      })();

@fx
fx merged commit 904da23 into main Aug 11, 2026
2 checks passed
@fx
fx deleted the feat/landing-page branch August 11, 2026 03:11
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