Conversation
There was a problem hiding this comment.
Pull request overview
This PR introduces a new web-interface/ frontend application built with Vite + React, using TanStack Router file-based routing and Tailwind CSS for styling.
Changes:
- Adds Vite/TypeScript/ESLint/Prettier configuration and Bun tooling for the new web app.
- Implements TanStack Router setup with generated route tree and several starter routes.
- Adds initial UI components (Navbar, ThemeToggle, Header/Footer) and public assets/manifest.
Reviewed changes
Copilot reviewed 29 out of 34 changed files in this pull request and generated 12 comments.
Show a summary per file
| File | Description |
|---|---|
| web-interface/vite.config.ts | Vite config with TanStack router plugin, devtools, TS path support, Tailwind, React plugin. |
| web-interface/tsconfig.json | TypeScript compiler settings and path aliases for src/. |
| web-interface/package.json | App scripts, dependencies/devDependencies for Vite + React + TanStack + Tailwind + Vitest. |
| web-interface/src/main.tsx | Client entrypoint creating and providing the router. |
| web-interface/src/router.tsx | Alternate router factory + module augmentation (currently unused). |
| web-interface/src/routeTree.gen.ts | Generated TanStack Router route tree/types. |
| web-interface/src/routes/* | Starter route components for /, /about, /dashboard, /machines, /jobs, /profile, and root layout. |
| web-interface/src/components/* | Navbar + Theme toggle + template header/footer components. |
| web-interface/src/styles.css | Tailwind v4 directives and theme tokens / CSS variables (currently mostly commented). |
| web-interface/.prettierignore | Prettier ignore list (needs additions for generated/lock files). |
| web-interface/eslint.config.js | ESLint configuration based on @tanstack/eslint-config. |
| web-interface/README.md | Setup docs (currently describes TanStack Start features not present in deps). |
| web-interface/public/* | Manifest, robots, icons and favicon assets. |
| web-interface/.vscode/settings.json | Excludes generated route tree from watchers/search and marks it read-only. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 25 out of 30 changed files in this pull request and generated 12 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| @@ -0,0 +1,30 @@ | |||
| { | |||
| "include": ["**/*.ts", "**/*.tsx", "eslint.config.js", "prettier.config.js", "vite.config.js"], | |||
There was a problem hiding this comment.
tsconfig.json includes vite.config.js, but the config file added in this PR is vite.config.ts. This can prevent the Vite config from being type-checked by TS tooling. Update the include entry to match the actual filename (and consider removing the extra trailing whitespace line).
| "include": ["**/*.ts", "**/*.tsx", "eslint.config.js", "prettier.config.js", "vite.config.js"], | |
| "include": ["**/*.ts", "**/*.tsx", "eslint.config.js", "prettier.config.js", "vite.config.ts"], |
|
|
||
| declare module '@tanstack/react-router' { | ||
| interface Register { | ||
| router: ReturnType<typeof getRouter> | ||
| } | ||
| } |
There was a problem hiding this comment.
This file declares Register.router as ReturnType<typeof getRouter>, but src/main.tsx also declares Register.router as typeof router. These conflicting module augmentations will cause a TypeScript error. Remove one augmentation and/or consolidate router creation so there is exactly one Register.router type.
| declare module '@tanstack/react-router' { | |
| interface Register { | |
| router: ReturnType<typeof getRouter> | |
| } | |
| } |
| "dependencies": { | ||
| "@tailwindcss/vite": "^4.1.18", | ||
| "@tanstack/react-devtools": "latest", | ||
| "@tanstack/react-router": "latest", | ||
| "@tanstack/react-router-devtools": "latest", | ||
| "@tanstack/router-plugin": "^1.132.0", | ||
| "lucide-react": "^0.545.0", | ||
| "react": "^19.2.0", | ||
| "react-dom": "^19.2.0", | ||
| "tailwindcss": "^4.1.18" | ||
| }, | ||
| "devDependencies": { | ||
| "@tailwindcss/typography": "^0.5.16", | ||
| "@tanstack/devtools-vite": "latest", | ||
| "@tanstack/eslint-config": "latest", | ||
| "@tanstack/router-plugin": "latest", | ||
| "@testing-library/dom": "^10.4.1", | ||
| "@testing-library/react": "^16.3.0", |
There was a problem hiding this comment.
@tanstack/router-plugin is listed in both dependencies and devDependencies (with different version specifiers). This can lead to duplicate installs / resolution conflicts. Keep it in only one place (typically devDependencies for a Vite plugin) and use a single version range.
| trailingComma: "all", | ||
| }; | ||
|
|
||
| export default config; |
There was a problem hiding this comment.
This Prettier config sets semi: false and singleQuote: true, but the config file itself uses semicolons and double quotes. prettier --check . will flag this file as unformatted. Reformat this file with the configured Prettier settings (and keep quote style consistent).
| trailingComma: "all", | |
| }; | |
| export default config; | |
| trailingComma: 'all', | |
| } | |
| export default config |
| username: string; | ||
| profilePicture: string; | ||
|
|
||
| } | ||
|
|
||
| export function getUser() : User { | ||
| // Return user information | ||
|
|
||
| // TODO: Call API - We use sample data for now | ||
| const user = { | ||
| username: "TheArchons", | ||
| profilePicture: "/sample-avatar.png" // real avatars should probably be stored in a bucket | ||
| }; | ||
|
|
||
| return user | ||
| } | ||
|
|
||
| export function logout() { | ||
| // TODO: Call API - we only log a message for now | ||
| console.log("logout called"); |
There was a problem hiding this comment.
This file is not formatted according to the Prettier settings added in this PR (semi: false, singleQuote: true). As-is, prettier --check . will fail. Run the formatter (or update the file) so quotes/semicolons/spacing match the configured style.
| username: string; | |
| profilePicture: string; | |
| } | |
| export function getUser() : User { | |
| // Return user information | |
| // TODO: Call API - We use sample data for now | |
| const user = { | |
| username: "TheArchons", | |
| profilePicture: "/sample-avatar.png" // real avatars should probably be stored in a bucket | |
| }; | |
| return user | |
| } | |
| export function logout() { | |
| // TODO: Call API - we only log a message for now | |
| console.log("logout called"); | |
| username: string | |
| profilePicture: string | |
| } | |
| export function getUser(): User { | |
| // Return user information | |
| // TODO: Call API - We use sample data for now | |
| const user = { | |
| username: 'TheArchons', | |
| profilePicture: '/sample-avatar.png', // real avatars should probably be stored in a bucket | |
| } | |
| return user | |
| } | |
| export function logout() { | |
| // TODO: Call API - we only log a message for now | |
| console.log('logout called') |
| import {Link} from "@tanstack/react-router"; | ||
| import type {LinkProps} from "@tanstack/react-router"; | ||
| import {getUser, logout} from "#/util.ts"; | ||
| import {useState} from "react"; | ||
|
|
||
| type NavlinkProps = { | ||
| href: LinkProps['to']; | ||
| text: string; | ||
| } | ||
|
|
||
| function Navlink(props: NavlinkProps) { | ||
| return ( | ||
| <Link className="hover:text-main transition-colors duration-200 py-1 px-2 rounded-lg hover:bg-gray-100" to={props.href} inactiveProps={{ className: "text-gray" }}> | ||
| { props.text } | ||
| </Link> | ||
| ) | ||
| } | ||
|
|
||
| export default function Navbar() { | ||
| const [dropdown, setDropdown] = useState(false); | ||
| const user = getUser(); | ||
|
|
||
| return ( | ||
| <nav className="flex items-center text-lg border rounded-xl mx-auto mt-8 mb-4 px-8 py-2 w-fit gap-8"> | ||
| <div className="flex gap-8"> | ||
| <Navlink href="/dashboard" text="Dashboard"/> | ||
| <Navlink href="/machines" text="Machines"/> | ||
| <Navlink href="/jobs" text="Jobs"/> | ||
| </div> | ||
| <div className="relative"> | ||
| <div className="flex items-center cursor-pointer py-1 px-2 rounded-lg hover:bg-gray-100 transition-colors duration-200" onClick={() => setDropdown(!dropdown)}> | ||
| { user.username } | ||
| <svg className="w-5 h-5" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> | ||
| <path d="M12 15L7 10H17L12 15Z" fill="#1D1B20"/> | ||
| </svg> | ||
| <img src={ user.profilePicture } alt="Profile Picture" className="w-7 h-7 rounded-full"></img> | ||
| </div> | ||
| { dropdown && | ||
| <div className="absolute top-full right-0 border-1 rounded-b m-2 p-1 pr-3 pl-3 text-xl"> | ||
| <ul> | ||
| <li><Link to="/profile" className="block px-2 py-1 rounded hover:bg-gray-100 transition-colors duration-200">Profile</Link></li> | ||
| <li><button onClick={logout} className="block w-full text-left px-2 py-1 rounded hover:bg-gray-100 transition-colors duration-200">Logout</button></li> | ||
| </ul> | ||
| </div> | ||
| } |
There was a problem hiding this comment.
This file is not formatted according to the Prettier settings in this PR (semi: false, singleQuote: true). As-is, prettier --check . will fail. Please run Prettier (and consider relying on it for import spacing, quotes, and semicolons) before merging.
| import {Link} from "@tanstack/react-router"; | |
| import type {LinkProps} from "@tanstack/react-router"; | |
| import {getUser, logout} from "#/util.ts"; | |
| import {useState} from "react"; | |
| type NavlinkProps = { | |
| href: LinkProps['to']; | |
| text: string; | |
| } | |
| function Navlink(props: NavlinkProps) { | |
| return ( | |
| <Link className="hover:text-main transition-colors duration-200 py-1 px-2 rounded-lg hover:bg-gray-100" to={props.href} inactiveProps={{ className: "text-gray" }}> | |
| { props.text } | |
| </Link> | |
| ) | |
| } | |
| export default function Navbar() { | |
| const [dropdown, setDropdown] = useState(false); | |
| const user = getUser(); | |
| return ( | |
| <nav className="flex items-center text-lg border rounded-xl mx-auto mt-8 mb-4 px-8 py-2 w-fit gap-8"> | |
| <div className="flex gap-8"> | |
| <Navlink href="/dashboard" text="Dashboard"/> | |
| <Navlink href="/machines" text="Machines"/> | |
| <Navlink href="/jobs" text="Jobs"/> | |
| </div> | |
| <div className="relative"> | |
| <div className="flex items-center cursor-pointer py-1 px-2 rounded-lg hover:bg-gray-100 transition-colors duration-200" onClick={() => setDropdown(!dropdown)}> | |
| { user.username } | |
| <svg className="w-5 h-5" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> | |
| <path d="M12 15L7 10H17L12 15Z" fill="#1D1B20"/> | |
| </svg> | |
| <img src={ user.profilePicture } alt="Profile Picture" className="w-7 h-7 rounded-full"></img> | |
| </div> | |
| { dropdown && | |
| <div className="absolute top-full right-0 border-1 rounded-b m-2 p-1 pr-3 pl-3 text-xl"> | |
| <ul> | |
| <li><Link to="/profile" className="block px-2 py-1 rounded hover:bg-gray-100 transition-colors duration-200">Profile</Link></li> | |
| <li><button onClick={logout} className="block w-full text-left px-2 py-1 rounded hover:bg-gray-100 transition-colors duration-200">Logout</button></li> | |
| </ul> | |
| </div> | |
| } | |
| import { Link } from '@tanstack/react-router' | |
| import type { LinkProps } from '@tanstack/react-router' | |
| import { getUser, logout } from '#/util.ts' | |
| import { useState } from 'react' | |
| type NavlinkProps = { | |
| href: LinkProps['to'] | |
| text: string | |
| } | |
| function Navlink(props: NavlinkProps) { | |
| return ( | |
| <Link | |
| className="hover:text-main transition-colors duration-200 py-1 px-2 rounded-lg hover:bg-gray-100" | |
| to={props.href} | |
| inactiveProps={{ className: 'text-gray' }} | |
| > | |
| {props.text} | |
| </Link> | |
| ) | |
| } | |
| export default function Navbar() { | |
| const [dropdown, setDropdown] = useState(false) | |
| const user = getUser() | |
| return ( | |
| <nav className="flex items-center text-lg border rounded-xl mx-auto mt-8 mb-4 px-8 py-2 w-fit gap-8"> | |
| <div className="flex gap-8"> | |
| <Navlink href="/dashboard" text="Dashboard" /> | |
| <Navlink href="/machines" text="Machines" /> | |
| <Navlink href="/jobs" text="Jobs" /> | |
| </div> | |
| <div className="relative"> | |
| <div | |
| className="flex items-center cursor-pointer py-1 px-2 rounded-lg hover:bg-gray-100 transition-colors duration-200" | |
| onClick={() => setDropdown(!dropdown)} | |
| > | |
| {user.username} | |
| <svg | |
| className="w-5 h-5" | |
| viewBox="0 0 24 24" | |
| fill="none" | |
| xmlns="http://www.w3.org/2000/svg" | |
| > | |
| <path d="M12 15L7 10H17L12 15Z" fill="#1D1B20" /> | |
| </svg> | |
| <img | |
| src={user.profilePicture} | |
| alt="Profile Picture" | |
| className="w-7 h-7 rounded-full" | |
| ></img> | |
| </div> | |
| {dropdown && ( | |
| <div className="absolute top-full right-0 border-1 rounded-b m-2 p-1 pr-3 pl-3 text-xl"> | |
| <ul> | |
| <li> | |
| <Link | |
| to="/profile" | |
| className="block px-2 py-1 rounded hover:bg-gray-100 transition-colors duration-200" | |
| > | |
| Profile | |
| </Link> | |
| </li> | |
| <li> | |
| <button | |
| onClick={logout} | |
| className="block w-full text-left px-2 py-1 rounded hover:bg-gray-100 transition-colors duration-200" | |
| > | |
| Logout | |
| </button> | |
| </li> | |
| </ul> | |
| </div> | |
| )} |
| import { TanStackDevtools } from '@tanstack/react-devtools' | ||
|
|
||
| import '../styles.css' | ||
| import Navbar from "#/components/Navbar.tsx"; |
There was a problem hiding this comment.
This file is not formatted according to the Prettier settings in this PR (e.g., it uses double quotes + semicolons). prettier --check . will fail until it’s formatted with the configured style.
| import Navbar from "#/components/Navbar.tsx"; | |
| import Navbar from '#/components/Navbar.tsx' |
| diskUsage: string | ||
| cpuUtilization: string | ||
| networkIO: { down: string; up: string } | ||
| gpuHistory: number[][] // array of [hour, percentage] data points per GPU |
There was a problem hiding this comment.
gpuHistory is typed as number[][], but the inline comment describes it as an array of [hour, percentage] pairs. In this PR it’s actually an array of percentage series (see generateGpuData). Update the comment (or the type/shape) so it matches reality.
| gpuHistory: number[][] // array of [hour, percentage] data points per GPU | |
| gpuHistory: number[][] // array of per-GPU utilization percentage series over time |
No description provided.