From bff94e0ab8b7b6485e80bb7f7c1410205126b2b7 Mon Sep 17 00:00:00 2001 From: David Crespo Date: Tue, 24 Mar 2026 19:56:41 -0500 Subject: [PATCH] link to API and CLI docs in forms --- AGENTS.md | 1 + app/forms/anti-affinity-group-create.tsx | 6 ++- app/forms/anti-affinity-group-edit.tsx | 6 ++- app/forms/disk-create.tsx | 2 +- app/forms/external-subnet-create.tsx | 6 ++- app/forms/external-subnet-edit.tsx | 6 ++- app/forms/fleet-access.tsx | 12 ++++- app/forms/floating-ip-create.tsx | 6 ++- app/forms/floating-ip-edit.tsx | 6 ++- app/forms/idp/edit.tsx | 5 +- app/forms/image-edit.tsx | 2 +- app/forms/image-from-snapshot.tsx | 6 ++- app/forms/image-upload.tsx | 6 ++- app/forms/ip-pool-create.tsx | 6 ++- app/forms/ip-pool-edit.tsx | 6 ++- app/forms/ip-pool-range-add.tsx | 6 ++- app/forms/network-interface-create.tsx | 6 ++- app/forms/network-interface-edit.tsx | 6 ++- app/forms/project-access.tsx | 12 ++++- app/forms/project-create.tsx | 6 ++- app/forms/project-edit.tsx | 6 ++- app/forms/silo-access.tsx | 12 ++++- app/forms/silo-create.tsx | 6 ++- app/forms/snapshot-create.tsx | 6 ++- app/forms/ssh-key-create.tsx | 6 ++- app/forms/ssh-key-edit.tsx | 6 ++- app/forms/subnet-create.tsx | 6 ++- app/forms/subnet-edit.tsx | 6 ++- app/forms/vpc-create.tsx | 2 +- app/forms/vpc-edit.tsx | 2 +- app/forms/vpc-router-create.tsx | 6 ++- app/forms/vpc-router-edit.tsx | 6 ++- app/forms/vpc-router-route-common.tsx | 8 ++- app/forms/vpc-router-route-create.tsx | 2 +- app/forms/vpc-router-route-edit.tsx | 2 +- .../project/disks/DiskDetailSideModal.tsx | 2 +- .../project/vpcs/internet-gateway-edit.tsx | 6 ++- app/ui/lib/ModalLinks.tsx | 50 ++++++++++++++++++- 38 files changed, 217 insertions(+), 41 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 5b34b807a..ce9df3784 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -5,6 +5,7 @@ - Before starting a feature, skim an existing page or form with similar behavior and mirror the conventions—this codebase is intentionally conventional. Look for similar pages in `app/pages` and forms in `app/forms` to use as templates. - `@oxide/api` is at `app/api` and `@oxide/api-mocks` is at `mock-api/index.ts`. - The language server often has out of date errors. tsgo is extremely fast, so confirm errors that come from the language server by running `npm run tsc` +- Format with `npm run fmt` (oxfmt). Never use prettier directly—it is not the project formatter. - Use Node.js 22+, then install deps and start the mock-backed dev server (skip if `npm run dev` is already running in another terminal): ```sh diff --git a/app/forms/anti-affinity-group-create.tsx b/app/forms/anti-affinity-group-create.tsx index dfaaaa037..38eb34a6f 100644 --- a/app/forms/anti-affinity-group-create.tsx +++ b/app/forms/anti-affinity-group-create.tsx @@ -79,7 +79,11 @@ export default function CreateAntiAffinityGroupForm() { { value: 'fail', label: 'Fail' }, ]} /> - + ) } diff --git a/app/forms/anti-affinity-group-edit.tsx b/app/forms/anti-affinity-group-edit.tsx index d238827f8..1c1c43b25 100644 --- a/app/forms/anti-affinity-group-edit.tsx +++ b/app/forms/anti-affinity-group-edit.tsx @@ -83,7 +83,11 @@ export default function EditAntiAffintyGroupForm() { > - + ) } diff --git a/app/forms/disk-create.tsx b/app/forms/disk-create.tsx index 53d42fb8f..23d3835a2 100644 --- a/app/forms/disk-create.tsx +++ b/app/forms/disk-create.tsx @@ -230,7 +230,7 @@ export function CreateDiskSideModalForm({ images={images} areImagesLoading={areImagesLoading} /> - + ) } diff --git a/app/forms/external-subnet-create.tsx b/app/forms/external-subnet-create.tsx index 790adf93f..9ffa57b75 100644 --- a/app/forms/external-subnet-create.tsx +++ b/app/forms/external-subnet-create.tsx @@ -153,7 +153,11 @@ export default function CreateExternalSubnetSideModalForm() { description="The subnet to reserve, e.g., 10.128.1.0/24" /> )} - + ) } diff --git a/app/forms/external-subnet-edit.tsx b/app/forms/external-subnet-edit.tsx index 0efa2622c..6b2c635fb 100644 --- a/app/forms/external-subnet-edit.tsx +++ b/app/forms/external-subnet-edit.tsx @@ -114,7 +114,11 @@ export default function EditExternalSubnetSideModalForm() { - + ) } diff --git a/app/forms/fleet-access.tsx b/app/forms/fleet-access.tsx index 018097f55..4df4413ed 100644 --- a/app/forms/fleet-access.tsx +++ b/app/forms/fleet-access.tsx @@ -77,7 +77,11 @@ export function FleetAccessAddUserSideModal({ control={form.control} /> - + ) } @@ -122,7 +126,11 @@ export function FleetAccessEditUserSideModal({ }} > - + ) } diff --git a/app/forms/floating-ip-create.tsx b/app/forms/floating-ip-create.tsx index b36b218fe..18fb35d6d 100644 --- a/app/forms/floating-ip-create.tsx +++ b/app/forms/floating-ip-create.tsx @@ -108,7 +108,11 @@ export default function CreateFloatingIpSideModalForm() { placeholder="Select a pool" noItemsPlaceholder="No pools available" /> - + ) } diff --git a/app/forms/floating-ip-edit.tsx b/app/forms/floating-ip-edit.tsx index 960f2e71e..678f81975 100644 --- a/app/forms/floating-ip-edit.tsx +++ b/app/forms/floating-ip-edit.tsx @@ -115,7 +115,11 @@ export default function EditFloatingIpSideModalForm() { - + ) } diff --git a/app/forms/idp/edit.tsx b/app/forms/idp/edit.tsx index 2f02e2723..65995ade6 100644 --- a/app/forms/idp/edit.tsx +++ b/app/forms/idp/edit.tsx @@ -117,7 +117,10 @@ export default function EditIdpSideModalForm() { control={form.control} disabled /> - + ) } diff --git a/app/forms/image-edit.tsx b/app/forms/image-edit.tsx index 2db4ca3f3..f69504cdb 100644 --- a/app/forms/image-edit.tsx +++ b/app/forms/image-edit.tsx @@ -60,7 +60,7 @@ export function EditImageSideModalForm({ - + ) } diff --git a/app/forms/image-from-snapshot.tsx b/app/forms/image-from-snapshot.tsx index 7c6f49acb..1be33e62c 100644 --- a/app/forms/image-from-snapshot.tsx +++ b/app/forms/image-from-snapshot.tsx @@ -102,7 +102,11 @@ export default function CreateImageFromSnapshotSideModalForm() { - + ) } diff --git a/app/forms/image-upload.tsx b/app/forms/image-upload.tsx index facc6b28e..ffa2dff9f 100644 --- a/app/forms/image-upload.tsx +++ b/app/forms/image-upload.tsx @@ -681,7 +681,11 @@ export default function ImageCreate() { /> )} - + ) } diff --git a/app/forms/ip-pool-create.tsx b/app/forms/ip-pool-create.tsx index 67bd8e9d3..500d852d9 100644 --- a/app/forms/ip-pool-create.tsx +++ b/app/forms/ip-pool-create.tsx @@ -89,7 +89,11 @@ export default function CreateIpPoolSideModalForm() { ]} /> */} - + ) } diff --git a/app/forms/ip-pool-edit.tsx b/app/forms/ip-pool-edit.tsx index c46c46521..35deeffd7 100644 --- a/app/forms/ip-pool-edit.tsx +++ b/app/forms/ip-pool-edit.tsx @@ -75,7 +75,11 @@ export default function EditIpPoolSideModalForm() { - + ) } diff --git a/app/forms/ip-pool-range-add.tsx b/app/forms/ip-pool-range-add.tsx index aa6a75097..91f3faaaf 100644 --- a/app/forms/ip-pool-range-add.tsx +++ b/app/forms/ip-pool-range-add.tsx @@ -138,7 +138,11 @@ export default function IpPoolAddRange() { control={form.control} required /> - + ) } diff --git a/app/forms/network-interface-create.tsx b/app/forms/network-interface-create.tsx index 0694e5f29..e8aa4703c 100644 --- a/app/forms/network-interface-create.tsx +++ b/app/forms/network-interface-create.tsx @@ -180,7 +180,11 @@ export function CreateNetworkInterfaceForm({ placeholder="Leave blank for auto-assignment" /> )} - + ) } diff --git a/app/forms/network-interface-edit.tsx b/app/forms/network-interface-edit.tsx index 8bd206ccd..f64af5983 100644 --- a/app/forms/network-interface-edit.tsx +++ b/app/forms/network-interface-edit.tsx @@ -178,7 +178,11 @@ export function EditNetworkInterfaceForm({ variant="info" content={`This network interface supports ${supportedVersions} transit IPs.`} /> - + ) } diff --git a/app/forms/project-access.tsx b/app/forms/project-access.tsx index 15566bc56..7e1ea5033 100644 --- a/app/forms/project-access.tsx +++ b/app/forms/project-access.tsx @@ -76,7 +76,11 @@ export function ProjectAccessAddUserSideModal({ onDismiss, policy }: AddRoleModa control={form.control} /> - + ) } @@ -123,7 +127,11 @@ export function ProjectAccessEditUserSideModal({ onDismiss={onDismiss} > - + ) } diff --git a/app/forms/project-create.tsx b/app/forms/project-create.tsx index 46c0fec48..7a33c4182 100644 --- a/app/forms/project-create.tsx +++ b/app/forms/project-create.tsx @@ -61,7 +61,11 @@ export default function ProjectCreateSideModalForm() { > - + ) } diff --git a/app/forms/project-edit.tsx b/app/forms/project-edit.tsx index 43144736a..634834b83 100644 --- a/app/forms/project-edit.tsx +++ b/app/forms/project-edit.tsx @@ -70,7 +70,11 @@ export default function EditProjectSideModalForm() { > - + ) } diff --git a/app/forms/silo-access.tsx b/app/forms/silo-access.tsx index 6bc711230..c375f3f93 100644 --- a/app/forms/silo-access.tsx +++ b/app/forms/silo-access.tsx @@ -73,7 +73,11 @@ export function SiloAccessAddUserSideModal({ onDismiss, policy }: AddRoleModalPr control={form.control} /> - + ) } @@ -118,7 +122,11 @@ export function SiloAccessEditUserSideModal({ }} > - + ) } diff --git a/app/forms/silo-create.tsx b/app/forms/silo-create.tsx index de7e01223..05c694f9c 100644 --- a/app/forms/silo-create.tsx +++ b/app/forms/silo-create.tsx @@ -183,7 +183,11 @@ export default function CreateSiloSideModalForm() { - + ) } diff --git a/app/forms/snapshot-create.tsx b/app/forms/snapshot-create.tsx index 8079c59f3..ef7634ff0 100644 --- a/app/forms/snapshot-create.tsx +++ b/app/forms/snapshot-create.tsx @@ -90,7 +90,11 @@ export default function SnapshotCreate() { required control={form.control} /> - + ) } diff --git a/app/forms/ssh-key-create.tsx b/app/forms/ssh-key-create.tsx index 2c3d27626..8437035a9 100644 --- a/app/forms/ssh-key-create.tsx +++ b/app/forms/ssh-key-create.tsx @@ -68,7 +68,11 @@ export function SSHKeyCreate({ onDismiss, message }: Props) { control={form.control} /> {message} - + ) } diff --git a/app/forms/ssh-key-edit.tsx b/app/forms/ssh-key-edit.tsx index 77408c1ee..c9d280433 100644 --- a/app/forms/ssh-key-edit.tsx +++ b/app/forms/ssh-key-edit.tsx @@ -74,7 +74,11 @@ export default function EditSSHKeySideModalForm() { disabled /> - + ) } diff --git a/app/forms/subnet-create.tsx b/app/forms/subnet-create.tsx index ddfe14501..de55d8b68 100644 --- a/app/forms/subnet-create.tsx +++ b/app/forms/subnet-create.tsx @@ -96,7 +96,11 @@ export default function CreateSubnetForm() { control={form.control} required /> - + ) } diff --git a/app/forms/subnet-edit.tsx b/app/forms/subnet-edit.tsx index 8b5bcb61d..2f5035f5c 100644 --- a/app/forms/subnet-edit.tsx +++ b/app/forms/subnet-edit.tsx @@ -107,7 +107,11 @@ export default function EditSubnetForm() { control={form.control} required /> - + ) } diff --git a/app/forms/vpc-create.tsx b/app/forms/vpc-create.tsx index 8a22867ba..1692a2e22 100644 --- a/app/forms/vpc-create.tsx +++ b/app/forms/vpc-create.tsx @@ -65,7 +65,7 @@ export default function CreateVpcSideModalForm() { - + ) } diff --git a/app/forms/vpc-edit.tsx b/app/forms/vpc-edit.tsx index a2e5c60e1..991539937 100644 --- a/app/forms/vpc-edit.tsx +++ b/app/forms/vpc-edit.tsx @@ -78,7 +78,7 @@ export default function EditVpcSideModalForm() { - + ) } diff --git a/app/forms/vpc-router-create.tsx b/app/forms/vpc-router-create.tsx index 2578df6b0..cbcdfb6b9 100644 --- a/app/forms/vpc-router-create.tsx +++ b/app/forms/vpc-router-create.tsx @@ -57,7 +57,11 @@ export default function RouterCreate() { > - + ) } diff --git a/app/forms/vpc-router-edit.tsx b/app/forms/vpc-router-edit.tsx index 4feccb194..a8ac48eb3 100644 --- a/app/forms/vpc-router-edit.tsx +++ b/app/forms/vpc-router-edit.tsx @@ -80,7 +80,11 @@ export default function EditRouterSideModalForm() { > - + ) } diff --git a/app/forms/vpc-router-route-common.tsx b/app/forms/vpc-router-route-common.tsx index 4043f5f49..85375c2fa 100644 --- a/app/forms/vpc-router-route-common.tsx +++ b/app/forms/vpc-router-route-common.tsx @@ -222,6 +222,10 @@ export const RouteFormFields = ({ form, disabled }: RouteFormFieldsProps) => { ) } -export const RouteFormDocs = () => ( - +export const RouteFormDocs = ({ apiOp, cliCmd }: { apiOp: string; cliCmd: string }) => ( + ) diff --git a/app/forms/vpc-router-route-create.tsx b/app/forms/vpc-router-route-create.tsx index 764497144..a78414f51 100644 --- a/app/forms/vpc-router-route-create.tsx +++ b/app/forms/vpc-router-route-create.tsx @@ -83,7 +83,7 @@ export default function CreateRouterRouteSideModalForm() { submitError={createRouterRoute.error} > - + ) } diff --git a/app/forms/vpc-router-route-edit.tsx b/app/forms/vpc-router-route-edit.tsx index f0d2dbd24..6fcd1ca2e 100644 --- a/app/forms/vpc-router-route-edit.tsx +++ b/app/forms/vpc-router-route-edit.tsx @@ -100,7 +100,7 @@ export default function EditRouterRouteSideModalForm() { submitDisabled={disabled ? routeFormMessage.vpcSubnetNotModifiable : undefined} > - + ) } diff --git a/app/pages/project/disks/DiskDetailSideModal.tsx b/app/pages/project/disks/DiskDetailSideModal.tsx index 990695f98..587b3248d 100644 --- a/app/pages/project/disks/DiskDetailSideModal.tsx +++ b/app/pages/project/disks/DiskDetailSideModal.tsx @@ -94,7 +94,7 @@ export function DiskDetailSideModal({ - + ) } diff --git a/app/pages/project/vpcs/internet-gateway-edit.tsx b/app/pages/project/vpcs/internet-gateway-edit.tsx index 5d5ac415d..c35baadeb 100644 --- a/app/pages/project/vpcs/internet-gateway-edit.tsx +++ b/app/pages/project/vpcs/internet-gateway-edit.tsx @@ -201,7 +201,11 @@ export default function EditInternetGatewayForm() { - + ) } diff --git a/app/ui/lib/ModalLinks.tsx b/app/ui/lib/ModalLinks.tsx index d75794347..44e9e5867 100644 --- a/app/ui/lib/ModalLinks.tsx +++ b/app/ui/lib/ModalLinks.tsx @@ -11,6 +11,8 @@ import { OpenLink12Icon } from '@oxide/design-system/icons/react' import { FormDivider } from '~/ui/lib/Divider' +const DOC_BASE = 'https://docs.oxide.computer' + export const ModalLinks = ({ heading, children, @@ -41,13 +43,59 @@ export const ModalLink = ({ to, label }: { to: string; label: string }) => ( type DocLink = { href: string; linkText: string } -export const SideModalFormDocs = ({ docs }: { docs: DocLink[] }) => ( +/** + * `apiOp` is a snake_case operation ID, e.g., "project_create". + * `cliCmd` is a slash-delimited CLI path, e.g., "project/create". + */ +export const SideModalFormDocs = ({ + docs, + apiOp, + cliCmd, +}: { + docs: DocLink[] + apiOp?: string + cliCmd?: string +}) => ( <> {docs.map(({ href, linkText }) => ( ))} + {apiOp && ( +
  • + + + + API:{' '} + {apiOp} + + +
  • + )} + {cliCmd && ( +
  • + + + + CLI:{' '} + + oxide {cliCmd.replaceAll('/', ' ')} + + + +
  • + )}
    )