### Initialize and start the development server Source: https://github.com/oxidecomputer/console/blob/main/CLAUDE.md Commands to install project dependencies and launch the mock-backed development server. ```shell npm install npm run dev ``` -------------------------------- ### Install Dependencies and Run Development Server Source: https://github.com/oxidecomputer/console/blob/main/README.md Commands to set up the development environment, including installing project dependencies, browser binaries for E2E testing, and launching the Vite development server with MSW mocking. ```bash npm install npx playwright install npm run dev ``` -------------------------------- ### Start Vite Dev Server for Dogfood Rack (Shell) Source: https://github.com/oxidecomputer/console/blob/main/README.md This command starts the console development server configured to connect to the dogfood rack. It requires being on the VPN and accessing the server via HTTPS. This setup is for testing against a specific production-like environment. ```shell npm run start:dogfood ``` -------------------------------- ### Start Vite Dev Server with Nexus API (Shell) Source: https://github.com/oxidecomputer/console/blob/main/README.md This command starts the console development server, proxying requests to a local Nexus API running on port 12220. It requires Nexus to be running and accessible at the specified address. This is useful for developing against a live backend. ```shell npm run start:nexus ``` -------------------------------- ### Implement API Mutation with Error Reset Source: https://github.com/oxidecomputer/console/blob/main/AGENTS.md Demonstrates how to wrap destructive actions with confirmation and ensure stale error states are cleared when a modal is dismissed. ```typescript const mutation = useApiMutation(deleteResource); const handleDismiss = () => { mutation.reset(); closeModal(); }; const handleConfirm = async () => { await confirmAction("Are you sure?"); mutation.mutate(id, { onSuccess: () => addToast("Deleted") }); }; ``` -------------------------------- ### Run end-to-end tests with filtering Source: https://github.com/oxidecomputer/console/blob/main/CLAUDE.md Example command to execute specific Playwright end-to-end tests by filtering for a file and a specific test name. ```shell npm run e2ec -- instance -g 'boot disk' ``` -------------------------------- ### Role Management Helpers Source: https://github.com/oxidecomputer/console/blob/main/AGENTS.md Helper functions for managing roles within the application, likely used for access control and permission management. ```typescript import * as roles from "app/api/roles"; // Example usage: const userRoles = roles.getUserRoles(userId); ``` -------------------------------- ### Start Nexus API and Populate Data (Shell) Source: https://github.com/oxidecomputer/console/blob/main/README.md This script initiates the Nexus API and populates it with fake data using tmux for managing multiple processes. It assumes the 'console' and 'omicron' projects are located side-by-side. This provides a complete local environment for testing. ```shell ../console/tools/start_api.sh ``` -------------------------------- ### Compare Current Changes Source: https://github.com/oxidecomputer/console/blob/main/test/visual/README.md Executes visual comparison tests against the existing baseline. Supports various modes including headless, headed, and interactive UI inspection. ```bash # Run visual comparison npm run visual:compare # Open UI mode to inspect differences interactively npm run visual:compare -- --ui # Run in headed mode (show browser) npm run visual:compare -- --headed ``` -------------------------------- ### Update Baseline Snapshots Source: https://github.com/oxidecomputer/console/blob/main/test/visual/README.md Updates the baseline snapshots to match the current state, used when visual changes are confirmed as intentional. ```bash npm run visual:compare -- --update-snapshots ``` -------------------------------- ### Data Transformation with Remeda Source: https://github.com/oxidecomputer/console/blob/main/AGENTS.md Illustrates using the `remeda` library (imported as `R`) for efficient sorting and data transformations. It provides a functional approach to common data manipulation tasks. ```typescript import * as R from "remeda"; interface Item { id: number; name: string; value: number; } const items: Item[] = [ { id: 1, name: "C", value: 30 }, { id: 2, name: "A", value: 10 }, { id: 3, name: "B", value: 20 }, ]; // Sort by name, then by value const sortedItems = R.sortBy(items, (x) => x.name, (x) => x.value); console.log(sortedItems); /* [ { id: 2, name: 'A', value: 10 }, { id: 3, name: 'B', value: 20 }, { id: 1, name: 'C', value: 30 } ] */ ``` -------------------------------- ### Lazy Load Routes Source: https://github.com/oxidecomputer/console/blob/main/AGENTS.md Configures application routes using lazy loading to ensure components remain tree-shakeable and loaders are converted to clientLoaders. ```typescript import { lazy } from "react"; export const route = { path: "/projects", lazy: () => import("./pages/ProjectsPage").then(convert) }; ``` -------------------------------- ### Generate Baseline Snapshots Source: https://github.com/oxidecomputer/console/blob/main/test/visual/README.md Generates visual baseline snapshots from a specified git revision. It checks out the revision into a temporary directory, runs snapshot tests, and copies the results to the current working directory. ```bash # From main (most common) npm run visual:baseline # Or from a specific commit hash npm run visual:baseline -- abc123 # Or from the previous commit npm run visual:baseline -- HEAD~1 ``` -------------------------------- ### Resource and IP Validation Functions Source: https://github.com/oxidecomputer/console/blob/main/AGENTS.md Provides functions for validating resource names, descriptions, and IP addresses or networks. These are essential for maintaining data integrity and security within the application. ```typescript import { validateName, validateDescription, validateIp, validateIpNet, } from "app/util/validation"; // Example usage: const isValidName = validateName("my-resource-name"); const isValidIp = validateIp("192.168.1.1"); ``` -------------------------------- ### Define Form with React Hook Form Source: https://github.com/oxidecomputer/console/blob/main/AGENTS.md Standard pattern for creating forms using shared shells and react-hook-form, emphasizing the transformation of form state to API request shape during submission. ```typescript const form = useForm({ defaultValues: { name: "" } }); const onSubmit = (data: FormValues) => { const payload = transformToApiShape(data); mutation.mutate(payload); }; return ( ); ``` -------------------------------- ### Type Safety with ts-pattern Exhaustive Matching Source: https://github.com/oxidecomputer/console/blob/main/AGENTS.md Demonstrates the use of ts-pattern for exhaustive matching on union types, ensuring all possible cases are handled in conditional logic. This enhances type safety and prevents runtime errors. ```typescript import { match } from "ts-pattern"; type Status = "pending" | "processing" | "completed" | "failed"; function handleStatus(status: Status) { return match(status) .with("pending", () => "Waiting to start") .with("processing", () => "Currently in progress") .with("completed", () => "Finished successfully") .with("failed", () => "An error occurred") .exhaustive(); // Ensures all cases are handled } ``` -------------------------------- ### Implement Form Fields with React Hook Form Source: https://context7.com/oxidecomputer/console/llms.txt Integrates React Hook Form with UI components to provide consistent validation and state management. The example demonstrates defining form values and using specialized field components like NameField and NumberField. ```typescript import { useForm } from 'react-hook-form' import { NameField } from '~/components/form/fields/NameField' import { DescriptionField } from '~/components/form/fields/DescriptionField' import { NumberField } from '~/components/form/fields/NumberField' import { ListboxField } from '~/components/form/fields/ListboxField' import { CheckboxField } from '~/components/form/fields/CheckboxField' type InstanceFormValues = { name: string description: string ncpus: number memory: number hostname: string startOnCreate: boolean } function InstanceForm() { const form = useForm({ defaultValues: { name: '', description: '', ncpus: 2, memory: 8, hostname: '', startOnCreate: true } }) return (
Start instance on create ) } ``` -------------------------------- ### Implement API Mutation with UI Feedback Source: https://github.com/oxidecomputer/console/blob/main/CLAUDE.md Demonstrates how to wrap destructive actions with confirmation, handle API mutations, and surface results via toasts. This pattern ensures consistent error handling and user feedback. ```typescript const mutation = useApiMutation(deleteResource, { onSuccess: () => addToast({ message: "Deleted successfully" }), }); const handleDelete = () => { confirmAction({ message: "Are you sure?", onConfirm: () => mutation.mutate(id), }); }; // In modal dismiss handler const handleDismiss = () => { mutation.reset(); closeModal(); }; ``` -------------------------------- ### Common Oxide Console npm Commands (Table) Source: https://github.com/oxidecomputer/console/blob/main/README.md A summary table of frequently used npm commands for the Oxide Console project. It covers running the development server, executing tests, linting, type checking, formatting, generating API clients, and serving the mock API. ```markdown | Command | Description | | ------------------------ | ---------------------------------------------------------------------------------- | | `npm run dev` | Run Vite dev server with mock API | | `npm test` | Vitest unit tests | | `npm run e2ec` | Run Playwright E2E tests in Chrome only | | `npm run lint` | ESLint | | `npm run tsc` | Check types | | `npm run ci` | Lint, tests (unit and e2e), and types | | `npm run fmt` | Format everything. Rarely necessary thanks to editor integration | | `npm run gen-api` | Generate API client (see [`docs/update-pinned-api.md`](docs/update-pinned-api.md)) | | `npm run start:mock-api` | Serve mock API on port 12220 | ``` -------------------------------- ### Create Paginated Lists with QueryTable Source: https://context7.com/oxidecomputer/console/llms.txt Utilizes the getListQFn helper to configure paginated API queries for the QueryTable component. This pattern simplifies rendering lists of resources while maintaining performance via prefetching. ```typescript import { getListQFn, api } from '@oxide/api' import { useQueryTable } from '~/table/QueryTable' import { useProjectSelector } from '~/hooks/use-params' function InstancesPage() { const { project } = useProjectSelector() const { table } = useQueryTable({ query: getListQFn(api.instanceList, { query: { project } }), columns: [ { header: 'Name', accessorKey: 'name' }, { header: 'State', accessorKey: 'runState' }, { header: 'Memory', accessorKey: 'memory' }, { header: 'CPUs', accessorKey: 'ncpus' }, ], emptyState: , }) return
{table}
} // In loader - prefetch first page export async function clientLoader({ params }: LoaderFunctionArgs) { const { project } = getProjectSelector(params) await queryClient.prefetchQuery( getListQFn(api.instanceList, { query: { project } }).optionsFn() ) return null } ``` -------------------------------- ### Generate URLs with Path Builder Source: https://context7.com/oxidecomputer/console/llms.txt Provides a centralized 'pb' object for generating type-checked URLs across the application. This prevents hardcoded strings and ensures consistency across project, instance, and system routes. ```typescript import { pb } from '~/util/path-builder' import { Link, useNavigate } from 'react-router' const navigate = useNavigate() navigate(pb.projects()) navigate(pb.projectsNew()) navigate(pb.project({ project: 'my-proj' })) navigate(pb.instances({ project: 'my-proj' })) navigate(pb.instancesNew({ project: 'my-proj' })) navigate(pb.instance({ project: 'my-proj', instance: 'db1' })) navigate(pb.vpcs({ project: 'my-proj' })) navigate(pb.vpcFirewallRules({ project: 'my-proj', vpc: 'default' })) navigate(pb.silos()) navigate(pb.ipPools()) Settings ``` -------------------------------- ### Type Safety with `satisfies` Operator Source: https://github.com/oxidecomputer/console/blob/main/AGENTS.md Demonstrates the use of the `satisfies` operator in TypeScript to assert the type of an expression without changing its runtime value. This is particularly useful for catching type errors in situations involving `any` or complex object structures. ```typescript interface UserProfile { name: string; age: number; isActive: boolean; } const profileData = { name: "Alice", age: 30, isActive: true, extraField: "should not be here" } satisfies UserProfile; // Accessing properties is type-safe: console.log(profileData.name); // Attempting to access a non-existent property would cause a compile-time error: // console.log(profileData.email); // Error: Property 'email' does not exist on type 'UserProfile'. // The `extraField` is ignored by the `satisfies` assertion, but if it were a required field in UserProfile and missing, it would be a compile-time error. ``` -------------------------------- ### Perform API Requests with Generated Client Source: https://context7.com/oxidecomputer/console/llms.txt Demonstrates how to execute direct API calls and prefetch data within route loaders using the type-safe generated client. This approach ensures data is ready before component rendering. ```typescript import { api, useApiMutation, q, queryClient } from '@oxide/api' // Direct API call (typically used in loaders) const instance = await api.instanceView({ path: { instance: 'my-instance' }, query: { project: 'my-project' } }) // Using the query helper for React Query integration const instanceQuery = q(api.instanceView, { path: { instance: 'my-instance' }, query: { project: 'my-project' } }) // Prefetch in a route loader export async function clientLoader({ params }: LoaderFunctionArgs) { await queryClient.prefetchQuery( q(api.instanceView, { path: { instance: params.instance! }, query: { project: params.project! } }) ) return null } ``` -------------------------------- ### Run End-to-End Tests with Playwright (Shell) Source: https://github.com/oxidecomputer/console/blob/main/README.md These commands execute end-to-end tests using Playwright. `npm run e2e` runs tests across multiple browsers, while `npm run e2ec` is a shortcut for running tests only in Chrome, which is faster for local development. The `-- --ui` flag enables Playwright's interactive UI mode for debugging. ```shell npm run e2e npm run e2ec npm run e2e -- --ui ``` -------------------------------- ### Form Submission and Transformation Source: https://github.com/oxidecomputer/console/blob/main/CLAUDE.md Standard approach for form handling using react-hook-form. Logic emphasizes transforming UI-specific state to API-compliant shapes within the onSubmit handler. ```typescript const { handleSubmit } = useForm({ defaultValues: { ... } }); const onSubmit = (data: FormSchema) => { const payload = transformToApi(data); mutation.mutate(payload); }; ``` -------------------------------- ### Implement Query Hooks and Mutations in React Components Source: https://context7.com/oxidecomputer/console/llms.txt Shows the usage of usePrefetchedQuery for data retrieval and useApiMutation for state-changing operations. These hooks integrate with the Oxide API to provide automatic error handling and cache invalidation. ```typescript import { usePrefetchedQuery, useApiMutation, q, queryClient } from '@oxide/api' import { useInstanceSelector } from '~/hooks/use-params' function InstancePage() { const { project, instance } = useInstanceSelector() // Query that expects data to be prefetched in loader const { data: instanceData } = usePrefetchedQuery( q(api.instanceView, { path: { instance }, query: { project } }) ) // Mutation with callbacks const startInstance = useApiMutation(api.instanceStart, { onSuccess: () => { queryClient.invalidateEndpoint('instanceView') addToast({ content: 'Instance starting', variant: 'success' }) }, onError: (error) => { addToast({ content: error.message, variant: 'error' }) } }) const handleStart = () => { startInstance.mutate({ path: { instance }, query: { project } }) } return (

