Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
1 Skipped Deployment
|
📝 WalkthroughWalkthroughSplits monolithic settings into many focused UI modules, adds granular TRPC build/runtime endpoints, reorganizes router exports, enhances SettingCard and UI primitives, introduces sliders/icons, and implements EnvVars import/decryption/diff utilities; several legacy composite components were removed. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
participant User
participant Browser as Settings UI (client)
participant TRPC as tRPC Router
participant DB as Database
User->>Browser: Edit/submit setting (form, slider, env import)
Browser->>TRPC: call environmentSettings.build.* or environmentSettings.runtime.*
TRPC->>DB: UPDATE/INSERT environmentBuildSettings / environmentRuntimeSettings
DB-->>TRPC: acknowledgement
TRPC-->>Browser: mutation success
Browser->>TRPC: invalidate & refetch queries
TRPC->>DB: SELECT refreshed settings
DB-->>TRPC: return data
TRPC-->>Browser: updated data
Browser-->>User: show toast, update UI
Estimated code review effort🎯 5 (Critical) | ⏱️ ~120 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
Comment |
|
There will be second iteration where I move these all the tanstack db then mutation will be more organized |
There was a problem hiding this comment.
Actionable comments posted: 15
Note
Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
web/apps/dashboard/app/(app)/[workspaceSlug]/apis/[apiId]/settings/components/default-bytes.tsx (1)
40-41:⚠️ Potential issue | 🔴 CriticalReplace
as anywith proper Zod input/output typingThe
as anycast bypasses type checking. SincekeyBytesSchemausesz.coerce.number()(which has different input/output types), you need explicit typing. With@hookform/resolvers@5.2.2installed, use:useForm<z.input<typeof formSchema>, any, z.output<typeof formSchema>>({ ...createApiFormConfig(formSchema), resolver: zodResolver(formSchema), // ... rest of config })Alternatively, let React Hook Form infer types from the resolver by removing the explicit generic and letting it match the resolver output. Remove the
as anycast entirely.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/apps/dashboard/app/`(app)/[workspaceSlug]/apis/[apiId]/settings/components/default-bytes.tsx around lines 40 - 41, The current cast "as any" on the resolver bypasses type safety; update the useForm call that references resolver, formSchema, keyBytesSchema and createApiFormConfig to use correct Zod input/output generics instead of "as any" — either call useForm with explicit generics useForm<z.input<typeof formSchema>, any, z.output<typeof formSchema>> and pass resolver: zodResolver(formSchema) (removing the cast), or drop the explicit useForm generic so React Hook Form infers types from zodResolver(formSchema); ensure the resolver: zodResolver(formSchema) line no longer uses "as any".
🟡 Minor comments (11)
web/apps/dashboard/lib/trpc/routers/deploy/environment-settings/build/update-dockerfile.ts-13-22 (1)
13-22:⚠️ Potential issue | 🟡 MinorSilent failure if record doesn't exist; should validate update success.
The mutation updates the database without verifying a matching record exists. If no row matches the
workspaceId/environmentIdcombination, the update silently succeeds with 0 rows affected and returns nothing. Compare with similar mutations in the codebase (e.g.,env-vars/delete.ts) which checkrowsAffected === 0and throw aNOT_FOUNDerror. Either verify the record exists before updating usingfindFirst, or check the result and throw an error when no rows are affected.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/apps/dashboard/lib/trpc/routers/deploy/environment-settings/build/update-dockerfile.ts` around lines 13 - 22, The mutation currently calls db.update(environmentBuildSettings).set(...) without verifying a row was changed, so it can silently no-op; modify the handler in this mutation to either first lookup the record via a findFirst on environmentBuildSettings filtered by environmentBuildSettings.workspaceId === ctx.workspace.id and environmentBuildSettings.environmentId === input.environmentId, or inspect the update result's rowsAffected and if rowsAffected === 0 throw a TRPCError with code 'NOT_FOUND'; ensure you reference the same identifiers (db.update(environmentBuildSettings).set, environmentBuildSettings.workspaceId, environmentBuildSettings.environmentId, ctx.workspace.id, input.environmentId) and return/throw consistently as other mutations (e.g., env-vars/delete.ts).web/apps/dashboard/app/(app)/[workspaceSlug]/projects/[projectId]/(overview)/settings/components/runtime-settings/instances.tsx-51-55 (1)
51-55:⚠️ Potential issue | 🟡 MinorRemove
React.FCand use explicit function type annotation.The file doesn't import
React, so usingReact.FCrelies on the implicit global namespace. While this works with the current"jsx": "react-jsx"tsconfig setting, it's unnecessary and creates an undeclared dependency. Use a direct function type instead.🛠️ Suggested change
-const InstancesForm: React.FC<InstancesFormProps> = ({ +const InstancesForm = ({ environmentId, defaultInstances, selectedRegions, -}) => { +}: InstancesFormProps) => {🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/apps/dashboard/app/`(app)/[workspaceSlug]/projects/[projectId]/(overview)/settings/components/runtime-settings/instances.tsx around lines 51 - 55, Replace the React.FC usage on the InstancesForm declaration with a plain function typed with the props interface: change "const InstancesForm: React.FC<InstancesFormProps> = ({ ... }) => { ... }" to "function InstancesForm({ environmentId, defaultInstances, selectedRegions }: InstancesFormProps) { ... }" (or an equivalent const with explicit function type like "const InstancesForm = ({...}: InstancesFormProps) => { ... }"); remove any reliance on the React global type and do not add a React import.web/apps/dashboard/app/(app)/[workspaceSlug]/projects/[projectId]/(overview)/settings/components/runtime-settings/storage.tsx-39-39 (1)
39-39:⚠️ Potential issue | 🟡 MinorRemove
React.FCnamespace reference and implement save handler.Line 39 uses
React.FC<StorageFormProps>without importing React. While modern JSX doesn't require the import,React.FCis a direct namespace reference that will fail. Additionally, line 69'sonSubmithandler only prevents default without persisting changes—the Save button can become enabled but does nothing.🛠️ Suggested changes
-const StorageForm: React.FC<StorageFormProps> = ({ defaultStorage }) => { +const StorageForm = ({ defaultStorage }: StorageFormProps) => {- onSubmit={(e) => e.preventDefault()} + onSubmit={async (e) => { + e.preventDefault(); + // TODO: Implement storage persistence logic + }}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/apps/dashboard/app/`(app)/[workspaceSlug]/projects/[projectId]/(overview)/settings/components/runtime-settings/storage.tsx at line 39, The component uses a direct React namespace (React.FC<StorageFormProps>) without importing React and its onSubmit only preventsDefault without persisting changes; change the declaration to a plain typed function (e.g., const StorageForm = ({ defaultStorage }: StorageFormProps) => { ... }) to remove the React.FC reference, and implement a save handler (e.g., handleSave or onSubmit) that actually persists the form state (call your save API, context updater, or an onSave prop), manage a saving boolean to disable the Save button while awaiting the request, and handle success/error feedback so the enabled Save button performs the intended persist operation; update any references to onSubmit and the Save button to call this new handler.web/apps/dashboard/app/(app)/[workspaceSlug]/projects/[projectId]/(overview)/settings/components/shared/form-setting-card.tsx-6-70 (1)
6-70:⚠️ Potential issue | 🟡 MinorWrap with
forwardRefto properly forward therefprop.The
refprop won't be forwarded to the form element withoutforwardRef. React treatsrefas a special prop and does not pass it through as a normal prop to functional components, making the current API misleading.🛠️ Suggested change (forwardRef)
-import { Button, SettingCard, type SettingCardBorder } from "@unkey/ui"; -import type React from "react"; +import { Button, SettingCard, type SettingCardBorder } from "@unkey/ui"; +import { forwardRef } from "react"; +import type React from "react"; @@ type EditableSettingCardProps = { icon: React.ReactNode; @@ canSave: boolean; isSaving: boolean; - ref?: React.Ref<HTMLFormElement>; className?: string; }; -export const FormSettingCard = ({ +export const FormSettingCard = forwardRef<HTMLFormElement, EditableSettingCardProps>( + ({ icon, title, description, border, displayValue, onSubmit, children, canSave, isSaving, - ref, className, -}: EditableSettingCardProps) => { + }, ref) => { return ( @@ - ); -}; + ); +}); + +FormSettingCard.displayName = "FormSettingCard";🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/apps/dashboard/app/`(app)/[workspaceSlug]/projects/[projectId]/(overview)/settings/components/shared/form-setting-card.tsx around lines 6 - 70, The component FormSettingCard currently declares a ref prop in EditableSettingCardProps but doesn't actually forward React refs; wrap the component with React.forwardRef to accept a forwardedRef parameter (e.g., forwardRef<HTMLFormElement, EditableSettingCardProps>(function FormSettingCard(props, forwardedRef) { ... })), remove the explicit ref field from EditableSettingCardProps (or mark it optional and not used), and pass the forwardedRef to the form element's ref attribute instead of the props.ref; ensure the exported FormSettingCard is the forwardRef-wrapped function so consumers can attach refs to the underlying <form>.web/apps/dashboard/app/(app)/[workspaceSlug]/projects/[projectId]/(overview)/settings/components/runtime-settings/cpu.tsx-44-50 (1)
44-50:⚠️ Potential issue | 🟡 MinorType mismatch:
environmentIdmay beundefinedbut is typed asstring.Same issue as in
memory.tsx-environments[0]?.idreturnsstring | undefined, butCpuFormPropsdeclaresenvironmentId: string.🛡️ Proposed fix
type CpuFormProps = { - environmentId: string; + environmentId: string | undefined; defaultCpu: number; };As per coding guidelines: "Never compromise type safety"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/apps/dashboard/app/`(app)/[workspaceSlug]/projects/[projectId]/(overview)/settings/components/runtime-settings/cpu.tsx around lines 44 - 50, The CpuFormProps declaration currently types environmentId as string but the caller passes environments[0]?.id which is string | undefined; update the prop typing to accept undefined (change CpuFormProps.environmentId to string | undefined) or ensure the parent guarantees a string before rendering CpuForm (e.g., only render when environments[0]?.id is defined). Locate CpuForm, CpuFormProps and the caller that passes environments[0]?.id in cpu.tsx and apply one of these fixes so the types align without using unsafe assertions.web/apps/dashboard/app/(app)/[workspaceSlug]/projects/[projectId]/(overview)/settings/components/runtime-settings/memory.tsx-44-50 (1)
44-50:⚠️ Potential issue | 🟡 MinorType mismatch:
environmentIdmay beundefinedbut is typed asstring.
environments[0]?.idon Line 35 returnsstring | undefined, butMemoryFormPropson Line 48 declaresenvironmentId: string. This violates type safety since the component could receiveundefined.🛡️ Proposed fix
type MemoryFormProps = { - environmentId: string; + environmentId: string | undefined; defaultMemory: number; };Then handle the undefined case in
onSubmit:const onSubmit = async (values: MemoryFormValues) => { + if (!environmentId) { + return; + } await updateMemory.mutateAsync({ environmentId, memoryMib: values.memory, }); };As per coding guidelines: "Never compromise type safety: No
any, no!(non-null assertion), noas Type"🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/apps/dashboard/app/`(app)/[workspaceSlug]/projects/[projectId]/(overview)/settings/components/runtime-settings/memory.tsx around lines 44 - 50, The prop environmentId passed into MemoryForm is coming from environments[0]?.id which can be undefined, so update the MemoryFormProps/environmentId type to string | undefined (or make it optional) and then update MemoryForm's onSubmit and any callers to handle the undefined case safely (e.g., no-op or show validation/error and prevent API calls when environmentId is undefined); do not use non-null assertions or casts—explicitly check environmentId inside MemoryForm (and in submit handlers) before proceeding.web/apps/dashboard/app/(app)/[workspaceSlug]/projects/[projectId]/(overview)/settings/components/build-settings/port-settings.tsx-76-78 (1)
76-78:⚠️ Potential issue | 🟡 MinorEmpty string fallback for
environmentIdmay cause server errors.When
environmentIdisundefined, passing""to the mutation could result in a failed database query or unexpected behavior. Consider early-returning or disabling submission whenenvironmentIdis unavailable.🛡️ Proposed fix
const onSubmit = async (values: z.infer<typeof portSchema>) => { + if (!environmentId) { + return; + } - await updatePort.mutateAsync({ environmentId: environmentId ?? "", port: values.port }); + await updatePort.mutateAsync({ environmentId, port: values.port }); };Also disable the form when environmentId is missing:
- canSave={isValid && !isSubmitting && currentPort !== defaultValue} + canSave={isValid && !isSubmitting && currentPort !== defaultValue && Boolean(environmentId)}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/apps/dashboard/app/`(app)/[workspaceSlug]/projects/[projectId]/(overview)/settings/components/build-settings/port-settings.tsx around lines 76 - 78, The handler onSubmit currently passes environmentId ?? "" into updatePort.mutateAsync which can send an empty string to the server; change onSubmit to early-return when environmentId is undefined (or throw/notify) so updatePort.mutateAsync is only called with a valid environmentId, and also disable the form submission UI when environmentId is missing (tie the form/button disabled state to the presence of environmentId) so portSchema validation and updatePort.mutateAsync are never invoked with an empty string.web/apps/dashboard/app/(app)/[workspaceSlug]/projects/[projectId]/(overview)/settings/components/build-settings/github-settings/shared.tsx-37-46 (1)
37-46:⚠️ Potential issue | 🟡 MinorHandle edge case when
fullNamelacks a separatorIf
fullNamedoesn't contain/, the destructuring yieldsundefinedforrepoName, which renders as the literal text "undefined" in the UI.🛡️ Proposed defensive fix
export const RepoNameLabel = ({ fullName }: { fullName: string }) => { - const [handle, repoName] = fullName.split("/"); + const parts = fullName.split("/"); + const handle = parts[0] ?? fullName; + const repoName = parts[1]; return ( // This max-w-[185px] and w-[185px] in ComboboxSkeleton should match <div className="max-w-[185px] truncate"> <span className="text-[13px] text-gray-12 font-medium">{handle}</span> - <span className="text-[13px] text-gray-11">/{repoName}</span> + {repoName && <span className="text-[13px] text-gray-11">/{repoName}</span>} </div> ); };🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/apps/dashboard/app/`(app)/[workspaceSlug]/projects/[projectId]/(overview)/settings/components/build-settings/github-settings/shared.tsx around lines 37 - 46, The RepoNameLabel component currently destructures fullName.split("/") so if fullName lacks "/" repoName becomes undefined and "undefined" shows in the UI; change the logic in RepoNameLabel to defensively split and handle missing separator (e.g., const parts = fullName.split("/", 2); const handle = parts[0]; const repoName = parts[1] ?? ""; and only render the "/" and repoName when repoName is non-empty) so the component displays the fullName or handle cleanly instead of "undefined".web/apps/dashboard/lib/trpc/routers/deploy/environment-settings/runtime/update-regions.ts-21-21 (1)
21-21:⚠️ Potential issue | 🟡 MinorType assertion
as Record<string, number>violates type safety guidelineThis cast bypasses type checking. The schema defines
regionConfigwith.$type<Record<string, number>>(), so Drizzle should infer the correct type. If the query result typing is incomplete, consider creating a typed helper or using Zod to parse the result.As per coding guidelines: "Never compromise type safety: No
any, no!(non-null assertion), noas Type".🛡️ Proposed fix using nullish coalescing with type guard
- const currentConfig = (existing?.regionConfig as Record<string, number>) ?? {}; + const currentConfig = existing?.regionConfig ?? {};If the type error persists, ensure the Drizzle schema's
.$type<>()is properly propagated to query results, or parse with Zod:const regionConfigSchema = z.record(z.string(), z.number()); const currentConfig = regionConfigSchema.parse(existing?.regionConfig ?? {});🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/apps/dashboard/lib/trpc/routers/deploy/environment-settings/runtime/update-regions.ts` at line 21, Replace the unsafe type assertion on regionConfig—remove "as Record<string, number>" used when creating currentConfig—and instead validate/derive a properly typed object: either ensure the Drizzle schema type propagates to the query so existing.regionConfig is already typed, or parse/validate existing?.regionConfig with a Zod record schema (e.g., regionConfigSchema.parse(existing?.regionConfig ?? {})) or a small type-guard that returns a Record<string, number> before assigning to currentConfig; update any references to currentConfig to use the resulting typed value.web/apps/dashboard/app/(app)/[workspaceSlug]/projects/[projectId]/(overview)/settings/components/advanced-settings/env-vars/use-drop-zone.ts-96-101 (1)
96-101:⚠️ Potential issue | 🟡 MinorAvoid
as Nodetype assertion in drag-leave handling.
DragEvent.relatedTargetis typed asEventTarget | null, notNode. The cast bypasses type safety and would throw a runtime error ifrelatedTargetis null. Use aninstanceof Nodecheck instead, which handles null and provides proper type narrowing.🔧 Suggested fix
const handleDragLeave = (e: DragEvent) => { e.preventDefault(); e.stopPropagation(); - if (e.currentTarget === dropZone && !dropZone.contains(e.relatedTarget as Node)) { + const relatedTarget = e.relatedTarget; + const leftDropZone = + !(relatedTarget instanceof Node) || !dropZone.contains(relatedTarget); + if (e.currentTarget === dropZone && leftDropZone) { setIsDragging(false); } };🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/apps/dashboard/app/`(app)/[workspaceSlug]/projects/[projectId]/(overview)/settings/components/advanced-settings/env-vars/use-drop-zone.ts around lines 96 - 101, In handleDragLeave, avoid the unsafe cast of e.relatedTarget as Node; instead check that e.relatedTarget is a Node (e.g. using "instanceof Node") before calling dropZone.contains, and handle the null case so you only call dropZone.contains when relatedTarget is non-null and a Node; update the conditional that references dropZone.contains(e.relatedTarget as Node) to first guard via an instanceof Node check and then call setIsDragging(false) accordingly.web/apps/dashboard/app/(app)/[workspaceSlug]/projects/[projectId]/(overview)/settings/components/runtime-settings/regions.tsx-184-188 (1)
184-188:⚠️ Potential issue | 🟡 Minor
optionallabel conflicts with required validation.The schema enforces at least one region, but the combobox is marked optional. Either remove
optionalor relax the validation to avoid UX confusion.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/apps/dashboard/app/`(app)/[workspaceSlug]/projects/[projectId]/(overview)/settings/components/runtime-settings/regions.tsx around lines 184 - 188, The FormCombobox for Regions is marked optional but your validation schema requires at least one region; either remove the optional prop from the FormCombobox component (named FormCombobox) so the UI reflects the required validation, or update the validation rule that enforces at least one region (the schema that validates selected regions) to allow an empty selection; pick one consistent approach and update the corresponding code (remove the optional prop on the FormCombobox or relax the “min items”/required constraint in the regions validation schema) so the UI and validation match.
🧹 Nitpick comments (9)
web/apps/dashboard/app/(app)/[workspaceSlug]/projects/[projectId]/(overview)/settings/components/shared/setting-description.tsx (1)
10-13: Semantic mismatch:<output>element used for static description.The
<output>element is semantically intended for "the result of a calculation or user action" (e.g., form calculation results). For static descriptive text, a<div>or<span>is more appropriate and avoids potential screen reader confusion.Proposed fix
- <output className="text-gray-9 flex gap-2 items-start"> + <div className="text-gray-9 flex gap-2 items-start"> <CircleInfo iconSize="md-medium" className="flex-shrink-0 mt-[3px]" aria-hidden="true" /> <span className="flex-1 text-gray-10">{children}</span> - </output> + </div>🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/apps/dashboard/app/`(app)/[workspaceSlug]/projects/[projectId]/(overview)/settings/components/shared/setting-description.tsx around lines 10 - 13, The component is using an <output> element for static descriptive text which is semantically incorrect; replace the <output className="text-gray-9 flex gap-2 items-start">...</output> wrapper with a non-semantic container like a <div> (or <span> if inline) preserving the inner structure (CircleInfo with aria-hidden and the <span className="flex-1 text-gray-10">{children}</span>) and keep existing className values and layout so styling and accessibility remain unchanged.web/apps/dashboard/lib/trpc/routers/deploy/environment-settings/build/update-docker-context.ts (1)
8-11: Input validation inconsistency with sibling endpoint.
dockerContextaccepts empty strings (z.string()) whileupdateDockerfilerequires non-empty input (z.string().min(1)). The database schema defaultsdockerContextto".", suggesting empty values are likely unintended.Proposed fix for consistent validation
z.object({ environmentId: z.string(), - dockerContext: z.string(), + dockerContext: z.string().min(1), }),🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/apps/dashboard/lib/trpc/routers/deploy/environment-settings/build/update-docker-context.ts` around lines 8 - 11, The zod input schema here accepts empty dockerContext values (dockerContext: z.string()) which is inconsistent with the sibling updateDockerfile endpoint and the DB default of "."; update the input validation in this file to require a non-empty string (use z.string().min(1) for dockerContext) so the schema enforces a meaningful value, leaving environmentId as z.string() unchanged and keeping behavior consistent with updateDockerfile.web/apps/dashboard/app/(app)/[workspaceSlug]/projects/[projectId]/(overview)/settings/components/runtime-settings/memory.tsx (1)
164-169: Consider reusingformatMemoryfrom deployment-formatters.
parseMemoryDisplayduplicates logic already present informatMemory(imported on Line 4). The only difference is the return type (tuple vs string).♻️ Suggested approach
Either extend
formatMemoryto support tuple output, or extract a shared helper:function parseMemoryDisplay(mib: number): [string, string] { const formatted = formatMemory(mib); // Parse the formatted string, or refactor formatMemory to expose parts if (mib >= 1024) { return [`${(mib / 1024).toFixed(mib % 1024 === 0 ? 0 : 1)}`, "GiB"]; } return [`${mib}`, "MiB"]; }Alternatively, add a
formatMemoryPartsutility to deployment-formatters.ts that both components can use.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/apps/dashboard/app/`(app)/[workspaceSlug]/projects/[projectId]/(overview)/settings/components/runtime-settings/memory.tsx around lines 164 - 169, parseMemoryDisplay duplicates formatting logic already implemented by formatMemory from deployment-formatters; refactor by either extending formatMemory to return a tuple or by adding a shared helper (e.g., formatMemoryParts) in deployment-formatters and update parseMemoryDisplay to call that helper instead of reimplementing logic; change parseMemoryDisplay to use the new helper/extended formatMemory so it returns [string, string] based on the shared implementation.web/apps/dashboard/app/(app)/[workspaceSlug]/projects/[projectId]/(overview)/settings/components/runtime-settings/scaling.tsx (1)
66-68: Form submission is disabled - component appears incomplete.The
onSubmithandler only callse.preventDefault()andisSavingis hardcoded tofalse. The Save button is rendered but does nothing. If this is intentional (as noted in the AI summary as "temporarily disabled"), consider:
- Adding a comment explaining the WIP state
- Hiding the Save button entirely, or
- Disabling the expandable form section
📝 Suggested documentation
+// TODO: Wire up TRPC mutation when scaling backend is ready export const Scaling = () => {Or disable saving entirely:
- canSave={isValid && hasChanges} + canSave={false} // Scaling mutation not yet implemented🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/apps/dashboard/app/`(app)/[workspaceSlug]/projects/[projectId]/(overview)/settings/components/runtime-settings/scaling.tsx around lines 66 - 68, The form currently prevents submit and hardcodes isSaving to false, leaving the Save button non-functional; update the component that renders the form (the props onSubmit, canSave, isSaving) so submission is handled or clearly disabled: implement a submit handler (replace onSubmit={(e) => e.preventDefault()} with a function that calls the save routine or dispatches saveProjectScaling), wire a real isSaving state variable (e.g., useState/isSaving or reading from the relevant async mutation) instead of false, and either hide/disable the Save control when saving is intentionally WIP or add a clear inline comment explaining it’s intentionally disabled; reference the props named onSubmit, canSave (uses isValid && hasChanges), and isSaving when making changes.web/apps/dashboard/app/(app)/[workspaceSlug]/projects/[projectId]/(overview)/settings/components/build-settings/github-settings/github-no-repo.tsx (1)
71-71: Unconventional JSX attribute syntaxUsing
placeholder=<span>without curly braces is valid but unconventional. Most style guides prefer wrapping JSX in curly braces for clarity.♻️ Suggested change for consistency
- placeholder=<span className="text-left w-full">Select a repository...</span> + placeholder={<span className="text-left w-full">Select a repository...</span>}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/apps/dashboard/app/`(app)/[workspaceSlug]/projects/[projectId]/(overview)/settings/components/build-settings/github-settings/github-no-repo.tsx at line 71, The placeholder prop in the GitHubNoRepo component (github-no-repo.tsx) uses JSX without curly braces (placeholder=<span className="...">…), which is unconventional; change it to use an expression by wrapping the JSX in curly braces (placeholder={<span className="text-left w-full">Select a repository...</span>}) so the placeholder prop contains a proper JSX expression for consistency with style guides.web/apps/dashboard/app/(app)/[workspaceSlug]/projects/[projectId]/(overview)/settings/components/build-settings/github-settings/shared.tsx (1)
58-63: Semantic issue:<a>nested inside<Button>Nesting an
<a>inside a<Button>creates invalid HTML (interactive element inside interactive element) and can cause accessibility issues. The button's click handler and the anchor's navigation may conflict.Consider using the Button's
asChildpattern (if supported) or rendering the anchor directly with button-like styling.♻️ Proposed fix using anchor with button styling
-<Button variant={variant} className={className}> - <a href={installUrl} className="text-sm text-gray-12" target="_blank" rel="noopener noreferrer"> - {text} - </a> -</Button> +<a + href={installUrl} + target="_blank" + rel="noopener noreferrer" + className={cn( + "inline-flex items-center justify-center text-sm text-gray-12", + className, + )} +> + {text} +</a>🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/apps/dashboard/app/`(app)/[workspaceSlug]/projects/[projectId]/(overview)/settings/components/build-settings/github-settings/shared.tsx around lines 58 - 63, The current JSX nests an <a> inside <Button> which is invalid; update the rendering so the interactive element isn't nested: either use the Button's asChild prop (e.g., <Button asChild> and pass the <a href={installUrl} target="_blank" rel="noopener noreferrer" className=...>{text}</a>) or render the <a> directly with the same button-like styling and props (use variant/className values) and keep installUrl, target and rel intact; ensure you remove the nested structure around Button and preserve accessibility attributes.web/internal/ui/src/components/settings-card.tsx (1)
148-165: Potential height calculation issue on initial expansionWhen
isExpandedbecomestrue,contentRef.current?.scrollHeightmay return0or an incorrect value on the first render cycle because the content hasn't been measured yet. This could cause a visual glitch where the panel animates from0pxto0px, then jumps to the correct height on subsequent renders.Consider using a
useLayoutEffectto measure and set the height, or defaulting toscrollHeight || 'auto'for the initial expansion.♻️ Optional fix using fallback
style={{ - maxHeight: isExpanded ? `${contentRef.current?.scrollHeight}px` : "0px", + maxHeight: isExpanded ? `${contentRef.current?.scrollHeight || 9999}px` : "0px", }}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/internal/ui/src/components/settings-card.tsx` around lines 148 - 165, The expandable panel can measure scrollHeight too late causing a 0px-to-0px animation; update the component that uses contentRef, isExpanded and expandable to measure and set the correct height before paint (use useLayoutEffect) and/or fall back to "auto" on first expansion; specifically, when isExpanded toggles to true run a useLayoutEffect that reads contentRef.current.scrollHeight and sets a local measuredHeight state (or directly sets the inline maxHeight) so the inline style uses the measured value (or "auto" if measurement is not yet available) to avoid the initial jump.web/apps/dashboard/lib/trpc/routers/deploy/environment-settings/runtime/update-command.ts (1)
13-22: Silent failure when environment settings don't existThe mutation updates without verifying the row exists. If
environmentIdis invalid or the settings record is missing, the update silently affects 0 rows with no error returned to the client. This could mask bugs or provide confusing UX.Consider checking the affected row count or querying first to confirm the record exists.
♻️ Proposed fix with existence check
.mutation(async ({ ctx, input }) => { - await db + const result = await db .update(environmentRuntimeSettings) .set({ command: input.command }) .where( and( eq(environmentRuntimeSettings.workspaceId, ctx.workspace.id), eq(environmentRuntimeSettings.environmentId, input.environmentId), ), ); + + if (result.rowsAffected === 0) { + throw new Error("Environment settings not found"); + } });🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/apps/dashboard/lib/trpc/routers/deploy/environment-settings/runtime/update-command.ts` around lines 13 - 22, The mutation currently performs db.update(environmentRuntimeSettings).set({ command: input.command }) with the where clause on environmentRuntimeSettings.workspaceId and environmentRuntimeSettings.environmentId but does not verify a row was actually updated; if the record is missing the call silently affects 0 rows. Change the handler in the mutation to either first select the row (e.g., select from environmentRuntimeSettings where workspaceId = ctx.workspace.id and environmentId = input.environmentId) and throw a not-found error if absent, or capture the update result/rowCount from the db.update call and throw a not-found/validation error when zero rows were affected so the client receives an explicit error instead of a silent no-op.web/apps/dashboard/lib/trpc/routers/deploy/environment-settings/runtime/update-regions.ts (1)
27-35: Silent failure when environment settings don't existSame concern as other update mutations: if
existingisundefined(no settings row exists), the update proceeds and silently affects 0 rows. The client receives no indication of failure.♻️ Proposed fix
+ if (!existing) { + throw new Error("Environment settings not found"); + } + await db .update(environmentRuntimeSettings) .set({ regionConfig })🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/apps/dashboard/lib/trpc/routers/deploy/environment-settings/runtime/update-regions.ts` around lines 27 - 35, The update currently runs even when no environment runtime settings row exists, silently affecting 0 rows; before calling db.update(...).set({ regionConfig }).where(...), query environmentRuntimeSettings for the row matching ctx.workspace.id and input.environmentId (e.g., db.select().from(environmentRuntimeSettings).where(...)) and if the result is undefined throw an appropriate error (e.g., TRPCError with NOT_FOUND) so the client gets a clear failure instead of a silent no-op; update the code paths in update-regions.ts to perform this existence check and only run the db.update when the row is present.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In
`@web/apps/dashboard/app/`(app)/[workspaceSlug]/projects/[projectId]/(overview)/settings/components/advanced-settings/command.tsx:
- Around line 31-32: The component currently passes environmentId ?? "" into
CommandForm which allows submitting while the real environmentId is missing;
update CommandForm usage and its submit path to require a truthy environmentId:
disable the Save button and prevent form submission when environmentId is falsy,
show a loading/disabled state until environmentId is available, and
short-circuit the mutation call in the CommandForm submit handler (or the parent
submit proxy) to avoid calling the mutation with an empty string; touch the
CommandForm component and any submit handler/mutation invocation that reads
environmentId (references: CommandForm, environmentId, the form submit
function/mutation) and apply the same guard for the other occurrences around
lines 73–97.
In
`@web/apps/dashboard/app/`(app)/[workspaceSlug]/projects/[projectId]/(overview)/settings/components/advanced-settings/env-vars/index.tsx:
- Around line 138-191: The code uses unsafe `as string` casts for env var ids;
replace them by narrowing with a type predicate (e.g. function hasId(v: EnvVar):
v is EnvVar & { id: string }) and use it everywhere: filter
originalVars/currentIds/toDelete/toCreate/toUpdate by hasId so maps and new
Map(originalVars.map((v)=>[v.id, v])) and uses of v.id (in
deleteMutation/updateMutation) no longer require `as string`; ensure toCreate
still filters out empty key/value and use toTrpcType(v.secret) unchanged.
In
`@web/apps/dashboard/app/`(app)/[workspaceSlug]/projects/[projectId]/(overview)/settings/components/build-settings/dockerfile-settings.tsx:
- Around line 36-47: The form's defaultValues are only applied on mount so when
defaultValue changes the UI and canSave state can become stale; inside the
Dockerfile settings component call the reset function returned by useForm
whenever defaultValue changes (e.g., useEffect watching defaultValue and
invoking reset({ dockerfile: defaultValue })) so useWatch/currentDockerfile and
form validity reflect the loaded settings; reference the existing useForm hook
(resolver: zodResolver(dockerfileSchema), defaultValues) and the
currentDockerfile/useWatch to locate where to add the effect.
In
`@web/apps/dashboard/app/`(app)/[workspaceSlug]/projects/[projectId]/(overview)/settings/components/build-settings/root-directory-settings.tsx:
- Around line 36-47: The form is initialized with useForm({... defaultValues: {
dockerContext: defaultValue }}) but defaultValues only applies on first render
so when the query-provided defaultValue changes you must call the form API to
update state; add a useEffect that watches defaultValue and calls reset({
dockerContext: defaultValue }) (imported from useForm result) to update the
controlled value returned by useWatch and prevent saving stale values —
reference the useForm call, rootDirectorySchema, defaultValue,
useWatch/currentDockerContext, and reset.
In
`@web/apps/dashboard/app/`(app)/[workspaceSlug]/projects/[projectId]/(overview)/settings/components/runtime-settings/cpu.tsx:
- Around line 16-25: Fix the typo in the CPU_OPTIONS constant: the "32 vCPU"
entry in CPU_OPTIONS currently has value 32786 but should be 32768 (32 * 1024).
Update the object in CPU_OPTIONS where label is "32 vCPU" to use value 32768 so
valueToIndex and CPU allocation match server values.
In
`@web/apps/dashboard/app/`(app)/[workspaceSlug]/projects/[projectId]/(overview)/settings/components/runtime-settings/healthcheck/index.tsx:
- Around line 32-37: The form seeds default field values when
runtimeSettings.healthcheck is null but never marks the form as changed, so
users cannot "enable" a healthcheck; fix by adding an existence flag into the
seeded/initial values and include it in the hasChanges comparison: compute const
initialHealthcheckExists = Boolean(healthcheck), include exists:
initialHealthcheckExists in the initial/default HealthcheckFormValues (alongside
method/path/interval using secondsToInterval), ensure the live form state sets
exists: true when the user toggles/enables the healthcheck, and update the
hasChanges check to compare this exists flag as well as method/path/interval so
creating a new healthcheck is detected as a change.
In
`@web/apps/dashboard/app/`(app)/[workspaceSlug]/projects/[projectId]/(overview)/settings/components/runtime-settings/instances.tsx:
- Around line 31-32: Remove the redundant type assertion on regionConfig: drop
"as Record<string, number>" from the expression that reads
settingsData?.runtimeSettings?.regionConfig and either rely on the declared DB
type inference or add an explicit variable annotation (e.g., declare
regionConfig: Record<string, number> = ...). Update the same pattern where
regionConfig is set in regions.tsx (the
settingsData?.runtimeSettings?.regionConfig usage) so both locations use a
direct typed variable or inference rather than the "as" assertion.
- Around line 23-41: Guard against a possibly undefined environmentId by making
the prop optional or early-returning before rendering InstancesForm: update
InstancesFormProps/environmentId to accept string | undefined or return null
when environments[0]?.id is undefined and ensure
trpc.deploy.environmentSettings.get.useQuery and any downstream mutations are
only called when environmentId is present; replace the unsafe cast on
settingsData?.runtimeSettings?.regionConfig by narrowing/parsing it at runtime
(e.g., check settingsData?.runtimeSettings?.regionConfig is an object, map its
entries and coerce values to numbers) and derive
selectedRegions/defaultInstances from that safe mapping; finally remove or
correctly import React for any React.FC usage (or prefer explicit function
component types) so the component type is valid.
In
`@web/apps/dashboard/app/`(app)/[workspaceSlug]/projects/[projectId]/(overview)/settings/components/runtime-settings/regions.tsx:
- Around line 210-219: The remove-region control uses a plain <span> with only
onClick, making it inaccessible to keyboard users; update the span wrapping the
XMark in regions.tsx to include role="button", tabIndex={0}, and an onKeyDown
handler that listens for Enter and Space and calls e.stopPropagation();
removeRegion(r); (also preventDefault for Space to avoid scrolling) so keyboard
activation mirrors the onClick behavior; keep the existing onClick and className
intact so removeRegion, XMark, and the stopPropagation logic remain central and
consistent.
- Around line 37-39: The code unsafely casts
settingsData?.runtimeSettings?.regionConfig with "as Record<string, number>";
remove that assertion and instead validate/parse
settingsData.runtimeSettings.regionConfig using a zod schema (e.g.,
z.record(z.number()).optional()) before using it — create a schema, run
schema.parse or safeParse on settingsData?.runtimeSettings, fallback to {} on
failure, assign the validated object to regionConfig, and then compute
defaultRegions = Object.keys(regionConfig); update any error handling or logging
around the validation to surface malformed API shapes.
In
`@web/apps/dashboard/app/`(app)/[workspaceSlug]/projects/[projectId]/(overview)/settings/components/runtime-settings/storage.tsx:
- Around line 69-71: The form's onSubmit is a no-op so the Save button
(controlled by canSave/isValid/hasChanges) does nothing; replace the noop with a
real submit handler (e.g., implement handleSubmit or wire onSubmit to
submitStorage) that calls the backend mutation (e.g.,
updateProjectStorage/updateStorageMutation), toggles isSaving while awaiting the
request, handles success by updating local state and clearing hasChanges, and
surfaces errors to the UI/logging; if backend work isn't ready, instead disable
the Save action by setting canSave to false or change the button to a "coming
soon" state so enabled Save cannot be clicked.
In
`@web/apps/dashboard/lib/trpc/routers/deploy/environment-settings/runtime/update-cpu.ts`:
- Around line 8-11: The schema for the incoming payload accepts floats and
negatives; update the z.object validation for cpuMillicores in update-cpu.ts to
enforce integer and non-negative constraints (e.g., replace z.number() with a
validator that requires integers and a minimum of 0 such as
z.number().int().min(0)) so only valid millicore integers are accepted before
persisting to the integer-backed column.
In
`@web/apps/dashboard/lib/trpc/routers/deploy/environment-settings/runtime/update-instances.ts`:
- Around line 31-34: The loop that builds regionConfig uses
process.env.AVAILABLE_REGIONS and splits on commas but can insert an
empty-string key when AVAILABLE_REGIONS is "" or contains extra commas; update
the logic that iterates regions so you trim each entry and skip empty/whitespace
entries (e.g., use regionsEnv.split(",").map(r=>r.trim()).filter(Boolean) or
equivalent) before assigning regionConfig[region] = input.replicasPerRegion;
ensure the change is applied where regionConfig, process.env.AVAILABLE_REGIONS,
and input.replicasPerRegion are referenced.
- Line 21: Remove the unsafe assertion on regionConfig and give currentConfig an
explicit type instead: replace the `as Record<string, number>` cast on the
expression that uses `existing?.regionConfig` with a type annotation on
`currentConfig` (e.g., declare `currentConfig: Record<string, number> =
existing?.regionConfig ?? {}`) so you keep type-safety while preserving the
fallback behavior; update the assignment that references
`existing`/`regionConfig` and ensure no other `as` or non-null assertions remain
in this statement.
In
`@web/apps/dashboard/lib/trpc/routers/deploy/environment-settings/runtime/update-memory.ts`:
- Around line 8-11: The zod schema for the update-memory mutation currently
accepts any number for memoryMib; change the schema entry so memoryMib is
validated as a positive integer (e.g., z.number().int().positive().min(1) or
z.number().int().positive()) to prevent negatives and decimals from passing;
update the z.object that contains { environmentId: z.string(), memoryMib:
z.number() } in update-memory.ts to use the stricter zod chain for memoryMib so
server-side validation matches the DB int constraint and mirrors the checks used
in updatePort/updateInstances.
---
Outside diff comments:
In
`@web/apps/dashboard/app/`(app)/[workspaceSlug]/apis/[apiId]/settings/components/default-bytes.tsx:
- Around line 40-41: The current cast "as any" on the resolver bypasses type
safety; update the useForm call that references resolver, formSchema,
keyBytesSchema and createApiFormConfig to use correct Zod input/output generics
instead of "as any" — either call useForm with explicit generics
useForm<z.input<typeof formSchema>, any, z.output<typeof formSchema>> and pass
resolver: zodResolver(formSchema) (removing the cast), or drop the explicit
useForm generic so React Hook Form infers types from zodResolver(formSchema);
ensure the resolver: zodResolver(formSchema) line no longer uses "as any".
---
Nitpick comments:
In
`@web/apps/dashboard/app/`(app)/[workspaceSlug]/projects/[projectId]/(overview)/settings/components/build-settings/github-settings/github-no-repo.tsx:
- Line 71: The placeholder prop in the GitHubNoRepo component
(github-no-repo.tsx) uses JSX without curly braces (placeholder=<span
className="...">…), which is unconventional; change it to use an expression by
wrapping the JSX in curly braces (placeholder={<span className="text-left
w-full">Select a repository...</span>}) so the placeholder prop contains a
proper JSX expression for consistency with style guides.
In
`@web/apps/dashboard/app/`(app)/[workspaceSlug]/projects/[projectId]/(overview)/settings/components/build-settings/github-settings/shared.tsx:
- Around line 58-63: The current JSX nests an <a> inside <Button> which is
invalid; update the rendering so the interactive element isn't nested: either
use the Button's asChild prop (e.g., <Button asChild> and pass the <a
href={installUrl} target="_blank" rel="noopener noreferrer"
className=...>{text}</a>) or render the <a> directly with the same button-like
styling and props (use variant/className values) and keep installUrl, target and
rel intact; ensure you remove the nested structure around Button and preserve
accessibility attributes.
In
`@web/apps/dashboard/app/`(app)/[workspaceSlug]/projects/[projectId]/(overview)/settings/components/runtime-settings/memory.tsx:
- Around line 164-169: parseMemoryDisplay duplicates formatting logic already
implemented by formatMemory from deployment-formatters; refactor by either
extending formatMemory to return a tuple or by adding a shared helper (e.g.,
formatMemoryParts) in deployment-formatters and update parseMemoryDisplay to
call that helper instead of reimplementing logic; change parseMemoryDisplay to
use the new helper/extended formatMemory so it returns [string, string] based on
the shared implementation.
In
`@web/apps/dashboard/app/`(app)/[workspaceSlug]/projects/[projectId]/(overview)/settings/components/runtime-settings/scaling.tsx:
- Around line 66-68: The form currently prevents submit and hardcodes isSaving
to false, leaving the Save button non-functional; update the component that
renders the form (the props onSubmit, canSave, isSaving) so submission is
handled or clearly disabled: implement a submit handler (replace onSubmit={(e)
=> e.preventDefault()} with a function that calls the save routine or dispatches
saveProjectScaling), wire a real isSaving state variable (e.g.,
useState/isSaving or reading from the relevant async mutation) instead of false,
and either hide/disable the Save control when saving is intentionally WIP or add
a clear inline comment explaining it’s intentionally disabled; reference the
props named onSubmit, canSave (uses isValid && hasChanges), and isSaving when
making changes.
In
`@web/apps/dashboard/app/`(app)/[workspaceSlug]/projects/[projectId]/(overview)/settings/components/shared/setting-description.tsx:
- Around line 10-13: The component is using an <output> element for static
descriptive text which is semantically incorrect; replace the <output
className="text-gray-9 flex gap-2 items-start">...</output> wrapper with a
non-semantic container like a <div> (or <span> if inline) preserving the inner
structure (CircleInfo with aria-hidden and the <span className="flex-1
text-gray-10">{children}</span>) and keep existing className values and layout
so styling and accessibility remain unchanged.
In
`@web/apps/dashboard/lib/trpc/routers/deploy/environment-settings/build/update-docker-context.ts`:
- Around line 8-11: The zod input schema here accepts empty dockerContext values
(dockerContext: z.string()) which is inconsistent with the sibling
updateDockerfile endpoint and the DB default of "."; update the input validation
in this file to require a non-empty string (use z.string().min(1) for
dockerContext) so the schema enforces a meaningful value, leaving environmentId
as z.string() unchanged and keeping behavior consistent with updateDockerfile.
In
`@web/apps/dashboard/lib/trpc/routers/deploy/environment-settings/runtime/update-command.ts`:
- Around line 13-22: The mutation currently performs
db.update(environmentRuntimeSettings).set({ command: input.command }) with the
where clause on environmentRuntimeSettings.workspaceId and
environmentRuntimeSettings.environmentId but does not verify a row was actually
updated; if the record is missing the call silently affects 0 rows. Change the
handler in the mutation to either first select the row (e.g., select from
environmentRuntimeSettings where workspaceId = ctx.workspace.id and
environmentId = input.environmentId) and throw a not-found error if absent, or
capture the update result/rowCount from the db.update call and throw a
not-found/validation error when zero rows were affected so the client receives
an explicit error instead of a silent no-op.
In
`@web/apps/dashboard/lib/trpc/routers/deploy/environment-settings/runtime/update-regions.ts`:
- Around line 27-35: The update currently runs even when no environment runtime
settings row exists, silently affecting 0 rows; before calling
db.update(...).set({ regionConfig }).where(...), query
environmentRuntimeSettings for the row matching ctx.workspace.id and
input.environmentId (e.g.,
db.select().from(environmentRuntimeSettings).where(...)) and if the result is
undefined throw an appropriate error (e.g., TRPCError with NOT_FOUND) so the
client gets a clear failure instead of a silent no-op; update the code paths in
update-regions.ts to perform this existence check and only run the db.update
when the row is present.
In `@web/internal/ui/src/components/settings-card.tsx`:
- Around line 148-165: The expandable panel can measure scrollHeight too late
causing a 0px-to-0px animation; update the component that uses contentRef,
isExpanded and expandable to measure and set the correct height before paint
(use useLayoutEffect) and/or fall back to "auto" on first expansion;
specifically, when isExpanded toggles to true run a useLayoutEffect that reads
contentRef.current.scrollHeight and sets a local measuredHeight state (or
directly sets the inline maxHeight) so the inline style uses the measured value
(or "auto" if measurement is not yet available) to avoid the initial jump.
| return <CommandForm environmentId={environmentId ?? ""} defaultCommand={defaultCommand} />; | ||
| }; |
There was a problem hiding this comment.
Guard submissions when environmentId is missing.
environmentId ?? "" means a user can edit and submit before the ID loads, sending an empty string to the mutation (likely a no-op with a misleading success). Gate the submit and disable save when environmentId is falsy.
🔒 Suggested guard
- const onSubmit = async (values: CommandFormValues) => {
+ const onSubmit = async (values: CommandFormValues) => {
+ if (!environmentId) return;
const trimmed = values.command.trim();
const command = trimmed === "" ? [] : trimmed.split(/\s+/).filter(Boolean);
await updateCommand.mutateAsync({ environmentId, command });
};
@@
- canSave={isValid && !isSubmitting && hasChanges}
+ canSave={isValid && !isSubmitting && hasChanges && Boolean(environmentId)}Also applies to: 73-97
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@web/apps/dashboard/app/`(app)/[workspaceSlug]/projects/[projectId]/(overview)/settings/components/advanced-settings/command.tsx
around lines 31 - 32, The component currently passes environmentId ?? "" into
CommandForm which allows submitting while the real environmentId is missing;
update CommandForm usage and its submit path to require a truthy environmentId:
disable the Save button and prevent form submission when environmentId is falsy,
show a loading/disabled state until environmentId is available, and
short-circuit the mutation call in the CommandForm submit handler (or the parent
submit proxy) to avoid calling the mutation with an empty string; touch the
CommandForm component and any submit handler/mutation invocation that reads
environmentId (references: CommandForm, environmentId, the form submit
function/mutation) and apply the same guard for the other occurrences around
lines 73–97.
...ug]/projects/[projectId]/(overview)/settings/components/advanced-settings/env-vars/index.tsx
Outdated
Show resolved
Hide resolved
| const { | ||
| register, | ||
| handleSubmit, | ||
| formState: { isValid, isSubmitting, errors }, | ||
| control, | ||
| } = useForm<z.infer<typeof dockerfileSchema>>({ | ||
| resolver: zodResolver(dockerfileSchema), | ||
| mode: "onChange", | ||
| defaultValues: { dockerfile: defaultValue }, | ||
| }); | ||
|
|
||
| const currentDockerfile = useWatch({ control, name: "dockerfile" }); |
There was a problem hiding this comment.
Reset the form when defaultValue changes.
react-hook-form applies defaultValues only on mount, so when settings load the input can stay stale and canSave flips true without user edits, risking an unintended overwrite. Add a reset effect tied to defaultValue.
🐛 Proposed fix
@@
-import { useForm, useWatch } from "react-hook-form";
+import { useEffect } from "react";
+import { useForm, useWatch } from "react-hook-form";
@@
- const {
- register,
- handleSubmit,
- formState: { isValid, isSubmitting, errors },
- control,
- } = useForm<z.infer<typeof dockerfileSchema>>({
+ const {
+ register,
+ handleSubmit,
+ formState: { isValid, isSubmitting, errors },
+ control,
+ reset,
+ } = useForm<z.infer<typeof dockerfileSchema>>({
resolver: zodResolver(dockerfileSchema),
mode: "onChange",
defaultValues: { dockerfile: defaultValue },
});
+ useEffect(() => {
+ reset({ dockerfile: defaultValue });
+ }, [defaultValue, reset]);
+
const currentDockerfile = useWatch({ control, name: "dockerfile" });🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@web/apps/dashboard/app/`(app)/[workspaceSlug]/projects/[projectId]/(overview)/settings/components/build-settings/dockerfile-settings.tsx
around lines 36 - 47, The form's defaultValues are only applied on mount so when
defaultValue changes the UI and canSave state can become stale; inside the
Dockerfile settings component call the reset function returned by useForm
whenever defaultValue changes (e.g., useEffect watching defaultValue and
invoking reset({ dockerfile: defaultValue })) so useWatch/currentDockerfile and
form validity reflect the loaded settings; reference the existing useForm hook
(resolver: zodResolver(dockerfileSchema), defaultValues) and the
currentDockerfile/useWatch to locate where to add the effect.
| const { | ||
| register, | ||
| handleSubmit, | ||
| formState: { isValid, isSubmitting, errors }, | ||
| control, | ||
| } = useForm<z.infer<typeof rootDirectorySchema>>({ | ||
| resolver: zodResolver(rootDirectorySchema), | ||
| mode: "onChange", | ||
| defaultValues: { dockerContext: defaultValue }, | ||
| }); | ||
|
|
||
| const currentDockerContext = useWatch({ control, name: "dockerContext" }); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Verify the file exists and examine its content
fd "root-directory-settings.tsx" web/apps/dashboard
# Check the file size before reading
wc -l web/apps/dashboard/app/\(app\)/\[workspaceSlug\]/projects/\[projectId\]/\(overview\)/settings/components/build-settings/root-directory-settings.tsx 2>/dev/null || find web/apps/dashboard -name "root-directory-settings.tsx" -type fRepository: unkeyed/unkey
Length of output: 340
🏁 Script executed:
#!/bin/bash
# Find the file more reliably
find web/apps/dashboard -name "root-directory-settings.tsx" -type f | head -1Repository: unkeyed/unkey
Length of output: 196
🏁 Script executed:
#!/bin/bash
# Check project structure
ls -la web/apps/dashboard/app/ 2>/dev/null || echo "Path not found, checking alternatives"Repository: unkeyed/unkey
Length of output: 1021
🏁 Script executed:
#!/bin/bash
# Read the file to understand the implementation
cat -n "web/apps/dashboard/app/(app)/[workspaceSlug]/projects/[projectId]/(overview)/settings/components/build-settings/root-directory-settings.tsx"Repository: unkeyed/unkey
Length of output: 4342
🏁 Script executed:
#!/bin/bash
# Also check the referenced default-prefix.tsx from the learning
find web/apps/dashboard -name "default-prefix.tsx" -type fRepository: unkeyed/unkey
Length of output: 153
🏁 Script executed:
#!/bin/bash
# Check the referenced default-prefix.tsx from the learning
cat -n "web/apps/dashboard/app/(app)/[workspaceSlug]/apis/[apiId]/settings/components/default-prefix.tsx" | head -100Repository: unkeyed/unkey
Length of output: 3649
🏁 Script executed:
#!/bin/bash
# Check if there's a useEffect dependency on defaultValue anywhere in the codebase
rg -n "useEffect.*defaultValue|reset.*defaultValue" web/apps/dashboard -g "*.tsx" -A 3Repository: unkeyed/unkey
Length of output: 2297
Reset form state when defaultValue changes to prevent saving stale values.
useForm only applies defaultValues on first render. When the query resolves and defaultValue updates, the form field stays at its initial value, allowing unintended saves. This pattern is already used elsewhere in the codebase for query-driven form values.
🔧 Minimal fix
+import { useEffect } from "react";
import { useForm, useWatch } from "react-hook-form";
const RootDirectoryForm = ({
environmentId,
defaultValue,
}: {
environmentId: string;
defaultValue: string;
}) => {
const utils = trpc.useUtils();
const {
register,
handleSubmit,
formState: { isValid, isSubmitting, errors },
control,
+ reset,
} = useForm<z.infer<typeof rootDirectorySchema>>({
resolver: zodResolver(rootDirectorySchema),
mode: "onChange",
defaultValues: { dockerContext: defaultValue },
});
+ useEffect(() => {
+ reset({ dockerContext: defaultValue });
+ }, [defaultValue, reset]);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const { | |
| register, | |
| handleSubmit, | |
| formState: { isValid, isSubmitting, errors }, | |
| control, | |
| } = useForm<z.infer<typeof rootDirectorySchema>>({ | |
| resolver: zodResolver(rootDirectorySchema), | |
| mode: "onChange", | |
| defaultValues: { dockerContext: defaultValue }, | |
| }); | |
| const currentDockerContext = useWatch({ control, name: "dockerContext" }); | |
| const { | |
| register, | |
| handleSubmit, | |
| formState: { isValid, isSubmitting, errors }, | |
| control, | |
| reset, | |
| } = useForm<z.infer<typeof rootDirectorySchema>>({ | |
| resolver: zodResolver(rootDirectorySchema), | |
| mode: "onChange", | |
| defaultValues: { dockerContext: defaultValue }, | |
| }); | |
| useEffect(() => { | |
| reset({ dockerContext: defaultValue }); | |
| }, [defaultValue, reset]); | |
| const currentDockerContext = useWatch({ control, name: "dockerContext" }); |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@web/apps/dashboard/app/`(app)/[workspaceSlug]/projects/[projectId]/(overview)/settings/components/build-settings/root-directory-settings.tsx
around lines 36 - 47, The form is initialized with useForm({... defaultValues: {
dockerContext: defaultValue }}) but defaultValues only applies on first render
so when the query-provided defaultValue changes you must call the form API to
update state; add a useEffect that watches defaultValue and calls reset({
dockerContext: defaultValue }) (imported from useForm result) to update the
controlled value returned by useWatch and prevent saving stale values —
reference the useForm call, rootDirectorySchema, defaultValue,
useWatch/currentDockerContext, and reset.
| const healthcheck = settingsData?.runtimeSettings?.healthcheck; | ||
| const defaultValues: HealthcheckFormValues = { | ||
| method: healthcheck?.method ?? "GET", | ||
| path: healthcheck?.path ?? "/health", | ||
| interval: healthcheck ? secondsToInterval(healthcheck.intervalSeconds) : "30s", | ||
| }; |
There was a problem hiding this comment.
Allow enabling a healthcheck when none exists.
When runtimeSettings.healthcheck is null, the form seeds defaults but hasChanges is false, so Save is disabled and users can’t enable a healthcheck with the default values.
🔧 Minimal fix
const healthcheck = settingsData?.runtimeSettings?.healthcheck;
+ const hasExistingHealthcheck = Boolean(healthcheck);
const defaultValues: HealthcheckFormValues = {
method: healthcheck?.method ?? "GET",
path: healthcheck?.path ?? "/health",
interval: healthcheck ? secondsToInterval(healthcheck.intervalSeconds) : "30s",
};
- return <HealthcheckForm environmentId={environmentId ?? ""} defaultValues={defaultValues} />;
+ return (
+ <HealthcheckForm
+ environmentId={environmentId ?? ""}
+ defaultValues={defaultValues}
+ hasExistingHealthcheck={hasExistingHealthcheck}
+ />
+ );
};
type HealthcheckFormProps = {
environmentId: string;
defaultValues: HealthcheckFormValues;
+ hasExistingHealthcheck: boolean;
};
-const HealthcheckForm: React.FC<HealthcheckFormProps> = ({ environmentId, defaultValues }) => {
+const HealthcheckForm: React.FC<HealthcheckFormProps> = ({
+ environmentId,
+ defaultValues,
+ hasExistingHealthcheck,
+}) => {
@@
const hasChanges =
currentMethod !== defaultValues.method ||
currentPath !== defaultValues.path ||
currentInterval !== defaultValues.interval;
+ const canSave = isValid && !isSubmitting && (hasChanges || !hasExistingHealthcheck);
@@
- canSave={isValid && !isSubmitting && hasChanges}
+ canSave={canSave}Also applies to: 112-131
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@web/apps/dashboard/app/`(app)/[workspaceSlug]/projects/[projectId]/(overview)/settings/components/runtime-settings/healthcheck/index.tsx
around lines 32 - 37, The form seeds default field values when
runtimeSettings.healthcheck is null but never marks the form as changed, so
users cannot "enable" a healthcheck; fix by adding an existence flag into the
seeded/initial values and include it in the hasChanges comparison: compute const
initialHealthcheckExists = Boolean(healthcheck), include exists:
initialHealthcheckExists in the initial/default HealthcheckFormValues (alongside
method/path/interval using secondsToInterval), ensure the live form state sets
exists: true when the user toggles/enables the healthcheck, and update the
hasChanges check to compare this exists flag as well as method/path/interval so
creating a new healthcheck is detected as a change.
| onSubmit={(e) => e.preventDefault()} | ||
| canSave={isValid && hasChanges} | ||
| isSaving={false} |
There was a problem hiding this comment.
Save is currently a no-op.
Line 69 prevents default submission but doesn’t persist the updated storage value. That means the Save button can be enabled and still do nothing.
Consider wiring this to a backend mutation (or disabling Save until it’s supported).
If you want, I can help wire this to a new storage mutation or add a temporary disabled/“coming soon” state.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@web/apps/dashboard/app/`(app)/[workspaceSlug]/projects/[projectId]/(overview)/settings/components/runtime-settings/storage.tsx
around lines 69 - 71, The form's onSubmit is a no-op so the Save button
(controlled by canSave/isValid/hasChanges) does nothing; replace the noop with a
real submit handler (e.g., implement handleSubmit or wire onSubmit to
submitStorage) that calls the backend mutation (e.g.,
updateProjectStorage/updateStorageMutation), toggles isSaving while awaiting the
request, handles success by updating local state and clearing hasChanges, and
surfaces errors to the UI/logging; if backend work isn't ready, instead disable
the Save action by setting canSave to false or change the button to a "coming
soon" state so enabled Save cannot be clicked.
| z.object({ | ||
| environmentId: z.string(), | ||
| cpuMillicores: z.number(), | ||
| }), |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, verify the file exists and check its content
fd "update-cpu.ts" --type fRepository: unkeyed/unkey
Length of output: 142
🏁 Script executed:
# Check the actual content of the file to confirm the current schema
cat -n "web/apps/dashboard/lib/trpc/routers/deploy/environment-settings/runtime/update-cpu.ts"Repository: unkeyed/unkey
Length of output: 900
🏁 Script executed:
# Search for cpuMillicores usage patterns across the codebase
rg "cpuMillicores" --type ts --type tsx -B 2 -A 2 | head -100Repository: unkeyed/unkey
Length of output: 84
🏁 Script executed:
# Check for database schema or migration files related to cpu_millicores
find . -type f \( -name "*.sql" -o -name "*migration*" -o -name "*schema*" \) | xargs rg "cpu_millicores" 2>/dev/null | head -50Repository: unkeyed/unkey
Length of output: 1061
🏁 Script executed:
# Check zod version in package.json
rg '"zod"' package.jsonRepository: unkeyed/unkey
Length of output: 109
🏁 Script executed:
# Search for other cpuMillicores usages with validation
rg "cpuMillicores" --type ts -B 5 -A 5 | head -200Repository: unkeyed/unkey
Length of output: 17837
🏁 Script executed:
# Look for environment runtime settings schema definition
fd "schema" --type f | xargs rg "environmentRuntimeSettings\|cpuMillicores" -B 2 -A 2 | head -100Repository: unkeyed/unkey
Length of output: 39
🏁 Script executed:
# Check if there are any constants or documentation about valid CPU values
rg "cpu\|CPU\|millicores\|millicor" --type ts --type js -i -B 1 -A 1 | grep -i "min\|max\|valid\|range\|limit" | head -50Repository: unkeyed/unkey
Length of output: 39
Add integer and minimum constraints to cpuMillicores schema validation.
The schema currently accepts floats and negative values, which can persist invalid data into an integer-backed column (floats truncate, negatives are invalid for resource allocation). Per coding guidelines, enforce constraints at the boundary.
Suggested change
- cpuMillicores: z.number(),
+ cpuMillicores: z.number().int().min(1),📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| z.object({ | |
| environmentId: z.string(), | |
| cpuMillicores: z.number(), | |
| }), | |
| z.object({ | |
| environmentId: z.string(), | |
| cpuMillicores: z.number().int().min(1), | |
| }), |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@web/apps/dashboard/lib/trpc/routers/deploy/environment-settings/runtime/update-cpu.ts`
around lines 8 - 11, The schema for the incoming payload accepts floats and
negatives; update the z.object validation for cpuMillicores in update-cpu.ts to
enforce integer and non-negative constraints (e.g., replace z.number() with a
validator that requires integers and a minimum of 0 such as
z.number().int().min(0)) so only valid millicore integers are accepted before
persisting to the integer-backed column.
| ), | ||
| }); | ||
|
|
||
| const currentConfig = (existing?.regionConfig as Record<string, number>) ?? {}; |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, find and examine the schema definition for regionConfig
find . -type f -name "*.ts" -path "*schema*" | head -20Repository: unkeyed/unkey
Length of output: 2341
🏁 Script executed:
# Check the specific file mentioned in the request
rg -n "regionConfig" --type ts -B3 -A3Repository: unkeyed/unkey
Length of output: 8744
🏁 Script executed:
# Examine the update-instances.ts file to understand context
head -60 web/apps/dashboard/lib/trpc/routers/deploy/environment-settings/runtime/update-instances.tsRepository: unkeyed/unkey
Length of output: 1536
Remove the as Record<string, number> assertion to maintain type safety.
The regionConfig is already properly typed in the schema as Record<string, number> with .notNull(), so the assertion is redundant and violates the type safety guideline. Replace it with an explicit type annotation instead.
🛠️ Suggested change
- const currentConfig = (existing?.regionConfig as Record<string, number>) ?? {};
+ const currentConfig: Record<string, number> = existing?.regionConfig ?? {};Per coding guidelines: "Never compromise type safety: No any, no ! (non-null assertion), no as Type."
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const currentConfig = (existing?.regionConfig as Record<string, number>) ?? {}; | |
| const currentConfig: Record<string, number> = existing?.regionConfig ?? {}; |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@web/apps/dashboard/lib/trpc/routers/deploy/environment-settings/runtime/update-instances.ts`
at line 21, Remove the unsafe assertion on regionConfig and give currentConfig
an explicit type instead: replace the `as Record<string, number>` cast on the
expression that uses `existing?.regionConfig` with a type annotation on
`currentConfig` (e.g., declare `currentConfig: Record<string, number> =
existing?.regionConfig ?? {}`) so you keep type-safety while preserving the
fallback behavior; update the assignment that references
`existing`/`regionConfig` and ensure no other `as` or non-null assertions remain
in this statement.
| const regionsEnv = process.env.AVAILABLE_REGIONS ?? ""; | ||
| for (const region of regionsEnv.split(",")) { | ||
| regionConfig[region] = input.replicasPerRegion; | ||
| } |
There was a problem hiding this comment.
Filter empty/whitespace regions when AVAILABLE_REGIONS is unset.
Line 31-34 currently writes an empty string key when AVAILABLE_REGIONS is "" (or has trailing commas). That persists invalid region keys.
🛠️ Suggested change
- const regionsEnv = process.env.AVAILABLE_REGIONS ?? "";
- for (const region of regionsEnv.split(",")) {
+ const regionsEnv = process.env.AVAILABLE_REGIONS ?? "";
+ for (const region of regionsEnv.split(",").map((r) => r.trim()).filter(Boolean)) {
regionConfig[region] = input.replicasPerRegion;
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const regionsEnv = process.env.AVAILABLE_REGIONS ?? ""; | |
| for (const region of regionsEnv.split(",")) { | |
| regionConfig[region] = input.replicasPerRegion; | |
| } | |
| const regionsEnv = process.env.AVAILABLE_REGIONS ?? ""; | |
| for (const region of regionsEnv.split(",").map((r) => r.trim()).filter(Boolean)) { | |
| regionConfig[region] = input.replicasPerRegion; | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@web/apps/dashboard/lib/trpc/routers/deploy/environment-settings/runtime/update-instances.ts`
around lines 31 - 34, The loop that builds regionConfig uses
process.env.AVAILABLE_REGIONS and splits on commas but can insert an
empty-string key when AVAILABLE_REGIONS is "" or contains extra commas; update
the logic that iterates regions so you trim each entry and skip empty/whitespace
entries (e.g., use regionsEnv.split(",").map(r=>r.trim()).filter(Boolean) or
equivalent) before assigning regionConfig[region] = input.replicasPerRegion;
ensure the change is applied where regionConfig, process.env.AVAILABLE_REGIONS,
and input.replicasPerRegion are referenced.
| z.object({ | ||
| environmentId: z.string(), | ||
| memoryMib: z.number(), | ||
| }), |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Find existing memory bounds/options in dashboard
echo "=== Searching for memory configuration in dashboard ==="
rg -n "memory" web/apps/dashboard/app -g '*.tsx' -g '*.ts' | head -30
echo -e "\n=== Searching for memoryMib usage ==="
rg -rn "memoryMib" web/apps/dashboard/lib -g '*.ts' -g '*.tsx'
echo -e "\n=== Searching for memoryMib in the entire apps/dashboard ==="
rg -rn "memoryMib" web/apps/dashboard -g '*.ts' -g '*.tsx'Repository: unkeyed/unkey
Length of output: 9357
🏁 Script executed:
#!/bin/bash
# Check the actual file under review
echo "=== Checking update-memory.ts ==="
if [ -f "web/apps/dashboard/lib/trpc/routers/deploy/environment-settings/runtime/update-memory.ts" ]; then
cat -n "web/apps/dashboard/lib/trpc/routers/deploy/environment-settings/runtime/update-memory.ts"
else
echo "File not found at specified path"
find web -name "update-memory.ts" -type f
fiRepository: unkeyed/unkey
Length of output: 925
🏁 Script executed:
#!/bin/bash
# Search for validation patterns with z.number() in similar contexts
echo "=== Searching for similar z.number() validation patterns ==="
rg -rn "z\.number\(\)" web/apps/dashboard/lib/trpc -g '*.ts' | head -20
echo -e "\n=== Searching for .positive() usage pattern ==="
rg -rn "\.positive\(\)" web/apps/dashboard -g '*.ts' -g '*.tsx' | head -20Repository: unkeyed/unkey
Length of output: 2060
🏁 Script executed:
#!/bin/bash
# Look for any database schema that defines memoryMib column
echo "=== Searching for memoryMib in schema/database definitions ==="
rg -rn "memoryMib" web -g '*.ts' -g '*.prisma' -g '*.sql'Repository: unkeyed/unkey
Length of output: 1705
🏁 Script executed:
#!/bin/bash
# Check the client-side memory form validation
echo "=== Full memory.tsx validation schema ==="
if [ -f "web/apps/dashboard/app/(app)/[workspaceSlug]/projects/[projectId]/(overview)/settings/components/runtime-settings/memory.tsx" ]; then
head -50 "web/apps/dashboard/app/(app)/[workspaceSlug]/projects/[projectId]/(overview)/settings/components/runtime-settings/memory.tsx"
else
echo "File not found"
fi
echo -e "\n=== Check for any memory bounds configuration ==="
rg -rn "MEMORY_OPTIONS\|memory.*min\|memory.*max" web/apps/dashboard -g '*.ts' -g '*.tsx' -A 3Repository: unkeyed/unkey
Length of output: 1752
🏁 Script executed:
#!/bin/bash
# Check the complete memory.tsx file to see the slider and how it enforces bounds
echo "=== Complete memorySchema and related validation ==="
sed -n '27,35p' "web/apps/dashboard/app/(app)/[workspaceSlug]/projects/[projectId]/(overview)/settings/components/runtime-settings/memory.tsx"
echo -e "\n=== Check slider implementation and any client-side bounds ==="
rg -rn "Slider.*memory\|valueToIndex\|indexToValue" "web/apps/dashboard/app/(app)/[workspaceSlug]/projects/[projectId]/(overview)/settings/components/runtime-settings/memory.tsx" -A 5 -B 2
echo -e "\n=== Check if there's server-side validation elsewhere ==="
rg -rn "environmentRuntimeSettings\|memoryMib" web/apps/dashboard/lib/trpc/routers/deploy/environment-settings -g '*.ts' -A 3Repository: unkeyed/unkey
Length of output: 466
🏁 Script executed:
#!/bin/bash
# Get the complete memory.tsx file to understand the full validation picture
echo "=== Full memory.tsx file ==="
cat "web/apps/dashboard/app/(app)/[workspaceSlug]/projects/[projectId]/(overview)/settings/components/runtime-settings/memory.tsx"Repository: unkeyed/unkey
Length of output: 5665
🏁 Script executed:
#!/bin/bash
# Check the update mutation call in environment-settings router
echo "=== Environment settings runtime folder content ==="
ls -la web/apps/dashboard/lib/trpc/routers/deploy/environment-settings/runtime/
echo -e "\n=== Check all mutations in environment-settings ==="
find web/apps/dashboard/lib/trpc/routers/deploy/environment-settings -name "*.ts" -exec echo "=== {} ===" \; -exec head -20 {} \;Repository: unkeyed/unkey
Length of output: 8843
🏁 Script executed:
#!/bin/bash
# Check the slider-utils to understand how memory bounds are managed
echo "=== slider-utils implementation ==="
cat web/apps/dashboard/app/\(app\)/\[workspaceSlug\]/projects/\[projectId\]/\(overview\)/settings/components/runtime-settings/shared/slider-utils.ts 2>/dev/null || \
find web/apps/dashboard -name "slider-utils.ts" -exec cat {} \;Repository: unkeyed/unkey
Length of output: 531
Validate memoryMib as a positive integer.
The DB column is an int, but the schema accepts any number (including negatives and decimals). This allows invalid runtime settings to bypass server-side validation despite UI constraints. Similar mutations in the same file (updatePort, updateInstances) enforce proper boundary validation.
🛠️ Suggested change
- memoryMib: z.number(),
+ memoryMib: z.number().int().min(1),Per coding guidelines, input validation at boundaries should make illegal states unrepresentable.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| z.object({ | |
| environmentId: z.string(), | |
| memoryMib: z.number(), | |
| }), | |
| z.object({ | |
| environmentId: z.string(), | |
| memoryMib: z.number().int().min(1), | |
| }), |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@web/apps/dashboard/lib/trpc/routers/deploy/environment-settings/runtime/update-memory.ts`
around lines 8 - 11, The zod schema for the update-memory mutation currently
accepts any number for memoryMib; change the schema entry so memoryMib is
validated as a positive integer (e.g., z.number().int().positive().min(1) or
z.number().int().positive()) to prevent negatives and decimals from passing;
update the z.object that contains { environmentId: z.string(), memoryMib:
z.number() } in update-memory.ts to use the stricter zod chain for memoryMib so
server-side validation matches the DB int constraint and mirrors the checks used
in updatePort/updateInstances.
There was a problem hiding this comment.
Actionable comments posted: 7
🧹 Nitpick comments (1)
web/apps/dashboard/app/(app)/[workspaceSlug]/projects/[projectId]/(overview)/settings/components/advanced-settings/custom-domains/index.tsx (1)
66-70: Duplicate domain check is case-sensitive and doesn't trim stored values.The check at line 68 compares the trimmed input against
d.domainwhich may not be trimmed or normalized. Domain names are case-insensitive by DNS standards, soAPI.example.comandapi.example.comwould both be allowed but resolve identically.♻️ Proposed fix for case-insensitive comparison
const onSubmit = (values: CustomDomainFormValues) => { const trimmedDomain = values.domain.trim(); - if (customDomains.some((d) => d.domain === trimmedDomain)) { + const normalizedDomain = trimmedDomain.toLowerCase(); + if (customDomains.some((d) => d.domain.toLowerCase() === normalizedDomain)) { setError("domain", { message: "Domain already registered" }); return; } collection.customDomains.insert({ id: crypto.randomUUID(), - domain: trimmedDomain, + domain: normalizedDomain,🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/apps/dashboard/app/`(app)/[workspaceSlug]/projects/[projectId]/(overview)/settings/components/advanced-settings/custom-domains/index.tsx around lines 66 - 70, The duplicate-domain check in onSubmit uses trimmedDomain but compares against raw stored values in customDomains, causing case- and whitespace-sensitive misses; update the check to normalize both sides by trimming and lowercasing (e.g., compare trimmedDomain.toLowerCase() against each d.domain.trim().toLowerCase()) and ensure wherever you add a new domain you store the normalized form (trimmed/lowercased) so future checks use the same canonical representation; reference onSubmit, customDomains, trimmedDomain, and setError when making the change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In
`@web/apps/dashboard/app/`(app)/[workspaceSlug]/projects/[projectId]/(overview)/settings/components/advanced-settings/custom-domains/index.tsx:
- Around line 24-25: The current assignment of defaultEnvironmentId uses an
empty string fallback which violates the schema (z.string().min(1)) when
environments is empty; change the logic so you do not produce "" — instead
detect environments.length === 0 and short-circuit the component to show a "no
environments" state or disable the domain form, and set defaultEnvironmentId to
undefined/null (or omit its use) when there are no environments; update any
consumers of defaultEnvironmentId (the variable named defaultEnvironmentId and
the environments array) to handle the absent value so the form and zod
validation are never invoked with an empty string.
- Around line 72-89: Replace the hardcoded workspaceId="" when inserting a
custom domain by retrieving workspace.id from the existing
useWorkspaceNavigation() hook in this component (use the same pattern in
add-custom-domain.tsx where applicable) and pass that value into
collection.customDomains.insert; also wrap the insert call
(collection.customDomains.insert) in a try-catch so you only call reset({
environmentId: values.environmentId, domain: "" }) on success and surface an
error to the user on failure (e.g., via an existing toast/error handler or form
setError) so failures don't silently reset the form.
In
`@web/apps/dashboard/app/`(app)/[workspaceSlug]/projects/[projectId]/(overview)/settings/components/advanced-settings/env-vars/use-decrypted-values.ts:
- Around line 19-41: Create a stable ref for the mutation function and add
per-request error handling: initialize a useRef (e.g. decryptMutationRef) and
update decryptMutationRef.current = decryptMutation.mutateAsync whenever
decryptMutation changes; inside the useEffect that watches envData, call
decryptMutationRef.current instead of decryptMutation.mutateAsync so you don't
add the unstable mutate function to the dependency array; wrap each decrypt call
in try/catch (or map to an async function that returns undefined on error) so
failed decryptions are logged/ignored and only successful [envVarId, value]
tuples are collected, then call
setDecryptedValues(Object.fromEntries(successfulEntries)) and ensure
setIsDecrypting(true)/finally setIsDecrypting(false) remains.
In
`@web/apps/dashboard/app/`(app)/[workspaceSlug]/projects/[projectId]/(overview)/settings/components/advanced-settings/env-vars/utils.ts:
- Around line 7-27: The computeEnvVarsDiff function uses multiple `as string`
casts and has lint errors from single-line returns; replace casts by introducing
a narrow type (e.g., EnvVarWithId) and a type predicate function (e.g., hasId)
that filters EnvVarItem[] into EnvVarWithId[], then use that predicate when
building originalVars, originalIds, originalMap and currentIds; also change the
single-line arrow-return expressions in the current filter callbacks (the ones
computing toCreate, toUpdate and any early returns in the v.id checks) into
block statements with explicit return statements to satisfy the linter, and
ensure toUpdate is typed as EnvVarWithId[] so downstream code no longer needs
casts.
In
`@web/apps/dashboard/app/`(app)/[workspaceSlug]/projects/[projectId]/(overview)/settings/components/runtime-settings/cpu.tsx:
- Around line 16-25: CPU_OPTIONS is missing the 768 (3/4 vCPU) entry which
causes valueToIndex to fallback to index 0 when the backend returns 768; add the
option { label: "3/4 vCPU", value: 768 } into the CPU_OPTIONS array between the
entries for 512 and 1024 so parseCpuDisplay/formatCpu values are represented and
the slider maps correctly via valueToIndex.
- Around line 27-29: The cpuSchema currently allows any number; change it to
only accept the allowed CPU values by validating against the set
{256,512,1024,2048,4096,8192,16384,32768}—for example replace z.number() in
cpuSchema with a validator that enforces membership (either a z.union of
z.literal(...) for each allowed numeric value or z.number().refine(val =>
allowedSet.has(val), { message: "..."})); update the error message to clearly
state allowed CPU values so invalid backend data is rejected when parsing in
cpuSchema.
- Around line 33-45: The Cpu component must early-return when no environmentId
is available to avoid passing undefined to CpuForm and prevent downstream
mutations from running; update Cpu (which uses useProjectData -> environments
and trpc.deploy.environmentSettings.get.useQuery) to check if
environments[0]?.id is falsy and return null (or a placeholder) before calling
trpc or rendering CpuForm, and ensure you only call
trpc.deploy.environmentSettings.get.useQuery with a defined environmentId (keep
the enabled flag) and pass a string to CpuForm (defaultCpu can remain as
computed).
---
Duplicate comments:
In
`@web/apps/dashboard/app/`(app)/[workspaceSlug]/projects/[projectId]/(overview)/settings/components/advanced-settings/env-vars/index.tsx:
- Around line 129-142: Remove the remaining "as string" cast on v.id inside the
toUpdate.map call and instead narrow the type safely: ensure the array
`toUpdate` (produced by `computeEnvVarsDiff`) is typed to include only items
with an `id` (e.g., change `computeEnvVarsDiff.toUpdate` return type to
`EnvVarWithId[]`) or filter/narrow with a user-defined type guard (e.g.,
`hasId`) before calling `updateMutation.mutateAsync`; then call
`updateMutation.mutateAsync({ envVarId: v.id, key: v.key, value: v.value, type:
toTrpcType(v.secret) })` without any `as` cast so TypeScript knows `v.id` is a
string.
---
Nitpick comments:
In
`@web/apps/dashboard/app/`(app)/[workspaceSlug]/projects/[projectId]/(overview)/settings/components/advanced-settings/custom-domains/index.tsx:
- Around line 66-70: The duplicate-domain check in onSubmit uses trimmedDomain
but compares against raw stored values in customDomains, causing case- and
whitespace-sensitive misses; update the check to normalize both sides by
trimming and lowercasing (e.g., compare trimmedDomain.toLowerCase() against each
d.domain.trim().toLowerCase()) and ensure wherever you add a new domain you
store the normalized form (trimmed/lowercased) so future checks use the same
canonical representation; reference onSubmit, customDomains, trimmedDomain, and
setError when making the change.
| const defaultEnvironmentId = | ||
| environments.find((e) => e.slug === "production")?.id ?? environments[0]?.id ?? ""; |
There was a problem hiding this comment.
Empty string fallback when no environments exist could cause issues.
If environments is empty, defaultEnvironmentId becomes "", which will fail schema validation (z.string().min(1)). Consider handling the empty environments case explicitly in the UI rather than silently defaulting to an invalid state.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@web/apps/dashboard/app/`(app)/[workspaceSlug]/projects/[projectId]/(overview)/settings/components/advanced-settings/custom-domains/index.tsx
around lines 24 - 25, The current assignment of defaultEnvironmentId uses an
empty string fallback which violates the schema (z.string().min(1)) when
environments is empty; change the logic so you do not produce "" — instead
detect environments.length === 0 and short-circuit the component to show a "no
environments" state or disable the domain form, and set defaultEnvironmentId to
undefined/null (or omit its use) when there are no environments; update any
consumers of defaultEnvironmentId (the variable named defaultEnvironmentId and
the environments array) to handle the absent value so the form and zod
validation are never invoked with an empty string.
| collection.customDomains.insert({ | ||
| id: crypto.randomUUID(), | ||
| domain: trimmedDomain, | ||
| workspaceId: "", | ||
| projectId, | ||
| environmentId: values.environmentId, | ||
| verificationStatus: "pending", | ||
| verificationToken: "", | ||
| ownershipVerified: false, | ||
| cnameVerified: false, | ||
| targetCname: "", | ||
| checkAttempts: 0, | ||
| lastCheckedAt: null, | ||
| verificationError: null, | ||
| createdAt: Date.now(), | ||
| updatedAt: null, | ||
| }); | ||
| reset({ environmentId: values.environmentId, domain: "" }); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# First, check the actual content around lines 72-89
echo "=== File content at lines 72-89 ==="
cat -n web/apps/dashboard/app/\(app\)/\[workspaceSlug\]/projects/\[projectId\]/\(overview\)/settings/components/advanced-settings/custom-domains/index.tsx | sed -n '70,92p'
# Check file size to determine if we can read the whole file
echo -e "\n=== File line count ==="
wc -l web/apps/dashboard/app/\(app\)/\[workspaceSlug\]/projects/\[projectId\]/\(overview\)/settings/components/advanced-settings/custom-domains/index.tsx
# Check imports and initial context
echo -e "\n=== First 50 lines for context ==="
head -50 web/apps/dashboard/app/\(app\)/\[workspaceSlug\]/projects/\[projectId\]/\(overview\)/settings/components/advanced-settings/custom-domains/index.tsxRepository: unkeyed/unkey
Length of output: 2485
🏁 Script executed:
#!/bin/bash
# Check data-provider.tsx to see what context is available
echo "=== Data provider structure ==="
cat -n web/apps/dashboard/app/\(app\)/\[workspaceSlug\]/projects/\[projectId\]/\(overview\)/data-provider.tsx | head -80
# Check for ProjectDataContext definition
echo -e "\n=== Search for ProjectDataContext ==="
rg -n "ProjectDataContext\|export const useProjectData\|workspaceId" web/apps/dashboard/app/\(app\)/\[workspaceSlug\]/projects/\[projectId\]/\(overview\)/data-provider.tsx | head -30Repository: unkeyed/unkey
Length of output: 3289
🏁 Script executed:
#!/bin/bash
# Search for useProjectData hook implementation
echo "=== Search for useProjectData implementation ==="
rg -n "const useProjectData|export.*useProjectData" web/apps/dashboard/app/\(app\)/\[workspaceSlug\]/projects/\[projectId\]/ -A 5
# Search for how workspaceId is typically accessed in similar files
echo -e "\n=== Search workspaceId usage in workspace context ==="
rg -n "useWorkspace\|workspaceId.*useParams\|useParams.*workspace" web/apps/dashboard/app/\(app\)/\[workspaceSlug\]/ -t tsx | head -20Repository: unkeyed/unkey
Length of output: 1006
🏁 Script executed:
#!/bin/bash
# Check Project type definition to see if it has workspaceId
echo "=== Check Project type ==="
rg -n "type Project|interface Project" web/apps/dashboard/lib/collections/deploy/ -A 15 | head -40
# Check if useWorkspace hook is available
echo -e "\n=== Search for useWorkspace hook ==="
rg -n "export.*useWorkspace|const useWorkspace" web/apps/dashboard/ -t ts --max-count=10
# Check if there's workspace context/provider
echo -e "\n=== Search for workspace provider or context ==="
rg -n "WorkspaceContext\|useWorkspace" web/apps/dashboard/app/\(app\)/\[workspaceSlug\]/data-provider.tsx | head -20
# Check the full project query to see all fields
echo -e "\n=== Check what project object contains ==="
rg -n "from.*project\|project:" web/apps/dashboard/app/\(app\)/\[workspaceSlug\]/projects/\[projectId\]/\(overview\)/data-provider.tsx | head -20Repository: unkeyed/unkey
Length of output: 2122
🏁 Script executed:
#!/bin/bash
# Check if workspaceId is passed through params
echo "=== Check params and workspaceSlug handling ==="
sed -n '42,50p' web/apps/dashboard/app/\(app\)/\[workspaceSlug\]/projects/\[projectId\]/\(overview\)/data-provider.tsx
# Check if error handling is present with fault library
echo -e "\n=== Check for fault library import and error handling ==="
grep -n "fault\|try\|catch\|error" web/apps/dashboard/app/\(app\)/\[workspaceSlug\]/projects/\[projectId\]/\(overview\)/settings/components/advanced-settings/custom-domains/index.tsx | head -20
# Check how other insert operations are done nearby
echo -e "\n=== Check collection.customDomains usage pattern ==="
rg -n "collection\.customDomains" web/apps/dashboard/app/\(app\)/\[workspaceSlug\]/projects/\[projectId\]/ -B 3 -A 3Repository: unkeyed/unkey
Length of output: 7235
🏁 Script executed:
#!/bin/bash
# Check the Project schema definition
echo "=== Check Project schema ==="
sed -n '1,40p' web/apps/dashboard/lib/collections/deploy/projects.ts
# Check useWorkspace hook
echo -e "\n=== Check useWorkspace hook ==="
head -50 web/apps/dashboard/providers/workspace-provider.tsx
# Check CustomDomain type to verify workspaceId field exists
echo -e "\n=== Check CustomDomain type ==="
rg -n "type CustomDomain|interface CustomDomain" web/apps/dashboard/lib/collections/deploy/ -A 20Repository: unkeyed/unkey
Length of output: 5022
🏁 Script executed:
#!/bin/bash
# Check if workspaceSlug is extracted from params in the data provider
echo "=== Check if workspaceSlug is extracted ==="
cat web/apps/dashboard/app/\(app\)/\[workspaceSlug\]/projects/\[projectId\]/\(overview\)/data-provider.tsx | head -60 | tail -20
# Check what happens in handleSubmit - look at full implementation
echo -e "\n=== Check full handleSubmit implementation ==="
sed -n '50,95p' web/apps/dashboard/app/\(app\)/\[workspaceSlug\]/projects/\[projectId\]/\(overview\)/settings/components/advanced-settings/custom-domains/index.tsxRepository: unkeyed/unkey
Length of output: 2023
🏁 Script executed:
#!/bin/bash
# Get the CustomDomain schema definition
echo "=== Check CustomDomain schema ==="
sed -n '1,30p' web/apps/dashboard/lib/collections/deploy/custom-domains.ts
# See if there's a separate workspace data provider
echo -e "\n=== Check if workspace is available in ProjectDataProvider ==="
sed -n '42,52p' web/apps/dashboard/app/\(app\)/\[workspaceSlug\]/projects/\[projectId\]/\(overview\)/data-provider.tsx
# Check useWorkspace availability in the component or its parent
echo -e "\n=== Check file structure to see where useWorkspace could be used ==="
rg -n "useWorkspace|useParams" web/apps/dashboard/app/\(app\)/\[workspaceSlug\]/projects/\[projectId\]/\(overview\)/settings/components/advanced-settings/custom-domains/index.tsxRepository: unkeyed/unkey
Length of output: 1612
🏁 Script executed:
#!/bin/bash
# Check the add-custom-domain.tsx to see if it has the same issue
echo "=== Check add-custom-domain.tsx for comparison ==="
sed -n '85,100p' web/apps/dashboard/app/\(app\)/\[workspaceSlug\]/projects/\[projectId\]/\(overview\)/details/custom-domains-section/add-custom-domain.tsx
# Check if that file uses useWorkspace
echo -e "\n=== Check add-custom-domain.tsx imports and hooks ==="
head -30 web/apps/dashboard/app/\(app\)/\[workspaceSlug\]/projects/\[projectId\]/\(overview\)/details/custom-domains-section/add-custom-domain.tsxRepository: unkeyed/unkey
Length of output: 1296
🏁 Script executed:
#!/bin/bash
# Check what the actual file imports
echo "=== Check all imports in custom-domains/index.tsx ==="
sed -n '1,25p' web/apps/dashboard/app/\(app\)/\[workspaceSlug\]/projects/\[projectId\]/\(overview\)/settings/components/advanced-settings/custom-domains/index.tsx
# Check if workspace is available through any parent context
echo -e "\n=== Check parent settings layout ==="
ls -la web/apps/dashboard/app/\(app\)/\[workspaceSlug\]/projects/\[projectId\]/\(overview\)/settings/
# Check if there's a workspace available through layout.tsx or root provider
echo -e "\n=== Check layout.tsx in settings folder ==="
cat web/apps/dashboard/app/\(app\)/\[workspaceSlug\]/projects/\[projectId\]/\(overview\)/settings/layout.tsx 2>/dev/null | head -30
# Look for any other component that accesses workspaceId correctly
echo -e "\n=== Search for correct workspaceId usage in similar contexts ==="
rg -n "workspaceId.*useWorkspace\|useWorkspace.*workspaceId" web/apps/dashboard/app/\(app\)/\[workspaceSlug\]/ --type tsx -B 2 -A 2 | head -30Repository: unkeyed/unkey
Length of output: 1439
🏁 Script executed:
#!/bin/bash
# Check if the workspace can be extracted from useParams
echo "=== Check if useParams extracts workspaceSlug ==="
rg -n "useParams\(\)" web/apps/dashboard/app/\(app\)/\[workspaceSlug\]/ --type tsx -A 3 | head -40Repository: unkeyed/unkey
Length of output: 151
🏁 Script executed:
#!/bin/bash
# Search for workspaceId usage without type flag
echo "=== Search for workspaceId access patterns ==="
rg -n "workspaceId" web/apps/dashboard/app/\(app\)/\[workspaceSlug\]/projects/ -A 2 -B 2 | head -50
# Check if useWorkspace is used in the settings area
echo -e "\n=== Check useWorkspace usage in settings components ==="
rg -n "useWorkspace" web/apps/dashboard/app/\(app\)/\[workspaceSlug\]/projects/\[projectId\]/\(overview\)/settings/ | head -20
# Check data provider to see all params being used
echo -e "\n=== Check all params in ProjectDataProvider ==="
sed -n '42,48p' web/apps/dashboard/app/\(app\)/\[workspaceSlug\]/projects/\[projectId\]/\(overview\)/data-provider.tsxRepository: unkeyed/unkey
Length of output: 2122
🏁 Script executed:
#!/bin/bash
# Try to find how workspace is provided to project components
echo "=== Check root workspace provider/context in this area ==="
find web/apps/dashboard/app/\(app\)/\[workspaceSlug\] -name "layout.tsx" -o -name "data-provider.tsx" | head -10
# Check what's available in the parent layout
echo -e "\n=== Check workspace slug parent ==="
ls -la web/apps/dashboard/app/\(app\)/\[workspaceSlug\]/
# Look for workspace context usage in similar insert operations
echo -e "\n=== Check other insert operations ==="
rg -n "collection\..+\.insert" web/apps/dashboard/app/\(app\)/\[workspaceSlug\]/ -B 5 | head -60Repository: unkeyed/unkey
Length of output: 4939
🏁 Script executed:
#!/bin/bash
# Check the workspace layout to see if workspace is provided there
echo "=== Check workspace layout.tsx ==="
cat web/apps/dashboard/app/\(app\)/\[workspaceSlug\]/layout.tsx
# Check if workspace can be obtained from useWorkspace hook in components
echo -e "\n=== Check how workspace is typically accessed ==="
rg -n "const.*workspace.*useWorkspace\|useWorkspace.*workspace" web/apps/dashboard/app/\(app\)/\[workspaceSlug\]/ -B 2 -A 2 | head -40
# Check the ratelimit override insert to see if it has workspaceId
echo -e "\n=== Check ratelimit override insert pattern ==="
sed -n '76,95p' web/apps/dashboard/app/\(app\)/\[workspaceSlug\]/ratelimits/\[namespaceId\]/_components/identifier-dialog.tsxRepository: unkeyed/unkey
Length of output: 1636
🏁 Script executed:
#!/bin/bash
# Check if there's an existing pattern for getting workspaceId in this component tree
echo "=== Check if other components in projects access workspace ==="
rg -n "useWorkspace" web/apps/dashboard/app/\(app\)/\[workspaceSlug\]/projects/ -B 2 -A 5 | head -50
# Check CustomDomainRow to see if it accesses workspace
echo -e "\n=== Check CustomDomainRow implementation ==="
head -40 web/apps/dashboard/app/\(app\)/\[workspaceSlug\]/projects/\[projectId\]/\(overview\)/settings/components/advanced-settings/custom-domains/custom-domain-row.tsxRepository: unkeyed/unkey
Length of output: 9095
🏁 Script executed:
#!/bin/bash
# Check the useWorkspaceNavigation hook definition
echo "=== Check useWorkspaceNavigation hook ==="
cat web/apps/dashboard/hooks/use-workspace-navigation.tsx
# Verify that this hook is safe to use in these components
echo -e "\n=== Check if hook returns workspace with id ==="
rg -n "workspace\.id\|workspace\.slug" web/apps/dashboard/hooks/use-workspace-navigation.tsx -A 2 -B 2Repository: unkeyed/unkey
Length of output: 1568
🏁 Script executed:
#!/bin/bash
# Check the Workspace type to confirm it has an id field
echo "=== Check Workspace type definition ==="
rg -n "type Workspace|interface Workspace" web/apps/dashboard/ -B 2 -A 15 | head -40
# Confirm the workspace object structure
echo -e "\n=== Check what fields workspace has ==="
rg -n "workspace\." web/apps/dashboard/app/\(app\)/\[workspaceSlug\]/projects/navigation.tsx | head -10Repository: unkeyed/unkey
Length of output: 3947
🏁 Script executed:
#!/bin/bash
# Verify that Workspace from `@unkey/db` has an id field
echo "=== Confirm Workspace type has id field ==="
rg -n "export.*type Workspace\|export.*interface Workspace" web/apps/dashboard/ -A 30 | grep -A 30 "@unkey/db" | head -40Repository: unkeyed/unkey
Length of output: 100
Retrieve workspaceId from the workspace context instead of hardcoding an empty string.
The workspaceId is hardcoded to an empty string on line 75, which will create malformed records. Use the useWorkspaceNavigation() hook (already available in this component tree) to access workspace.id. This same issue also exists in add-custom-domain.tsx line 95.
Additionally, the insert operation lacks error handling. If the insert fails, the form resets anyway, leaving the user unaware of the failure. Wrap the insert in a try-catch block and provide user feedback.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@web/apps/dashboard/app/`(app)/[workspaceSlug]/projects/[projectId]/(overview)/settings/components/advanced-settings/custom-domains/index.tsx
around lines 72 - 89, Replace the hardcoded workspaceId="" when inserting a
custom domain by retrieving workspace.id from the existing
useWorkspaceNavigation() hook in this component (use the same pattern in
add-custom-domain.tsx where applicable) and pass that value into
collection.customDomains.insert; also wrap the insert call
(collection.customDomains.insert) in a try-catch so you only call reset({
environmentId: values.environmentId, domain: "" }) on success and surface an
error to the user on failure (e.g., via an existing toast/error handler or form
setError) so failures don't silently reset the form.
| useEffect(() => { | ||
| if (!envData) { | ||
| return; | ||
| } | ||
|
|
||
| const recoverableVars = envData.variables.filter((v) => v.type === "recoverable"); | ||
| if (recoverableVars.length === 0) { | ||
| return; | ||
| } | ||
|
|
||
| setIsDecrypting(true); | ||
| Promise.all( | ||
| recoverableVars.map((v) => | ||
| decryptMutation.mutateAsync({ envVarId: v.id }).then((r) => [v.id, r.value] as const) | ||
| ) | ||
| ) | ||
| .then((entries) => { | ||
| setDecryptedValues(Object.fromEntries(entries)); | ||
| }) | ||
| .finally(() => { | ||
| setIsDecrypting(false); | ||
| }); | ||
| }, [envData]); |
There was a problem hiding this comment.
Fix lint error and add error handling for decrypt failures.
The pipeline reports a missing dependency (decryptMutation.mutateAsync). Adding it directly would cause infinite re-renders since useMutation returns unstable references. Additionally, failed decryptions are silently ignored, leaving decryptedValues incomplete.
🛠️ Proposed fix using a stable ref
export function useDecryptedValues(envData: EnvData | undefined) {
const decryptMutation = trpc.deploy.envVar.decrypt.useMutation();
+ const decryptRef = React.useRef(decryptMutation.mutateAsync);
+ decryptRef.current = decryptMutation.mutateAsync;
+
const [decryptedValues, setDecryptedValues] = useState<Record<string, string>>({});
const [isDecrypting, setIsDecrypting] = useState(false);
useEffect(() => {
if (!envData) {
return;
}
const recoverableVars = envData.variables.filter((v) => v.type === "recoverable");
if (recoverableVars.length === 0) {
return;
}
setIsDecrypting(true);
Promise.all(
recoverableVars.map((v) =>
- decryptMutation.mutateAsync({ envVarId: v.id }).then((r) => [v.id, r.value] as const)
+ decryptRef.current({ envVarId: v.id })
+ .then((r) => [v.id, r.value] as const)
+ .catch(() => null)
)
)
.then((entries) => {
- setDecryptedValues(Object.fromEntries(entries));
+ const validEntries = entries.filter((e): e is [string, string] => e !== null);
+ setDecryptedValues(Object.fromEntries(validEntries));
})
.finally(() => {
setIsDecrypting(false);
});
}, [envData]);🧰 Tools
🪛 GitHub Actions: autofix.ci
[error] 19-19: lint/correctness/useExhaustiveDependencies: useEffect dependency array is missing decryptMutation.mutateAsync.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@web/apps/dashboard/app/`(app)/[workspaceSlug]/projects/[projectId]/(overview)/settings/components/advanced-settings/env-vars/use-decrypted-values.ts
around lines 19 - 41, Create a stable ref for the mutation function and add
per-request error handling: initialize a useRef (e.g. decryptMutationRef) and
update decryptMutationRef.current = decryptMutation.mutateAsync whenever
decryptMutation changes; inside the useEffect that watches envData, call
decryptMutationRef.current instead of decryptMutation.mutateAsync so you don't
add the unstable mutate function to the dependency array; wrap each decrypt call
in try/catch (or map to an async function that returns undefined on error) so
failed decryptions are logged/ignored and only successful [envVarId, value]
tuples are collected, then call
setDecryptedValues(Object.fromEntries(successfulEntries)) and ensure
setIsDecrypting(true)/finally setIsDecrypting(false) remains.
| export function computeEnvVarsDiff(original: EnvVarItem[], current: EnvVarItem[]) { | ||
| const originalVars = original.filter((v) => v.id); | ||
| const originalIds = new Set(originalVars.map((v) => v.id as string)); | ||
| const originalMap = new Map(originalVars.map((v) => [v.id as string, v])); | ||
|
|
||
| const currentIds = new Set(current.filter((v) => v.id).map((v) => v.id as string)); | ||
|
|
||
| const toDelete = [...originalIds].filter((id) => !currentIds.has(id)); | ||
|
|
||
| const toCreate = current.filter((v) => !v.id && v.key !== "" && v.value !== ""); | ||
|
|
||
| const toUpdate = current.filter((v) => { | ||
| if (!v.id) return false; | ||
| const orig = originalMap.get(v.id); | ||
| if (!orig) return false; | ||
| if (v.value === "") return false; | ||
| return v.key !== orig.key || v.value !== orig.value || v.secret !== orig.secret; | ||
| }); | ||
|
|
||
| return { toDelete, toCreate, toUpdate, originalMap }; | ||
| } |
There was a problem hiding this comment.
Remove as string casts and fix block statement lint errors.
Multiple as string casts violate coding guidelines. Additionally, the pipeline reports lint errors for missing block statements on lines 19, 21, and 22. Use a type predicate to narrow types properly.
🛡️ Proposed fix with type predicate and block statements
import type { EnvVarsFormValues } from "./schema";
export type EnvVarItem = EnvVarsFormValues["envVars"][number];
+export type EnvVarWithId = EnvVarItem & { id: string };
+
+const hasId = (v: EnvVarItem): v is EnvVarWithId => Boolean(v.id);
export const toTrpcType = (secret: boolean) => (secret ? "writeonly" : "recoverable");
export function computeEnvVarsDiff(original: EnvVarItem[], current: EnvVarItem[]) {
- const originalVars = original.filter((v) => v.id);
- const originalIds = new Set(originalVars.map((v) => v.id as string));
- const originalMap = new Map(originalVars.map((v) => [v.id as string, v]));
+ const originalVars = original.filter(hasId);
+ const originalIds = new Set(originalVars.map((v) => v.id));
+ const originalMap = new Map<string, EnvVarWithId>(originalVars.map((v) => [v.id, v]));
- const currentIds = new Set(current.filter((v) => v.id).map((v) => v.id as string));
+ const currentIds = new Set(current.filter(hasId).map((v) => v.id));
const toDelete = [...originalIds].filter((id) => !currentIds.has(id));
const toCreate = current.filter((v) => !v.id && v.key !== "" && v.value !== "");
- const toUpdate = current.filter((v) => {
- if (!v.id) return false;
+ const toUpdate = current.filter((v): v is EnvVarWithId => {
+ if (!v.id) {
+ return false;
+ }
const orig = originalMap.get(v.id);
- if (!orig) return false;
- if (v.value === "") return false;
+ if (!orig) {
+ return false;
+ }
+ if (v.value === "") {
+ return false;
+ }
return v.key !== orig.key || v.value !== orig.value || v.secret !== orig.secret;
});
return { toDelete, toCreate, toUpdate, originalMap };
}This fix:
- Introduces
EnvVarWithIdtype andhasIdtype predicate to eliminate allas stringcasts - Wraps single-line returns in block statements to satisfy the linter
- Returns
toUpdatewith properEnvVarWithId[]type, which will also fix the downstream cast inindex.tsx
As per coding guidelines: "Never compromise type safety: No any, no ! (non-null assertion), no as Type".
🧰 Tools
🪛 GitHub Actions: autofix.ci
[error] 19-19: lint/style/useBlockStatements: Block statements are preferred here. (Unsafe fix suggested: wrap in a JsBlockStatement)
[error] 21-21: lint/style/useBlockStatements: Block statements are preferred here. (Unsafe fix suggested: wrap in a JsBlockStatement)
[error] 22-22: lint/style/useBlockStatements: Block statements are preferred here. (Unsafe fix suggested: wrap in a JsBlockStatement)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@web/apps/dashboard/app/`(app)/[workspaceSlug]/projects/[projectId]/(overview)/settings/components/advanced-settings/env-vars/utils.ts
around lines 7 - 27, The computeEnvVarsDiff function uses multiple `as string`
casts and has lint errors from single-line returns; replace casts by introducing
a narrow type (e.g., EnvVarWithId) and a type predicate function (e.g., hasId)
that filters EnvVarItem[] into EnvVarWithId[], then use that predicate when
building originalVars, originalIds, originalMap and currentIds; also change the
single-line arrow-return expressions in the current filter callbacks (the ones
computing toCreate, toUpdate and any early returns in the v.id checks) into
block statements with explicit return statements to satisfy the linter, and
ensure toUpdate is typed as EnvVarWithId[] so downstream code no longer needs
casts.
| const CPU_OPTIONS = [ | ||
| { label: "1/4 vCPU", value: 256 }, | ||
| { label: "1/2 vCPU", value: 512 }, | ||
| { label: "1 vCPU", value: 1024 }, | ||
| { label: "2 vCPU", value: 2048 }, | ||
| { label: "4 vCPU", value: 4096 }, | ||
| { label: "8 vCPU", value: 8192 }, | ||
| { label: "16 vCPU", value: 16384 }, | ||
| { label: "32 vCPU", value: 32768 }, | ||
| ] as const; |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
cat -n web/apps/dashboard/app/\(app\)/\[workspaceSlug\]/projects/\[projectId\]/\(overview\)/settings/components/runtime-settings/cpu.tsx | head -50Repository: unkeyed/unkey
Length of output: 1978
🏁 Script executed:
rg -n "parseCpuDisplay" -g'*.ts' -g'*.tsx' --max-count=10Repository: unkeyed/unkey
Length of output: 432
🏁 Script executed:
rg -n "768" -g'*.ts' -g'*.tsx' --max-count=20Repository: unkeyed/unkey
Length of output: 1252
🏁 Script executed:
rg -n "valueToIndex" -g'*.ts' -g'*.tsx' --max-count=10Repository: unkeyed/unkey
Length of output: 1452
🏁 Script executed:
sed -n '155,180p' web/apps/dashboard/app/\(app\)/\[workspaceSlug\]/projects/\[projectId\]/\(overview\)/settings/components/runtime-settings/cpu.tsxRepository: unkeyed/unkey
Length of output: 489
🏁 Script executed:
cat -n web/apps/dashboard/app/\(app\)/\[workspaceSlug\]/projects/\[projectId\]/\(overview\)/settings/components/shared/slider-utils.tsRepository: unkeyed/unkey
Length of output: 593
🏁 Script executed:
sed -n '1,30p' web/apps/dashboard/lib/utils/deployment-formatters.tsRepository: unkeyed/unkey
Length of output: 662
🏁 Script executed:
rg -n "cpuMillicores" -g'*.ts' -g'*.tsx' -A 3 -B 3 | head -60Repository: unkeyed/unkey
Length of output: 5052
🏁 Script executed:
rg -n "3/4.*vCPU|768" web/apps/dashboard -g'*.ts' -g'*.tsx'Repository: unkeyed/unkey
Length of output: 857
Add 768 to CPU_OPTIONS to match parseCpuDisplay and prevent slider position fallback.
Both parseCpuDisplay and formatCpu (in deployment-formatters.ts) explicitly handle 768 (3/4 vCPU), indicating backend support. However, CPU_OPTIONS omits this value. When the backend returns 768, valueToIndex finds no match and falls back to index 0, incorrectly mapping to 256 vCPU on the slider. Add { label: "3/4 vCPU", value: 768 } to CPU_OPTIONS between 512 and 1024 to maintain consistency.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@web/apps/dashboard/app/`(app)/[workspaceSlug]/projects/[projectId]/(overview)/settings/components/runtime-settings/cpu.tsx
around lines 16 - 25, CPU_OPTIONS is missing the 768 (3/4 vCPU) entry which
causes valueToIndex to fallback to index 0 when the backend returns 768; add the
option { label: "3/4 vCPU", value: 768 } into the CPU_OPTIONS array between the
entries for 512 and 1024 so parseCpuDisplay/formatCpu values are represented and
the slider maps correctly via valueToIndex.
| const cpuSchema = z.object({ | ||
| cpu: z.number(), | ||
| }); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# Find the file and examine its contents
find . -type f -name "cpu.tsx" | grep -E "(runtime-settings|settings)" | head -5Repository: unkeyed/unkey
Length of output: 180
🏁 Script executed:
# Search for CPU_OPTIONS definition
rg "CPU_OPTIONS" -g '*.ts' -g '*.tsx' -B 2 -A 2Repository: unkeyed/unkey
Length of output: 4052
🏁 Script executed:
# Look for the specific file path
fd "cpu.tsx" web/apps/dashboardRepository: unkeyed/unkey
Length of output: 178
🏁 Script executed:
# Read the full cpu.tsx file
cat -n web/apps/dashboard/app/(app)/\[workspaceSlug\]/projects/\[projectId\]/\(overview\)/settings/components/runtime-settings/cpu.tsxRepository: unkeyed/unkey
Length of output: 278
🏁 Script executed:
# Use find to locate and read the file
find . -name "cpu.tsx" -path "*runtime-settings*" -exec cat -n {} \;Repository: unkeyed/unkey
Length of output: 6816
🏁 Script executed:
# Look for similar validation patterns with refine() or enum validation
rg "\.refine\(" -A 3 -B 1 -g '*.tsx' -g '*.ts' | head -40Repository: unkeyed/unkey
Length of output: 3357
🏁 Script executed:
# Search for memory settings to see if they have similar validation
rg -l "memory|Memory" web/apps/dashboard/app/\(app\)/\[workspaceSlug\]/projects/\[projectId\]/\(overview\)/settings/components/runtime-settings/ | head -5Repository: unkeyed/unkey
Length of output: 307
🏁 Script executed:
# Check memory.tsx to see how it handles validation
find . -name "memory.tsx" -path "*runtime-settings*" -exec cat -n {} \;Repository: unkeyed/unkey
Length of output: 6819
🏁 Script executed:
# Check storage.tsx to see the pattern there
find . -name "storage.tsx" -path "*runtime-settings*" -exec cat -n {} \;Repository: unkeyed/unkey
Length of output: 4544
Constrain cpu to valid options to make illegal states unrepresentable.
The schema accepts any number but only specific CPU values are valid (256, 512, 1024, 2048, 4096, 8192, 16384, 32768). While the UI uses a controlled slider to prevent direct user input of invalid values, the schema should validate the actual domain constraints to handle potentially invalid backend data.
const cpuSchema = z.object({
- cpu: z.number(),
+ cpu: z
+ .number()
+ .int()
+ .refine((v) => CPU_OPTIONS.some((o) => o.value === v), {
+ message: "Invalid CPU option",
+ }),
});This aligns with the coding guideline to make illegal states unrepresentable by validating inputs at boundaries into properly typed structures.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@web/apps/dashboard/app/`(app)/[workspaceSlug]/projects/[projectId]/(overview)/settings/components/runtime-settings/cpu.tsx
around lines 27 - 29, The cpuSchema currently allows any number; change it to
only accept the allowed CPU values by validating against the set
{256,512,1024,2048,4096,8192,16384,32768}—for example replace z.number() in
cpuSchema with a validator that enforces membership (either a z.union of
z.literal(...) for each allowed numeric value or z.number().refine(val =>
allowedSet.has(val), { message: "..."})); update the error message to clearly
state allowed CPU values so invalid backend data is rejected when parsing in
cpuSchema.
| export const Cpu = () => { | ||
| const { environments } = useProjectData(); | ||
| const environmentId = environments[0]?.id; | ||
|
|
||
| const { data: settingsData } = trpc.deploy.environmentSettings.get.useQuery( | ||
| { environmentId: environmentId ?? "" }, | ||
| { enabled: Boolean(environmentId) }, | ||
| ); | ||
|
|
||
| const defaultCpu = settingsData?.runtimeSettings?.cpuMillicores ?? 256; | ||
|
|
||
| return <CpuForm environmentId={environmentId} defaultCpu={defaultCpu} />; | ||
| }; |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
fd -t f "cpu.tsx" -x cat -n {}Repository: unkeyed/unkey
Length of output: 6816
🏁 Script executed:
rg -n "CpuForm" --type ts --type tsx -A 3 -B 3Repository: unkeyed/unkey
Length of output: 84
🏁 Script executed:
rg -n "interface CpuFormProps|type CpuFormProps" --type ts --type tsxRepository: unkeyed/unkey
Length of output: 84
🏁 Script executed:
rg -n "useProjectData" --type ts --type tsx -A 10Repository: unkeyed/unkey
Length of output: 84
🏁 Script executed:
rg -n "useProjectData" -A 15 -B 2Repository: unkeyed/unkey
Length of output: 50370
🏁 Script executed:
fd -type f "data-provider" -path "*/dashboard/*"Repository: unkeyed/unkey
Length of output: 227
🏁 Script executed:
rg -n "export.*useProjectData|const useProjectData" -A 20Repository: unkeyed/unkey
Length of output: 959
🏁 Script executed:
cat -n web/apps/dashboard/app/\(app\)/\[workspaceSlug\]/projects/\[projectId\]/\(overview\)/data-provider.tsx | head -160Repository: unkeyed/unkey
Length of output: 6258
🏁 Script executed:
rg -n "ProjectDataContext|environments" web/apps/dashboard/app/\(app\)/\[workspaceSlug\]/projects/\[projectId\]/\(overview\)/data-provider.tsx -A 2 -B 2Repository: unkeyed/unkey
Length of output: 2221
🏁 Script executed:
fd -name "tsconfig.json" | head -5 | xargs catRepository: unkeyed/unkey
Length of output: 287
🏁 Script executed:
rg -n "environmentId: undefined" --type js --type ts -A 2 -B 2Repository: unkeyed/unkey
Length of output: 39
🏁 Script executed:
cat -n web/apps/dashboard/app/\(app\)/\[workspaceSlug\]/projects/\[projectId\]/\(overview\)/settings/components/runtime-settings/memory.tsx | sed -n '30,50p'Repository: unkeyed/unkey
Length of output: 791
🏁 Script executed:
find web/apps/dashboard -name "tsconfig.json" -o -name "tsconfig*.json" | head -3 | xargs -I {} sh -c 'echo "=== {} ===" && cat {}'Repository: unkeyed/unkey
Length of output: 814
🏁 Script executed:
rg "as const" web/apps/dashboard/app/\(app\)/\[workspaceSlug\]/projects/\[projectId\]/\(overview\)/settings/components/runtime-settings/ -lRepository: unkeyed/unkey
Length of output: 565
🏁 Script executed:
rg "environmentId\?" web/apps/dashboard -A 1 -B 1 --max-count=10Repository: unkeyed/unkey
Length of output: 483
🏁 Script executed:
rg "noUncheckedIndexedAccess" web/apps/dashboard/tsconfig.jsonRepository: unkeyed/unkey
Length of output: 39
🏁 Script executed:
cat -n web/apps/dashboard/app/\(app\)/\[workspaceSlug\]/projects/\[projectId\]/\(overview\)/settings/components/runtime-settings/cpu.tsx | sed -n '33,50p'Repository: unkeyed/unkey
Length of output: 697
🏁 Script executed:
rg "// `@ts-ignore`|// `@ts-expect-error`" web/apps/dashboard/app/\(app\)/\[workspaceSlug\]/projects/\[projectId\)/\(overview\)/settings/components/runtime-settings/ -B 3 -A 1Repository: unkeyed/unkey
Length of output: 211
🏁 Script executed:
rg "CpuForm|MemoryForm|RegionsForm" web/apps/dashboard/app/\(app\)/\[workspaceSlug\]/projects/\[projectId\]/\(overview\)/settings/components/runtime-settings/ -A 3 | grep -E "environmentId|return <" | head -20Repository: unkeyed/unkey
Length of output: 2063
🏁 Script executed:
rg -A 5 "type CpuFormProps" web/apps/dashboard/app/\(app\)/\[workspaceSlug\]/projects/\[projectId\]/\(overview\)/settings/components/runtime-settings/cpu.tsxRepository: unkeyed/unkey
Length of output: 206
🏁 Script executed:
rg -B 2 -A 10 "const CpuForm.*React.FC" web/apps/dashboard/app/\(app\)/\[workspaceSlug\]/projects/\[projectId\]/\(overview\)/settings/components/runtime-settings/cpu.tsxRepository: unkeyed/unkey
Length of output: 348
🏁 Script executed:
rg "environmentId" web/apps/dashboard/app/\(app\)/\[workspaceSlug\]/projects/\[projectId\]/\(overview\)/settings/components/runtime-settings/cpu.tsx | grep -E "mutate|mutation|invalidate"Repository: unkeyed/unkey
Length of output: 130
🏁 Script executed:
cat -n web/apps/dashboard/app/\(app\)/\[workspaceSlug\]/projects/\[projectId\]/\(overview\)/settings/components/runtime-settings/cpu.tsx | sed -n '73,105p'Repository: unkeyed/unkey
Length of output: 1396
🏁 Script executed:
rg "environmentId\s*\?" web/apps/dashboard/app/\(app\)/\[workspaceSlug\]/projects/\[projectId\]/\(overview\)/settings/components/runtime-settings/cpu.tsxRepository: unkeyed/unkey
Length of output: 100
🏁 Script executed:
rg "@ts-ignore|@ts-expect-error" web/apps/dashboard/app/\(app\)/\[workspaceSlug\]/projects/\[projectId\]/\(overview\)/settings/components/runtime-settings/cpu.tsxRepository: unkeyed/unkey
Length of output: 39
🏁 Script executed:
git -C web/apps/dashboard log --oneline --all -- "**/cpu.tsx" 2>/dev/null | head -5Repository: unkeyed/unkey
Length of output: 89
Guard rendering when environmentId is missing.
environmentId is typed as string | undefined (from environments[0]?.id), but CpuFormProps.environmentId expects string. With strict: true in TypeScript, passing an undefined value violates type safety. Additionally, mutations at lines 79 and 102 depend on environmentId being a valid string.
Add an early return to prevent rendering when no environment is available:
Guard example
export const Cpu = () => {
const { environments } = useProjectData();
const environmentId = environments[0]?.id;
+ if (!environmentId) {
+ return null;
+ }
+
const { data: settingsData } = trpc.deploy.environmentSettings.get.useQuery(
{ environmentId: environmentId ?? "" },
{ enabled: Boolean(environmentId) },
);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@web/apps/dashboard/app/`(app)/[workspaceSlug]/projects/[projectId]/(overview)/settings/components/runtime-settings/cpu.tsx
around lines 33 - 45, The Cpu component must early-return when no environmentId
is available to avoid passing undefined to CpuForm and prevent downstream
mutations from running; update Cpu (which uses useProjectData -> environments
and trpc.deploy.environmentSettings.get.useQuery) to check if
environments[0]?.id is falsy and return null (or a placeholder) before calling
trpc or rendering CpuForm, and ensure you only call
trpc.deploy.environmentSettings.get.useQuery with a defined environmentId (keep
the enabled flag) and pass a string to CpuForm (defaultCpu can remain as
computed).
There was a problem hiding this comment.
🧹 Nitpick comments (1)
web/apps/dashboard/app/(app)/[workspaceSlug]/projects/[projectId]/(overview)/settings/components/advanced-settings/env-vars/env-var-row.tsx (1)
56-66: Consider accessibility for the visibility toggle button.The eye button lacks an accessible label. Screen readers won't convey the button's purpose.
♿ Proposed accessibility improvement
<button type="button" className="text-gray-9 hover:text-gray-11 transition-colors" onClick={() => setIsVisible((v) => !v)} tabIndex={-1} + aria-label={isVisible ? "Hide value" : "Show value"} > {isVisible ? <EyeSlash iconSize="sm-regular" /> : <Eye iconSize="sm-regular" />} </button>🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/apps/dashboard/app/`(app)/[workspaceSlug]/projects/[projectId]/(overview)/settings/components/advanced-settings/env-vars/env-var-row.tsx around lines 56 - 66, The visibility toggle button (eyeButton) is missing an accessible label; update the button rendered in eyeButton to include a dynamic aria-label (e.g., aria-label={isVisible ? 'Hide value' : 'Show value'}) and/or title so screen readers and hover users know its purpose, and add aria-pressed={isVisible} if treating it as a toggle; ensure the change is applied where setIsVisible is used and the Eye/EyeSlash icons remain decorative (aria-hidden="true") so only the label is announced.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Duplicate comments:
In
`@web/apps/dashboard/app/`(app)/[workspaceSlug]/projects/[projectId]/(overview)/settings/components/advanced-settings/env-vars/utils.ts:
- Around line 7-32: computeEnvVarsDiff uses unsafe "as string" casts for v.id;
replace them by introducing a type predicate (e.g., isItemWithId(item): item is
EnvVarItem & { id: string }) and use it in the filters that produce
originalVars, originalIds/originalMap and the currentIds computation so
TypeScript knows id is present and string-typed without casts; update uses in
originalVars = original.filter(isItemWithId), originalMap = new
Map(originalVars.map(v => [v.id, v])), and currentIds = new
Set(current.filter(isItemWithId).map(v => v.id)) while leaving other checks
(like !v.id) intact where appropriate.
---
Nitpick comments:
In
`@web/apps/dashboard/app/`(app)/[workspaceSlug]/projects/[projectId]/(overview)/settings/components/advanced-settings/env-vars/env-var-row.tsx:
- Around line 56-66: The visibility toggle button (eyeButton) is missing an
accessible label; update the button rendered in eyeButton to include a dynamic
aria-label (e.g., aria-label={isVisible ? 'Hide value' : 'Show value'}) and/or
title so screen readers and hover users know its purpose, and add
aria-pressed={isVisible} if treating it as a toggle; ensure the change is
applied where setIsVisible is used and the Eye/EyeSlash icons remain decorative
(aria-hidden="true") so only the label is announced.
* feat: add github section * feat: Add icons * feat: add new sections * feat: add settingsgroup * feat: add region selection * feat: add instances * feat: add memory and cpu section * feat: add sections * feat: add health check * feat: add scaling * fix: get rid of redundant prop * refactor: Add toasts to mutations * refactor: rename component * feat: add port section * feat: fix overlapping borders * refactor: fix healthcheck tRPC * feat: add command section * feat: add env section * fix: finalize env-vars * refactor: finalize * feat: Add custom domains * fix: overflwo * feat: make tRPC route for each mutation * fix: displayValue styles * refactor: tidy * fix: revert accidental changes * feat: add cname table * fix: github styling issues * refactor: tidy * refactor: rename * fix: linter * fix: dynamic form issue * feat: allow env selection * chore: tidy * fix: use same chevron
* test keys table * re org and exports * error fix * Apos * chore: remove deployment breadcrumbs (#5019) * fix: cleanup project side nav * feat: simplify deployment overview page only show build logs until it's built, then show domains and network * chore: clean up nav * fix(clickhouse): improve latest keys used queries for high volume (150M +) (#4959) * fix(clickhouse): improve clickhouse query for key logs and add new table and mv for latest keys used * fix valid/error count = 0 scenario * remove identity_id from order by * wrap identity_id with aggregating function since its removed from the order key --------- Co-authored-by: Flo <53355483+Flo4604@users.noreply.github.com> * fix: domain refetch and promotion disable rule (#5013) * fix: domain refetch and promotion disable rule * fix: regression --------- Co-authored-by: Andreas Thomas <dev@chronark.com> * refactor: move custom domains to tanstack db (#5017) * refactor: move custom domains to tanstack db * fix: comment * fix: delete mutation * remove: unnecessary query * remove agent (#5021) * remove agent * remove agent * chore: vault in dashboard (#5023) * remove agent * remove agent * use vault in dashboard * remove * project domain (#5022) * fix: cleanup project side nav * feat: simplify deployment overview page only show build logs until it's built, then show domains and network * chore: clean up nav * feat: add per-project sticky domain and only display that * chore: use vault in api (#5024) * chore: use vault in api * chore: use vault in api * fix harness * use memory test * vault container go start * [autofix.ci] apply automated fixes --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> * fix: Make GH callback dynamic (#5029) * dunno * nextjs should allow a setting that says dynamic * [autofix.ci] apply automated fixes --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> * allow longer timeouts (#5032) * docs: add ratelimit.unkey.com benchmark links to ratelimiting docs Add references to real-time performance benchmarks in: - introduction.mdx: new 'Performance at scale' accordion - modes.mdx: link after latency claim Presents benchmarks as capability demonstration rather than comparison. * docs: add description to cache store interface page (#5037) Add missing SEO description to frontmatter Generated-By: mintlify-agent Co-authored-by: mintlify[bot] <109931778+mintlify[bot]@users.noreply.github.com> Co-authored-by: Andreas Thomas <dev@chronark.com> * docs: remove orphaned SDK documentation (#5033) Remove Spring Boot Java, Rust, and Elixir SDK docs that are not linked in navigation and appear to be outdated/unmaintained. Generated-By: mintlify-agent Co-authored-by: mintlify[bot] <109931778+mintlify[bot]@users.noreply.github.com> Co-authored-by: Andreas Thomas <dev@chronark.com> * No data * add bg * rework release (#5044) * rework release * rework release * feat: generate rpc wrappers (#5028) * feat: generate rpc wrappers * bazel happyier * more changes * more changes * move path * delete old files (#5043) * fix: rabbit comments --------- Co-authored-by: Oz <21091016+ogzhanolguncu@users.noreply.github.com> * feat/gossip (#5015) * add a gossip implementation * add gossip to sentinel/frontline * add message muxing * sentinel fun * cleansings * cleansings * cleansings * cleansings * use oneof * fix bazel happiness * do some changies * exportoneof * more cool fancy thingx * change gateway choosing * add label * adjjust some more * adjjust some more * fixa test * goodbye kafka * fix: bazel * rename gateway -> ambassador * add docs * fix: rabbit comments * [autofix.ci] apply automated fixes * idfk * more changes * more changes * fix ordering * fix missing files * fix test --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> * fix: retry hubble ui (#5056) * fix: wait for cillium policy until CRDs are ready (#5059) * fix: retry cillium policy until CRDs are ready * fix: blocks until all system pods are ready * deployment build screen v1 (#5042) * fix: cleanup project side nav * feat: simplify deployment overview page only show build logs until it's built, then show domains and network * feat: new build screen for ongoing deployments * fix: table column typo * fix: update copy to remove mention of analytics deletion (#5067) * fix typo (#5039) * rfc: sentinel middlewares (#5041) * fix: cleanup project side nav * feat: simplify deployment overview page only show build logs until it's built, then show domains and network * feat: middleware rfc * Update svc/sentinel/proto/buf.gen.ts.yaml Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --------- Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> Co-authored-by: Flo <53355483+Flo4604@users.noreply.github.com> * feat: config files (#5045) * fix: cleanup project side nav * feat: simplify deployment overview page only show build logs until it's built, then show domains and network * feat: add pkg/config for struct-tag-driven TOML/YAML/JSON configuration Introduces a new configuration package that replaces environment variable based configuration with file-based config. Features: - Load and validate config from TOML, YAML, or JSON files - Struct tag driven: required, default, min/max, oneof, nonempty - Environment variable expansion (${VAR} and ${VAR:-default}) - JSON Schema generation for editor autocompletion - Collects all validation errors instead of failing on first - Custom Validator interface for cross-field checks Also adds cmd/generate-config-docs for generating MDX documentation from Go struct tags, and a Makefile target 'config-docs'. Amp-Thread-ID: https://ampcode.com/threads/T-019c672a-0e8e-7138-b0ab-27cdbeaca7ba Co-authored-by: Amp <amp@ampcode.com> * remove gen * clean up * feat(api): migrate API service to file-based TOML config (#5046) * feat(api): migrate API service to file-based config Migrate the API service from environment variables to TOML file-based configuration using pkg/config. Replaces all UNKEY_* env vars with a structured api.toml config file. Changes: - Rewrite svc/api/config.go with tagged Config struct - Update svc/api/run.go to use new config fields - Update cmd/api/main.go to accept --config flag - Add dev/config/api.toml for docker-compose - Update dev/k8s/manifests/api.yaml with ConfigMap - Regenerate config docs from struct tags Amp-Thread-ID: https://ampcode.com/threads/T-019c672a-0e8e-7138-b0ab-27cdbeaca7ba Co-authored-by: Amp <amp@ampcode.com> * feat(vault): migrate Vault service to file-based TOML config (#5047) * feat(vault): migrate Vault service to file-based config Migrate the Vault service from environment variables to TOML file-based configuration using pkg/config. Changes: - Rewrite svc/vault/config.go with tagged Config struct - Update svc/vault/run.go to use new config fields - Update cmd/vault/main.go to accept --config flag - Add dev/config/vault.toml for docker-compose - Update dev/k8s/manifests/vault.yaml with ConfigMap - Remove UNKEY_* env vars from docker-compose and k8s Amp-Thread-ID: https://ampcode.com/threads/T-019c672a-0e8e-7138-b0ab-27cdbeaca7ba Co-authored-by: Amp <amp@ampcode.com> * feat(ctrl): migrate Ctrl API and Worker to file-based TOML config (#5048) * feat(ctrl): migrate Ctrl API and Worker services to file-based config Migrate both ctrl-api and ctrl-worker from environment variables to TOML file-based configuration using pkg/config. Changes: - Rewrite svc/ctrl/api/config.go and svc/ctrl/worker/config.go - Update run.go files to use new config fields - Update cmd/ctrl/api.go and worker.go to accept --config flag - Add dev/config/ctrl-api.toml and ctrl-worker.toml - Update dev/k8s/manifests/ctrl-api.yaml and ctrl-worker.yaml with ConfigMaps - Remove UNKEY_* env vars from docker-compose and k8s manifests * feat(krane): migrate Krane service to file-based TOML config (#5049) * feat(krane): migrate Krane service to file-based config Migrate the Krane container orchestrator from environment variables to TOML file-based configuration using pkg/config. Changes: - Rewrite svc/krane/config.go with tagged Config struct - Update svc/krane/run.go to use new config fields - Update cmd/krane/main.go to accept --config flag - Add dev/config/krane.toml for docker-compose - Update dev/k8s/manifests/krane.yaml with ConfigMap - Remove UNKEY_* env vars from docker-compose and k8s Amp-Thread-ID: https://ampcode.com/threads/T-019c672a-0e8e-7138-b0ab-27cdbeaca7ba Co-authored-by: Amp <amp@ampcode.com> * feat(frontline): migrate Frontline service to file-based TOML config (#5050) * feat(frontline): migrate Frontline service to file-based config Migrate the Frontline reverse proxy from environment variables to TOML file-based configuration using pkg/config. Changes: - Rewrite svc/frontline/config.go with tagged Config struct - Update svc/frontline/run.go to use new config fields - Update cmd/frontline/main.go to accept --config flag - Update dev/k8s/manifests/frontline.yaml with ConfigMap - Remove UNKEY_* env vars from k8s manifest Amp-Thread-ID: https://ampcode.com/threads/T-019c672a-0e8e-7138-b0ab-27cdbeaca7ba Co-authored-by: Amp <amp@ampcode.com> * feat(preflight): migrate Preflight service to file-based TOML config (#5051) * feat(preflight): migrate Preflight service to file-based config Migrate the Preflight webhook admission controller from environment variables to TOML file-based configuration using pkg/config. Changes: - Rewrite svc/preflight/config.go with tagged Config struct - Update svc/preflight/run.go to use new config fields - Update cmd/preflight/main.go to accept --config flag - Update dev/k8s/manifests/preflight.yaml with ConfigMap - Remove UNKEY_* env vars from k8s manifest Amp-Thread-ID: https://ampcode.com/threads/T-019c672a-0e8e-7138-b0ab-27cdbeaca7ba Co-authored-by: Amp <amp@ampcode.com> * feat(sentinel): migrate Sentinel service to file-based config (#5052) Migrate the Sentinel sidecar from environment variables to TOML file-based configuration using pkg/config. This is the final service migration in the config stack. Changes: - Rewrite svc/sentinel/config.go with tagged Config struct - Update svc/sentinel/run.go to use new config fields - Update cmd/sentinel/main.go to accept --config flag - Update dev/docker-compose.yaml: replace env vars with TOML volume mounts for all migrated services (api, vault, krane, ctrl-api, ctrl-worker) - Minor formatting fix in pkg/db generated code --------- Co-authored-by: Amp <amp@ampcode.com> --------- Co-authored-by: Amp <amp@ampcode.com> --------- Co-authored-by: Amp <amp@ampcode.com> --------- Co-authored-by: Amp <amp@ampcode.com> --------- Co-authored-by: Amp <amp@ampcode.com> --------- Co-authored-by: Amp <amp@ampcode.com> * fix: bad config * remove unnecessary tls config for ctrl api * fix: error * fix: do not log config content * ix: remove kafka * fix: replica * fix: return err * fix: only overwrite frontline id if missing * fix: observability * fix: otel * fix: redundant config * fix: reuse tls * fix: consolidate * fix: use shared configs * fix: config * fix: something * Update pkg/config/common.go Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * fix: vault startup * fix: instanceid * fix: vault config * fix: make configs required * fix: everything works again --------- Co-authored-by: Amp <amp@ampcode.com> Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * clean deployment url label (#4976) * clean deployment url * fix conversion error and maintain single source of truth --------- Co-authored-by: Andreas Thomas <dev@chronark.com> Co-authored-by: Flo <53355483+Flo4604@users.noreply.github.com> * feat: New deploy settings (#5073) * feat: add github section * feat: Add icons * feat: add new sections * feat: add settingsgroup * feat: add region selection * feat: add instances * feat: add memory and cpu section * feat: add sections * feat: add health check * feat: add scaling * fix: get rid of redundant prop * refactor: Add toasts to mutations * refactor: rename component * feat: add port section * feat: fix overlapping borders * refactor: fix healthcheck tRPC * feat: add command section * feat: add env section * fix: finalize env-vars * refactor: finalize * feat: Add custom domains * fix: overflwo * feat: make tRPC route for each mutation * fix: displayValue styles * refactor: tidy * fix: revert accidental changes * feat: add cname table * fix: github styling issues * refactor: tidy * refactor: rename * fix: linter * fix: dynamic form issue * feat: allow env selection * chore: tidy * fix: use same chevron * fix: use certmanager if availiable otherwise certfile (#5076) * fix: use certmanager if availiable otherwise certfile * feat: make tls enabled by default now you need to explicitely pass tls.disabled=true if not, we fail during startup. also renamed some port vars to make it obvious what they are used for * chore: log candidates for easier debugging * fix: use static certs first --------- Co-authored-by: chronark <dev@chronark.com> * feat: sentinel key verification middleware (#5079) * feat: key-sentinel-middleware * fix error pages (#5083) * fix error pages * remove test * move some files * Update svc/frontline/internal/errorpage/error.go.tmpl Co-authored-by: Andreas Thomas <dev@chronark.com> * [autofix.ci] apply automated fixes --------- Co-authored-by: Andreas Thomas <dev@chronark.com> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> * add rl headers. * feat: new ui and fixed a bunch of stuff * Update svc/sentinel/engine/match.go Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * fix: coderabbit --------- Co-authored-by: Andreas Thomas <dev@chronark.com> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * clean up after sentinel middleware (#5088) * feat: key-sentinel-middleware * fix error pages (#5083) * fix error pages * remove test * move some files * Update svc/frontline/internal/errorpage/error.go.tmpl Co-authored-by: Andreas Thomas <dev@chronark.com> * [autofix.ci] apply automated fixes --------- Co-authored-by: Andreas Thomas <dev@chronark.com> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> * add rl headers. * feat: new ui and fixed a bunch of stuff * Update svc/sentinel/engine/match.go Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * fix: coderabbit * chore: clean up old columns * fix: db --------- Co-authored-by: Flo <flo@unkey.com> Co-authored-by: Flo <53355483+Flo4604@users.noreply.github.com> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * fix proto type (#5093) * fix: cleanup project side nav * feat: simplify deployment overview page only show build logs until it's built, then show domains and network * fix: runtime exception due to gaslighting type * fix: Modals with combo box work again (#5002) * chore: remove chproxy routes (#5101) * chore: remove chproxy routes * refactor: move prometheus metrics to scoped packages (#5102) * remove the hand holding (#5108) * feat: gossip metrics (#5107) * fix: Make identity slugs copyable (#5100) * fix: make me copy * Update web/apps/dashboard/app/(app)/[workspaceSlug]/authorization/permissions/components/table/components/assigned-items-cell.tsx Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --------- Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * refactor: useDeployment hook usage (#5086) * refactor: useDeployment hook usage * fix: remove redundant check * feat: i need more metrics (#5115) * docs: Python and Go examples (#5058) * Add Python and Go SDK documentation - Add Go quickstart guide with stdlib, Gin, and Echo examples - Add Python quickstart guide with FastAPI, Flask, Django - Add Go cookbook: stdlib, Gin, Echo middleware recipes - Add Python cookbook: FastAPI rate limiting recipe All content uses Unkey v2 API only. * Add final Python cookbook recipe and 5-minutes guide * Update docs.json sidebar navigation - Add Go and Python quickstart guides to Framework Guides - Add Go and Python cookbook recipes to Recipes section - Remove duplicate 5-minutes quickstart file * Add Go examples to quickstart and reorganize cookbook by language - Add Go code examples to /quickstart/quickstart.mdx for key creation and verification - Reorganize cookbook recipes into subsections: TypeScript, Go, Python, General - Keep existing TypeScript and Python examples in quickstart * Update cookbook index with new Go and Python recipes * Fix code issues in Go and Python documentation - Fix int to string conversion in go-gin-middleware (use strconv) - Fix middleware composition in go-stdlib-middleware - Fix wait calculation in python-fastapi-ratelimit (use total_seconds) - Fix headers attachment in python-fastapi-ratelimit (use JSONResponse) - Fix nil pointer dereference in quickstart/go - Fix unsafe type assertion in quickstart/go * Fix async/sync issue and nil pointer in quickstart docs - Use verify_key_async in Python async route - Add nil check for result.Code in Go quickstart * Fix more code issues in documentation - Fix GetUnkeyResult type assertion in go-gin-middleware - Fix imports in python-fastapi-ratelimit (add JSONResponse, remove unused timedelta) - Update basic rate limit example to use async API with context manager - Add missing os import in Django settings snippet * Fix missing os import in python-flask-auth.mdx * Fix unsafe type assertions in Go middleware docs - Fix RequirePermission in go-echo-middleware with safe type assertion - Fix GetUnkeyResult in go-echo-middleware with safe type assertion - Fix RequirePermission in go-gin-middleware with safe type assertion * Fix error handling in Python docs - replace ApiError with UnkeyError * Update legacy analytics documentation - Replace outdated /apis/features/analytics.mdx with minimal reference page - Remove analytics from API Keys sidebar in docs.json - Add redirect from /apis/features/analytics to /analytics/overview * fix * Update to mint * Fix critical type assertion issues in go-gin-middleware - Store pointer to struct in context (not value) for type assertion compatibility - Add checked type assertion in RequireRole with proper error handling * Add it back * fix the comma * revert * Update go examples * cookbook update * update quickstart * remove analytics page that is redirected * Update web/apps/docs/cookbook/go-echo-middleware.mdx Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * Update web/apps/docs/cookbook/go-echo-middleware.mdx Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * fix --------- Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * fix: deploy quick fixes (#5085) * fix: fetch correct deployment+sentinel * fix: add missing team switcher hover indicator * refactor: use the same empty text * fix: lock network view and fix generate dummy network * fix: safari rendering issue of network * chore: fmt * fix: build * [autofix.ci] apply automated fixes --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: Flo <53355483+Flo4604@users.noreply.github.com> Co-authored-by: James P <james@unkey.com> * docs: remove duplicate onboarding page (#5035) Remove quickstart/onboarding/onboarding-api.mdx which duplicates content from the new quickstart. Redirects already exist in docs.json. Generated-By: mintlify-agent Co-authored-by: mintlify[bot] <109931778+mintlify[bot]@users.noreply.github.com> Co-authored-by: Andreas Thomas <dev@chronark.com> Co-authored-by: James P <james@unkey.com> * docs: remove deprecated Vercel integration page (#5034) The Vercel integration is currently not supported. Remove the page to avoid confusing users. Generated-By: mintlify-agent Co-authored-by: mintlify[bot] <109931778+mintlify[bot]@users.noreply.github.com> Co-authored-by: James P <james@unkey.com> * fix: schema cache (#5116) * fix: do background gossip connect (#5119) * fix: do background gossip connect * bazel happy * chore: debug wan failures (#5124) * chore: debug wan failures * add log writer * bazel .......... * bazel .......... * fix: a user cannot click outside of the org selection modal (#5031) * fix: a user cannot click outside of the org selection modal * use errorMessage instead of hard coding messages * restore x closing functionality * fix rabbit, fix flash of empty state * clear last used workspace when auto-selection fails * remove unused conditional --------- Co-authored-by: James P <james@unkey.com> * sentinel prewarm cache (#5071) * fix: cleanup project side nav * feat: simplify deployment overview page only show build logs until it's built, then show domains and network * feat: sentinels prewarm their cache it's not optmized, but pretty neat --------- Co-authored-by: Flo <53355483+Flo4604@users.noreply.github.com> * fix: ignore empty wan (#5122) Co-authored-by: Andreas Thomas <dev@chronark.com> Co-authored-by: James P <james@unkey.com> * docs: move docs (#5125) * refactor: deploy settings tanstack (#5104) * refactor: move them to tanstack * refactor: tidy up * feat: add env provider to decide what env we are on * refactor: tidy * feat: add scroll into view for settingcard * fix: bg * refactor: remove toasts from env-vars * chore: tidy * fix: build * feat: vault bulk en/decrypt (#5127) * feat: vault bulk en/decrypt * oops wrong file * cleanup proto * [autofix.ci] apply automated fixes * cleanup * cleanup --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> * feat: trace generated rpc clients (#5128) * feat: trace generated rpc clients * ignore not found * fix: docs generator paths (#5136) * fix: retry memberlist creation (#5134) * fix: retry memberlist creation * remove comments * move to const * fix: Restore filtering on logs (#5138) * Restore filtering on logs Restores filtering on the logs. * [autofix.ci] apply automated fixes * fmt --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> * fix: resolve dns to ip (#5139) * fix: resolve dns to ip * rabbit comments * Fix/issue 5132 billing section widths (#5140) * fix: Billing page has inconsistent section widths (#5132) Standardized all SettingCard components to use consistent width classes: - Updated Usage component: contentWidth changed from 'w-full lg:w-[320px]' to 'w-full' - Updated CancelAlert component: contentWidth changed from 'w-full lg:w-[320px]' to 'w-full' - Updated Billing Portal in client.tsx: contentWidth changed from 'w-full lg:w-[320px]' to 'w-full' - Updated CurrentPlanCard component: removed min-w-[200px] from className for consistency All billing sections now use contentWidth='w-full' for consistent layout. Fixes #5132 * Fix billing and setting cards * [autofix.ci] apply automated fixes --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> * pnpm i * No virtualization * pagination footer * sorting and pagination changes * refactor * exports * move data-table to ui * install * fmt * Footer style changes * sorting and pagination changes * sorting and footer loading fix * cleanup * prefetch pages * changes for review comments from rabbit and meg * Ref fix and removed not needed import * [autofix.ci] apply automated fixes * sorting fix and key navigation * review changes mess * minor rabbit changes * Update loading-indicator animation delay * style change on pageination footer * ref type change --------- Co-authored-by: Andreas Thomas <dev@chronark.com> Co-authored-by: Meg Stepp <mcstepp@users.noreply.github.com> Co-authored-by: Flo <53355483+Flo4604@users.noreply.github.com> Co-authored-by: Oz <21091016+ogzhanolguncu@users.noreply.github.com> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: James P <james@unkey.com> Co-authored-by: mintlify[bot] <109931778+mintlify[bot]@users.noreply.github.com> Co-authored-by: gui martins <guilhermev2huehue@gmail.com> Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> Co-authored-by: Amp <amp@ampcode.com> Co-authored-by: Vansh Malhotra <vansh.malhotra439@gmail.com> Co-authored-by: Flo <flo@unkey.com>
What does this PR do?
This is a complete overhaul of deploy settings and preparation for deploy onboarding.
This PR:
<SettingCard />to have an expandable section.<SettingCard/> called<SettingCardGroup />Example:
They will be automatically wrapped with correct borders so no need for
border="both"mess.<SettingsGroup icon={<Icon />} title="..">component to group SettingCards which later can be moved to@unkey/uiHow to test