mobux 0.6.0

A touch-friendly tmux web UI for unhinged people who run terminal sessions from their phone while walking the dog
import { Router, Route, Switch, Link, useLocation } from "wouter-preact";
import { useHashLocation } from "wouter-preact/use-hash-location";
import { HomePage } from "./pages/Home.jsx";
import { TerminalPage } from "./pages/Terminal.jsx";
import { SettingsPage } from "./pages/Settings.jsx";
import { InstallPage } from "./pages/Install.jsx";
import { HostPicker } from "./components/HostPicker.jsx";

// App shell. Wouter owns client-side routing for the SPA's own routes. The
// terminal page renders no chrome (full-screen island); the others get a slim
// nav so the skeleton is navigable while the migration is in progress.
export function App() {
  // Hash routing. The SPA is mounted under a sub-path (/static/spa/) parallel
  // to the existing Rust-rendered pages, so hash-based locations avoid needing
  // server-side history fallback and work identically in dev and prod.
  return (
    <Router hook={useHashLocation}>
      <Switch>
        {/* Terminal is a full-bleed island — no shell chrome around it. */}
        <Route path="/s/:host/:name">
          {(params) => <TerminalPage host={params.host} name={params.name} />}
        </Route>
        <Route path="/s/:name">
          {(params) => <TerminalPage name={params.name} />}
        </Route>

        {/* Everything else shares the shell. */}
        <Route>
          <Shell>
            <Switch>
              <Route path="/" component={HomePage} />
              <Route path="/settings" component={SettingsPage} />
              <Route path="/install" component={InstallPage} />
              <Route>
                <div class="settings-card">
                  <h2>Not found</h2>
                  <p>
                    No SPA route here yet. <Link href="/">Home</Link>
                  </p>
                </div>
              </Route>
            </Switch>
          </Shell>
        </Route>
      </Switch>
    </Router>
  );
}

// App-shell chrome. Two headers, both copied verbatim from the old Rust-rendered
// pages (src/main.rs) so the SPA wears the old UI's chrome with the new engine
// underneath; .app-header / .app-header h1 / .header-icon / .header-back come
// from web/static/style.css, so colors/spacing/typography match exactly.
//
//   • home/install/etc: the old render_index header — a `mobux` wordmark
//     (clicks home) + `⚙` gear. The only addition is the native <select> host
//     picker between them (the user-preferred replacement for the old popover).
//     No Home/Install text tabs — Install stays reachable via Settings.
//   • /settings: the old settings_page header — a `‹` back link + "settings".
function HomeHeader() {
  const [, navigate] = useLocation();
  return (
    <header class="app-header">
      <h1
        class="app-wordmark"
        role="link"
        tabindex="0"
        onClick={() => navigate("/")}
        onKeyDown={(e) => {
          if (e.key === "Enter" || e.key === " ") navigate("/");
        }}
      >
        mobux
      </h1>
      <HostPicker />
      <button
        class="header-icon header-icon-btn"
        type="button"
        aria-label="Settings"
        onClick={() => navigate("/settings")}
      ></button>
    </header>
  );
}

function SettingsHeader() {
  return (
    <header class="app-header">
      <Link href="/" class="header-back" aria-label="Back"></Link>
      <h1>settings</h1>
    </header>
  );
}

function Shell({ children }) {
  const [location] = useLocation();
  const onSettings = location === "/settings";
  return (
    <div class="spa-shell">
      {onSettings ? <SettingsHeader /> : <HomeHeader />}
      <main class="spa-main">{children}</main>
    </div>
  );
}