{instanceData.name}

State: {instanceData.runState}

) } ``` -------------------------------- ### Create Dialogs with Modal Component Source: https://context7.com/oxidecomputer/console/llms.txt The Modal component provides a standardized dialog structure, including a title, body, and footer. It handles dismissal and action triggers, making it suitable for confirmation workflows. ```typescript import { Modal } from '~/ui/lib/Modal' import { useState } from 'react' function DeleteConfirmation({ instanceName, onConfirm, onCancel }) { const [isOpen, setIsOpen] = useState(true) return ( Are you sure you want to delete {instanceName}? This action cannot be undone. ) } ``` -------------------------------- ### Implement MSW API Handlers for Oxide Console Source: https://context7.com/oxidecomputer/console/llms.txt Defines mock API handlers using Mock Service Worker (MSW) to simulate backend behavior for projects and instances. It uses a local database store and provides CRUD-like operations for development and testing environments. ```typescript import { json, makeHandlers, type Json } from '~/api/__generated__/msw-handlers' import { db, lookup } from './db' export const handlers = makeHandlers({ projectList: ({ query, cookies }) => { const user = currentUser(cookies) const siloProjects = db.projects .filter((p) => p.silo_id === user.silo_id) return paginated(query, siloProjects) }, projectCreate: ({ body, cookies }) => { const user = currentUser(cookies) errIfExists(db.projects, { name: body.name, silo_id: user.silo_id }) const newProject = { id: uuid(), ...body, ...getTimestamps(), silo_id: user.silo_id, } db.projects.push(newProject) return json(newProject, { status: 201 }) }, instanceStart: ({ path, query }) => { const instance = lookup.instance({ ...path, ...query }) if (instance.run_state !== 'stopped' && instance.run_state !== 'failed') { throw 'Cannot start instance that is not stopped or failed' } instance.run_state = 'starting' return json(instance, { status: 202 }) }, }) ``` -------------------------------- ### Set Browser Session Cookie from Dogfood Rack (JavaScript) Source: https://github.com/oxidecomputer/console/blob/main/README.md This JavaScript snippet shows how to copy a session cookie from the dogfood rack's browser session and set it in the localhost development environment. This allows the local console to authenticate as if logged into the dogfood rack. The cookie is set for 'localhost' domain and root path. ```javascript document.cookie = 'session=d9b1a96e151092eb0ea08b1a0d8c4788441f1894;domain=localhost;path=/' ``` -------------------------------- ### Trigger Toast Notifications Source: https://context7.com/oxidecomputer/console/llms.txt Uses a Zustand-based store to trigger toast notifications throughout the application. Supports simple strings, variants, and custom React elements for flexible feedback. ```typescript import { addToast } from '~/stores/toast' addToast('Instance created successfully') addToast({ content: 'Failed to delete disk', variant: 'error' }) addToast({ content: ( Instance db1 is now running ), variant: 'success', timeout: 5000 }) const deleteInstance = useApiMutation(api.instanceDelete, { onSuccess: () => { addToast({ content: 'Instance deleted', variant: 'success' }) queryClient.invalidateEndpoint('instanceList') }, onError: (error) => { addToast({ content: error.message, variant: 'error' }) } }) ``` -------------------------------- ### Debug CI End-to-End Test Failures (Shell) Source: https://github.com/oxidecomputer/console/blob/main/README.md This script downloads the latest end-to-end test failures from CI and allows opening a Playwright trace viewer for debugging. It's a crucial tool for diagnosing issues that occur in the automated testing environment. ```shell ./tools/debug-ci-e2e-fail.sh ``` -------------------------------- ### Conditional Button State Management Source: https://github.com/oxidecomputer/console/blob/main/CLAUDE.md Pattern for managing button states by computing a disabled reason string. This improves discoverability by showing users why an action is unavailable rather than hiding it. ```typescript const disabledReason = !hasPermission ? "Insufficient permissions" : undefined; ``` -------------------------------- ### Perform E2E Testing with Playwright Source: https://context7.com/oxidecomputer/console/llms.txt Demonstrates an end-to-end test case for managing instance lifecycles, specifically stopping and deleting an instance. It verifies UI state transitions and interactions using custom test utilities. ```typescript import { expect, test, clickRowAction, expectRowVisible } from './utils' test('can stop and delete a running instance', async ({ page }) => { await page.goto('/projects/mock-project/instances') // Verify initial state await expectRowVisible(page.getByRole('table'), { name: 'db1', state: expect.stringContaining('running'), }) // Stop the instance via row action menu const row = page.getByRole('row', { name: 'db1', exact: false }) await row.getByRole('button', { name: 'Row actions' }).click() await page.getByRole('menuitem', { name: 'Stop' }).click() await page.getByRole('button', { name: 'Confirm' }).click() // Wait for state transitions await expectRowVisible(page.getByRole('table'), { name: 'db1', state: expect.stringContaining('stopping'), }) await expectRowVisible(page.getByRole('table'), { name: 'db1', state: expect.stringContaining('stopped'), }) // Now delete await clickRowAction(page, 'db1', 'Delete') await page.getByRole('button', { name: 'Confirm' }).click() await expect(row).toBeHidden() }) ``` -------------------------------- ### Implement Button Component with Variants and States Source: https://context7.com/oxidecomputer/console/llms.txt The Button component supports multiple visual variants, sizes, and interaction states like loading and disabled. It includes an optional tooltip for disabled states to provide user feedback. ```typescript import { Button } from '~/ui/lib/Button' // Primary button with loading state // Danger button for destructive actions // Disabled button with explanation tooltip // Icon-only button ``` -------------------------------- ### Display Data with Table Component Source: https://context7.com/oxidecomputer/console/llms.txt The Table component renders structured data with header and body sections. It supports empty states via TableEmptyBox and row selection states. ```typescript import { Table, TableEmptyBox } from '~/ui/lib/Table' import { EmptyMessage } from '~/ui/lib/EmptyMessage' function DiskTable({ disks }) { if (disks.length === 0) { return ( ) } return ( Name Size State {disks.map((disk) => ( {disk.name} {filesize(disk.size)} {disk.state.state} ))}
) } ```