πŸ“ commands/remote-setup/ Β· 02_interactive_setup_ui.md

Chapter 2: Interactive Setup UI

πŸ“„ commands/remote-setup/02_interactive_setup_ui.md

Chapter 2: Interactive Setup UI

In Chapter 1: Command Registration & Gating, we built the "Front Door" to our application. We learned how to register a command and guard it with security policies.

Now that the user has successfully entered, what do they see?

Motivation: The Friendly Wizard

Terminals can be scary places. Usually, when you run a command, you stare at a blinking cursor, hoping something is happening. For a complex setup process, this "silent treatment" is bad user experience.

We want to build a Guided Wizard that:

  1. Informs: Tells the user "I'm checking your credentials..."
  2. Asks: "Is it okay if I connect to GitHub?"
  3. Updates: "Great, uploading your token now..."

To do this in a terminal, we use React. Yes, the same tool used for websites can render text-based UIs in your terminal!

Key Concepts

1. The State Machine

Think of our setup process like a traffic light. It can only be in one "state" at a time.

2. React in the Terminal (Ink)

We use a library called Ink. It allows us to use React components like <Text>, <Box>, and hooks like useState. Instead of rendering HTML <div>s, it renders text lines in the console.


Building the UI Flow

We are working in remote-setup.tsx. Let's build this step-by-step.

Step 1: Defining the States

First, we define the possible phases of our journey. We also need a place to store data (like the GitHub token) once we find it.

type Step =
  | { name: 'checking' }
  | { name: 'confirm'; token: RedactedGithubToken }
  | { name: 'uploading' };

// Inside our Component:
const [step, setStep] = useState<Step>({ name: 'checking' });

Step 2: The "Checking" Logic

When the command starts, we immediately want to check if the user is logged in. We use useEffect for this. This acts like a generic "start up" function.

useEffect(() => {
  // Start the check immediately
  checkLoginState().then(async (result) => {
    if (result.status === 'has_gh_token') {
      // If we found a token, move to the CONFIRM screen
      setStep({ name: 'confirm', token: result.token });
    }
    // ... handle other error cases ...
  });
}, []);

Step 3: Rendering the "Checking" Screen

React allows us to decide what to show based on our state. If we are checking, we show a spinner.

if (step.name === 'checking') {
  // Renders a spinning animation
  return <LoadingState message="Checking login status…" />;
}

Step 4: Rendering the "Confirm" Screen

If the check succeeds, the state changes to confirm. Now we show a dialog box asking for permission.

// If we are in the 'confirm' step...
return (
  <Dialog title="Connect Claude to GitHub?" onCancel={handleCancel}>
    <Text>
      We need to connect your account to push code on your behalf.
    </Text>
    <Select
      options={[{ label: 'Continue', value: 'send' }]}
      onChange={(val) => val === 'send' ? handleConfirm(step.token) : handleCancel()}
    />
  </Dialog>
);

Step 5: Handling the Confirmation

When the user says "Continue", we move the state to uploading and send the data to the backend.

const handleConfirm = async (token: RedactedGithubToken) => {
  // 1. Update UI to show we are working
  setStep({ name: 'uploading' });

  // 2. Perform the actual upload (Backend API)
  const result = await importGithubToken(token);

  // 3. Finish the command
  onDone(`Connected as ${result.username}`);
};

Under the Hood: The Sequence

How does the data flow through this interactive system?

  1. Mount: The UI appears.
  2. Effect: The logical check runs automatically.
  3. Update: The logic tells the UI "I found a token".
  4. Interaction: The user presses "Enter" to confirm.
  5. Completion: The UI closes.
sequenceDiagram participant User participant UI as React Component participant Logic as Check Logic participant API as Backend API User->>UI: Runs Command UI->>UI: Show "Checking..." UI->>Logic: Run checkLoginState() Logic-->>UI: Result: Token Found UI->>UI: Set State: "Confirm" UI-->>User: Show "Connect to GitHub?" Dialog User->>UI: Selects "Continue" UI->>UI: Set State: "Uploading" UI->>API: Upload Token API-->>UI: Success UI-->>User: Exit Command

Implementation Deep Dive

Let's look at the Web component in remote-setup.tsx. This component orchestrates the entire experience.

The Entry Point

We export a function call that renders our component.

export async function call(onDone: LocalJSXCommandOnDone) {
  // Renders the Web component into the terminal
  return <Web onDone={onDone} />;
}

Handling Errors

What if the user isn't logged in? We handle that in our useEffect.

// Inside useEffect checkLoginState().then(...)
case 'gh_not_installed':
  const url = `${getCodeWebUrl()}/onboarding`;
  // Open their browser to help them
  await openBrowser(url);
  // Close the command with an error message
  onDone(`GitHub CLI not found. Please visit ${url}`);
  return;

Conclusion

We have successfully built a State-Driven UI.

However, the "Checking" phase relied on a function called checkLoginState to interact with the system's GitHub tools. How does that work?

Next Chapter: GitHub CLI Integration


Generated by Code IQ