Skip to content

Conversation

@eluce2
Copy link
Collaborator

@eluce2 eluce2 commented Dec 15, 2025

Summary by CodeRabbit

  • New Features

    • Web UI for managing typegen configs with a local HTTP server and typed API; rich editor, layout selector, and run/save flows.
    • OData metadata tooling: download, parse, and generate TypeScript types; webhook management and test utilities.
    • New suite of reusable UI components (forms, dialogs, data grid, selects, tooltips, sheets) and dev-focused demos.
  • Improvements

    • Improved env-var resolution, connection testing, diagnostics, and resilient dev-server proxy.
    • Build/tooling enhancements and extended project configs for web package.
  • Chores

    • Dependency upgrades (zod, TypeScript, Vitest, Next, and others).

✏️ Tip: You can customize this high-level summary in your review settings.


Note

Adds packages/typegen/web to pnpm-workspace.yaml package list.

Written by Cursor Bugbot for commit bd77c9a. This will update automatically on new commits. Configure here.

@vercel
Copy link

vercel bot commented Dec 15, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Review Updated (UTC)
proofkit-docs Ready Ready Preview Dec 19, 2025 8:29pm

@eluce2 eluce2 marked this pull request as ready for review December 15, 2025 14:53
@eluce2 eluce2 mentioned this pull request Dec 15, 2025
Copy link
Collaborator Author

eluce2 commented Dec 15, 2025

Warning

This pull request is not mergeable via GitHub because a downstack PR is open. Once all requirements are satisfied, merge this PR as a stack on Graphite.
Learn more

This stack of pull requests is managed by Graphite. Learn more about stacking.

@coderabbitai
Copy link

coderabbitai bot commented Dec 15, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

Adds a TypeGen web UI and Hono-based API, fmdapi/fmodata support (env handling, metadata download/parse, OData type generation), many new web UI components/hooks, registry and fmodata enhancements (webhooks), workspace/package manifest updates, and dependency bumps (zod v4, TypeScript, Vitest).

Changes

Cohort / File(s) Summary
Monorepo manifests & dependency bumps
package.json, pnpm-workspace.yaml, apps/demo/package.json, apps/docs/package.json, packages/*/package.json (e.g., packages/better-auth/package.json, packages/cli/package.json, packages/fmdapi/package.json, packages/fmodata/package.json, packages/registry/package.json, packages/webviewer/package.json, packages/typegen/web/package.json)
Updated dependency versions across workspace (notably zod → v4.x, typescript → ^5.9.3, vitest → ^4.0.7), added packages/typegen/web to workspace, and added new tooling deps (e.g., tsx) and web package manifest.
TypeGen server & API
packages/typegen/src/server/*
.../api.ts, .../app.ts, .../contract.ts, .../createDataApiClient.ts, .../index.ts
New Hono-based API and server entrypoint, typed Zod contracts, endpoints for config/layouts/env/test-connection/run, data-api client creation, static asset serving, port discovery and lifecycle management.
CLI, scripts & build orchestration
packages/typegen/src/cli.ts, packages/typegen/scripts/* (build-copy.js, build.ts), packages/registry/scripts/validate.ts, packages/typegen/vite.config.ts, packages/cli/src/utils/formatting.ts
Added ui CLI command to start server, env/config resolution changes, build-copy script, prebuild/validation script for registry, Vite plugin for typegen subpaths, and switched Prettier imports to namespace form with safe fallbacks.
Type/schema changes (typegen)
packages/typegen/src/types.ts, packages/typegen/src/buildSchema.ts, packages/typegen/vite.config.ts, packages/typegen/tsconfig.json
Converted config to a discriminated union supporting fmdapi/fmodata, added Fmodata types, standardized zod import mapping (zod/v4) in generators and snapshots, and adjusted TS lib settings.
Fmodata: metadata & OData generation
packages/typegen/src/fmodata/* (downloadMetadata.ts, parseMetadata.ts, generateODataTypes.ts, typegen.ts, index.ts)
New utilities to download metadata, parse OData XML into rich metadata models, generate per-table TypeScript types, orchestration entrypoints, and public re-exports.
Env handling & client creation
packages/typegen/src/getEnvValues.ts, packages/typegen/src/server/createDataApiClient.ts
Centralized env resolution/validation (apiKey vs username/password), validation logging, and creation of DataApi/OData clients from config with rich error types.
TypeGen web UI package
packages/typegen/web/** (many files: src/*, vite.config.ts, tsconfig*.json, index.html, package.json, src/main.tsx)
New React web app (@proofkit/typegen-web): App, numerous UI components, primitives, hooks (useConfig, useTestConnection, useEnvValue, useParseMetadata, useRunTypegen), typed API client, Tailwind/Vite build and dev proxy.
Web UI components & primitives
packages/typegen/web/src/components/**, packages/typegen/web/src/components/ui/**
Large set of new UI primitives and components (form, dialog, accordion, badge, button, select, popover, tooltip, data-grid, sheet, skeleton, tree, data-grid DnD, and many demos), plus hooks and utilities (cn, theme/provider).
Formatting & tooling adjustments
packages/cli/src/utils/formatting.ts, packages/typegen/src/formatting.ts, packages/cli/vitest.config.ts, packages/registry/vitest.config.ts
Prettier import style changes, safe getFileInfo fallback, increased Vitest timeouts and adjusted coverage/include rules.
Registry: types, build & validation
packages/registry/lib/types.ts, packages/registry/lib/validator.ts, packages/registry/tsdown.config.ts, packages/registry/tsconfig.json, packages/registry/scripts/validate.ts
Updated registry type schemas, switched import paths to .js, enabled d.ts generation and post-build renames, added prebuild validation script, and narrowed tsconfig includes/excludes.
fmodata: webhooks & client integration
packages/fmodata/src/client/* (webhook-builder.ts, database.ts, filemaker-odata.ts), packages/fmodata/scripts/*, packages/fmodata/tests/*, packages/fmodata/tests/fixtures/*
Added WebhookManager API and types, integrated into Database, added tests and capture scripts, updated mock responses and fixtures, and minor API-key unsupported log.
Tests & snapshots
packages/typegen/tests/**, apps/docs/tests/**, packages/registry/lib/validator.test.ts, packages/fmodata/tests/**
New webhook tests, updated snapshots to import zod/v4, adjusted registry test mocks/expectations and other test updates.
Workspace & gitignore
pnpm-workspace.yaml, .gitignore
Included packages/typegen/web in workspace and ignored packages/typegen/schema/metadata.xml.
Misc & whitespace
packages/fmodata/README.md, small whitespace-only edits, README duplications
Minor docs duplication and non-functional edits; formatting integration in scripts.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant Browser
    participant UI as TypeGen Web UI
    participant API as TypeGen API (Hono)
    participant FS as Filesystem
    participant FM as FileMaker Server

    User->>Browser: navigate to UI
    Browser->>UI: load assets (vite dev or static)
    UI->>API: GET /api/config
    API->>FS: read configPath
    FS-->>API: file content / not-found
    API->>API: parse JSONC & validate (zod)
    API-->>UI: 200 { exists, path, config } / 500 error

    User->>UI: Save config
    UI->>API: POST /api/config { config }
    API->>API: validate payload (zod)
    alt valid
        API->>FS: write formatted JSON
        FS-->>API: OK
        API-->>UI: { success: true }
    else invalid
        API-->>UI: { success: false, issues }
    end

    User->>UI: Test connection (choose config)
    UI->>API: POST /api/test-connection { config? }
    API->>API: resolve envs & create client
    API->>FM: request layouts / test endpoints
    alt FM success
        FM-->>API: layouts / ok
        API-->>UI: success + layouts
    else FM error
        FM-->>API: error
        API-->>UI: error details (kind/code)
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Potential hotspots to focus review on:

  • Zod v4 migration and zod import-path mapping across generators, tests, and snapshots.
  • New server endpoints: request validation, filesystem read/write, error mapping, and lifecycle/port handling.
  • Env resolution/validation and createDataApiClient authentication flows (apiKey vs username/password).
  • OData metadata parsing and type generation correctness (keys, nullable/readOnly, navigation).
  • Large UI surface: react-hook-form wiring, react-query keys, and dev server proxy handling.

Possibly related PRs

Suggested reviewers

  • chriscors

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 2 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 16.34% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Title check ❓ Inconclusive The title 'typegen web ui' is vague and overly generic. It does not specify the primary change or provide meaningful context about what was implemented beyond a general reference to a web UI for typegen. Consider a more descriptive title such as 'Add typegen web UI with React, Vite, and Tailwind' or 'Implement web-based UI for typegen configuration' to clearly communicate the scope and nature of the changes.
Description check ❓ Inconclusive The PR description is minimal and generic, providing only an automatically-generated summary by Cursor Bugbot without author-provided context about the changeset objectives. Consider providing a more detailed PR description explaining the purpose, scope, and context of the Typegen web UI implementation and its integration into the monorepo.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch 12-13-typegen_web_ui

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 14

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)
packages/registry/package.json (1)

42-48: Version mismatch: @vitest/coverage-v8 should match vitest major version.

vitest is upgraded to ^4.0.7 but @vitest/coverage-v8 remains at ^2.1.8. These packages should have matching major versions to ensure compatibility.

  "devDependencies": {
-   "@vitest/coverage-v8": "^2.1.8",
+   "@vitest/coverage-v8": "^4.0.7",
    "chokidar-cli": "^3.0.0",
🟡 Minor comments (26)
packages/typegen/web/src/components/ui/accordion.tsx-86-91 (1)

86-91: Inconsistent indicator type — missing 'none' option.

The AccordionContextType (line 77) and accordionTriggerVariants (line 48) both support 'none' as an indicator value, but the Accordion component's props type only allows 'arrow' | 'plus'. This prevents consumers from explicitly disabling the indicator.

Apply this diff to include the 'none' option:

 function Accordion(
   props: React.ComponentProps<typeof AccordionPrimitive.Root> &
     VariantProps<typeof accordionRootVariants> & {
-      indicator?: 'arrow' | 'plus';
+      indicator?: 'arrow' | 'plus' | 'none';
     },
 ) {
packages/typegen/web/src/components/ui/accordion.tsx-143-156 (1)

143-156: className applied to both wrapper and inner div.

The className prop is merged into both the AccordionPrimitive.Content (line 150) and the inner <div> (line 153). This could cause unexpected behavior if a consumer passes layout or spacing classes, as they'll be applied twice.

Apply this diff to apply className only to the outer wrapper:

   return (
     <AccordionPrimitive.Content
       data-slot="accordion-content"
       className={cn(accordionContentVariants({ variant }), className)}
       {...rest}
     >
-      <div className={cn('pb-5 pt-0', className)}>{children}</div>
+      <div className="pb-5 pt-0">{children}</div>
     </AccordionPrimitive.Content>
   );
packages/typegen/web/src/components/tree/default.tsx-85-88 (1)

85-88: Potential runtime error in getChildren if itemId is missing.

If getChildren is called with an itemId that doesn't exist in items, accessing items[itemId].children will throw a TypeError because items[itemId] is undefined.

Apply this diff to add a safe access pattern consistent with getItem:

     dataLoader: {
       getItem: (itemId) => items[itemId],
-      getChildren: (itemId) => items[itemId].children ?? [],
+      getChildren: (itemId) => items[itemId]?.children ?? [],
     },
packages/typegen/web/src/components/ui/tree.tsx-124-134 (1)

124-134: Missing defensive checks in TreeItemLabel.

TreeItemLabel calls item.isFolder(), item.isExpanded(), and item.getItemName() directly without the defensive typeof checks used in TreeItem. For consistency and robustness, consider applying the same pattern here.

-      {item.isFolder() &&
+      {typeof item.isFolder === 'function' && item.isFolder() &&
         (toggleIconType === 'plus-minus' ? (
-          item.isExpanded() ? (
+          typeof item.isExpanded === 'function' && item.isExpanded() ? (
             <SquareMinus className="text-muted-foreground size-3.5" stroke="currentColor" strokeWidth="1" />
           ) : (
             <SquarePlus className="text-muted-foreground size-3.5" stroke="currentColor" strokeWidth="1" />
           )
         ) : (
           <ChevronDownIcon className="text-muted-foreground size-4 in-aria-[expanded=false]:-rotate-90" />
         ))}
-      {children || (typeof item.getItemName === 'function' ? item.getItemName() : null)}
+      {children || (typeof item.getItemName === 'function' ? item.getItemName() : null)}
packages/typegen/web/src/components/ui/tree.tsx-88-94 (1)

88-94: Inconsistent defensive checks for item methods.

Lines 88-92 defensively check if methods like isFocused, isFolder, isSelected exist before calling them, but line 93 calls item.isExpanded() directly without the same check. This inconsistency could cause a runtime error if the item instance doesn't have isExpanded.

Apply this diff for consistency:

         data-search-match={typeof item.isMatchingSearch === 'function' ? item.isMatchingSearch() || false : undefined}
-        aria-expanded={item.isExpanded()}
+        aria-expanded={typeof item.isExpanded === 'function' ? item.isExpanded() : undefined}
packages/typegen/web/src/components/button/loading.tsx-11-14 (1)

11-14: Update comment to match actual interval.

The comment states "every 4 seconds" but the interval is set to 1000ms (1 second).

Apply this diff:

-    // Automatically toggle button state every 4 seconds
+    // Automatically toggle button state every 1 second
     const interval = setInterval(() => {
packages/typegen/web/src/components/ui/alert.tsx-71-75 (1)

71-75: Typo in data-slot selector for mono solid variant.

The selector *:data-slot-[alert=close] on line 74 appears malformed. Based on the pattern used elsewhere (e.g., [&_[data-slot=alert-close]]), this should likely be [&_[data-slot=alert-close]].

-      className: 'bg-zinc-950 text-white dark:bg-zinc-300 dark:text-black *:data-slot-[alert=close]:text-white',
+      className: 'bg-zinc-950 text-white dark:bg-zinc-300 dark:text-black [&_[data-slot=alert-close]]:text-white',
packages/typegen/web/src/components/ui/alert.tsx-31-34 (1)

31-34: Inconsistent data attribute selector in size variants.

Line 33 uses *:data-alert-icon which appears to be a typo. Other size variants use *:data-slot=alert-icon (line 31-32). This inconsistency will cause the small size styling to not apply correctly to the alert icon.

-      sm: 'rounded-md px-3 py-2.5 gap-2 text-xs [&>[data-slot=alert-icon]>svg]:size-4 *:data-alert-icon:mt-0.5 [&_[data-slot=alert-close]]:mt-0.25 [&_[data-slot=alert-close]_svg]:size-3.5',
+      sm: 'rounded-md px-3 py-2.5 gap-2 text-xs [&>[data-slot=alert-icon]>svg]:size-4 *:data-slot=alert-icon:mt-0.5 [&_[data-slot=alert-close]]:mt-0.25 [&_[data-slot=alert-close]_svg]:size-3.5',
packages/typegen/web/src/components/badge/circle.tsx-1-59 (1)

1-59: Rename demo component to be consistent with other demos.

The default export should be named BadgeCircleDemo to match the naming convention used by other demo components in the codebase (ButtonDemo, DialogDemo, InputDemo, SwitchDemo, SelectDemo, ComboboxDemo). The generic Component name is inconsistent and lacks clarity about the component's purpose.

Additionally, consider whether this and similar unused demo files should be relocated to a dedicated examples/ or __demos__/ directory to avoid bundling them into the production build.

packages/typegen/web/package.json-36-36 (1)

36-36: Fix Zod 4 incompatibility: replace .check() method in form schema

The .check() method doesn't exist in Zod 4 and should be replaced with .email(). At packages/typegen/web/src/components/form/default.tsx line 22, change:

email: z.string().check(z.email('Please enter a valid email address.')),

to:

email: z.string().email('Please enter a valid email address.'),

The rest of the codebase's Zod patterns (z.record with both key and value schemas, z.default usage, etc.) are already Zod 4 compatible.

packages/typegen/web/src/components/input/addon.tsx-1-29 (1)

1-29: This demo component is unused and should be removed or relocated.

InputDemo is not imported anywhere in the codebase. While Vite's tree-shaking will exclude it from the bundle, keeping example/demo components in the production source directory alongside UI components is poor organization. Relocate to a dedicated examples directory or remove entirely—similar to other demo files like button/loading.tsx, dialog/default.tsx, etc.

packages/typegen/web/src/hooks/useConfig.ts-35-43 (1)

35-43: Remove debug logging.

The console.log statement appears to be a debug artifact that should be removed before merging.

  const saveMutation = useMutation({
    mutationFn: async (configsToSave: SingleConfig[]) => {
-     console.log("configsToSave", configsToSave);
      return await saveConfig(configsToSave);
    },
packages/typegen/web/src/App.tsx-54-61 (1)

54-61: Missing form in useEffect dependency array.

The form.reset() call depends on the form object, which should be included in the dependency array. While form from useForm is typically stable, the linter will flag this as a missing dependency.

   useEffect(() => {
     console.log("configData from useEffect", configDataResponse);
     if (configDataResponse) {
       const configData = configDataResponse?.config;
       const serverConfigs = normalizeConfig(configData);
       form.reset({ config: serverConfigs });
     }
-  }, [configDataResponse]);
+  }, [configDataResponse, form]);
packages/typegen/web/src/components/ConfigList.tsx-46-57 (1)

46-57: Add keyboard accessibility to clickable list items.

The clickable div elements are not accessible via keyboard. Users navigating with keyboard or screen readers cannot activate these items. Consider using a button element or adding proper ARIA attributes and keyboard handlers.

-            <div
+            <button
               key={index}
-              className="config-list-item"
+              type="button"
+              className="config-list-item w-full text-left"
               onClick={() => onSelectConfig(index)}
             >
               <div className="config-list-item-content">
                 <span className="config-list-item-label">
                   {getConfigLabel(config, index)}
                 </span>
                 <span className="config-list-item-arrow">→</span>
               </div>
-            </div>
+            </button>
packages/typegen/web/src/App.tsx-188-194 (1)

188-194: Guard against undefined config access.

configs[index] could be undefined if configs and fields are temporarily out of sync during re-renders. Add a guard to prevent passing undefined to ConfigSummary.

             {fields.map((field, index) => {
               const config = configs[index];
+              if (!config) return null;
               return (
                 <AccordionItem key={field.id} value={index.toString()}>
                   <AccordionTrigger>
                     <ConfigSummary config={config} />
packages/typegen/web/src/components/ui/button.tsx-310-319 (1)

310-319: Typo in CSS selectors - extra opening bracket.

Lines 313 and 318 have malformed Tailwind selectors with an extra [ at the start, which will prevent the SVG sizing from being applied correctly.

       {
         size: 'xs',
         mode: 'icon',
-        className: 'w-7 h-7 p-0 [[&_svg:not([class*=size-])]:size-3.5',
+        className: 'w-7 h-7 p-0 [&_svg:not([class*=size-])]:size-3.5',
       },
       {
         size: 'sm',
         mode: 'icon',
-        className: 'w-8 h-8 p-0 [[&_svg:not([class*=size-])]:size-3.5',
+        className: 'w-8 h-8 p-0 [&_svg:not([class*=size-])]:size-3.5',
       },
packages/typegen/web/vite.config.ts-44-56 (1)

44-56: Proxy error handler may leave requests hanging.

When ECONNREFUSED is caught and silently returned, no response is sent to the client. This can cause the request to hang until timeout. Consider sending an error response.

         proxy.on("error", (err, _req, res) => {
           // Suppress ECONNREFUSED errors during startup
           if (
             err &&
             (err as any).code === "ECONNREFUSED" &&
             res &&
             !res.headersSent
           ) {
-            // Silently handle connection refused - the retry logic will handle it
-            return;
+            // Send 503 so client-side retry logic can handle it
+            res.writeHead(503, { "Content-Type": "text/plain" });
+            res.end("Server starting up, please retry");
           }
         });
packages/typegen/web/src/components/EnvVarDialog.tsx-347-377 (1)

347-377: The warning indicator placement appears incorrect.

The ⚠️ emoji is placed inside parentheses which results in output like "Server (⚠️)" when suspectedField === "server", or "Server ()" otherwise - leaving empty parentheses. Consider moving the indicator outside or removing the parentheses:

                              {errorDetails.details.missing.server && (
                                <li>
-                                  Server (
-                                  {errorDetails.suspectedField === "server" &&
-                                    "⚠️"}
-                                  )
+                                  Server
+                                  {errorDetails.suspectedField === "server" &&
+                                    " ⚠️"}
                                </li>
                              )}

Apply similar changes to Database, Authentication, and Password list items.

packages/typegen/src/server/api.ts-1-41 (1)

1-41: Remove duplicate file api.ts.

This module is never imported or used anywhere in the codebase. The API endpoints are instead implemented in app.ts via createApiApp, which is the only implementation referenced by index.ts. This file is dead code and should be deleted.

packages/typegen/src/server/api.ts-61-73 (1)

61-73: Missing schema validation after parsing JSONC.

The JSONC is parsed but not validated against typegenConfig schema before returning. Compare to app.ts which does typegenConfig.parse(rawJson). This could return malformed config data.

     const raw = fs.readFileSync(fullPath, "utf8");
     const parsed = parse(raw);
+    const validated = typegenConfig.parse(parsed);

     return {
       status: 200,
       headers: { "Content-Type": "application/json" },
       body: JSON.stringify({
         exists: true,
         path: configPath,
-        config: parsed,
+        config: validated.config,
       }),
     };
packages/typegen/web/src/components/ui/badge.tsx-6-12 (1)

6-12: BadgeProps interface has unused dotClassName property.

The dotClassName property is defined in BadgeProps but is never used in the Badge component implementation (lines 186-209). Either remove it or implement the functionality.

 export interface BadgeProps
   extends React.HTMLAttributes<HTMLDivElement>,
     VariantProps<typeof badgeVariants> {
   asChild?: boolean;
-  dotClassName?: string;
   disabled?: boolean;
 }
packages/typegen/web/src/components/ui/badge.tsx-14-18 (1)

14-18: BadgeButtonProps extends HTMLDivElement but component renders as span/button.

BadgeButtonProps extends React.ButtonHTMLAttributes<HTMLDivElement> which is semantically incorrect—button attributes on a div type. The component renders as a span with role="button" (line 223).

Consider aligning the type with the actual element:

 export interface BadgeButtonProps
-  extends React.ButtonHTMLAttributes<HTMLDivElement>,
+  extends React.HTMLAttributes<HTMLSpanElement>,
     VariantProps<typeof badgeButtonVariants> {
   asChild?: boolean;
 }
packages/typegen/src/server/createDataApiClient.ts-71-120 (1)

71-120: Consider logging sensitive information carefully.

The error logging on lines 73-78 outputs the values of server, db, apiKey, and username to the console. While this is helpful for debugging, logging credentials (even partial) could be a security concern in production environments.

Consider masking or omitting the apiKey value in logs:

     console.error("Missing required environment variables", {
       server,
       db,
-      apiKey,
+      apiKey: apiKey ? "[REDACTED]" : undefined,
       username,
     });
packages/typegen/web/src/components/ui/switch.tsx-9-19 (1)

9-19: Context check is ineffective due to default value.

The context is initialized with a default object { permanent: false }, so React.useContext(SwitchContext) will never return a falsy value. The check on line 15 will never trigger the error, defeating the purpose of validating that SwitchIndicator is used within a Switch component.

To properly detect missing provider, use null as default:

-const SwitchContext = React.createContext<{ permanent: boolean }>({
-  permanent: false,
-});
+const SwitchContext = React.createContext<{ permanent: boolean } | null>(null);

 const useSwitchContext = () => {
   const context = React.useContext(SwitchContext);
   if (!context) {
     throw new Error("SwitchIndicator must be used within a Switch component");
   }
   return context;
 };
packages/typegen/web/src/components/ui/form.tsx-34-55 (1)

34-55: Context validation occurs after context usage.

The check if (!fieldContext) on line 41 happens after fieldContext.name is already accessed on line 39. Additionally, with the default empty object {}, fieldContext will always be truthy, making this check ineffective.

Consider restructuring to validate contexts before usage:

 const useFormField = () => {
   const fieldContext = React.useContext(FormFieldContext);
   const itemContext = React.useContext(FormItemContext);
+
+  if (!fieldContext.name) {
+    throw new Error('useFormField should be used within <FormField>');
+  }
+
+  if (!itemContext.id) {
+    throw new Error('useFormField should be used within <FormItem>');
+  }
+
   const { getFieldState, formState } = useFormContext();
 
   const fieldState = getFieldState(fieldContext.name, formState);
 
-  if (!fieldContext) {
-    throw new Error('useFormField should be used within <FormField>');
-  }
-
   const { id } = itemContext;
   // ...
 };
packages/typegen/web/src/components/ui/select.tsx-171-185 (1)

171-185: Custom indicator lacks selection-aware visibility.

When a custom indicator is provided (line 173), it's rendered directly without being wrapped in SelectPrimitive.ItemIndicator. This means custom indicators will always be visible, while the default Check icon (lines 181-183) only shows when the item is selected.

If custom indicators should also only show when selected, wrap them in ItemIndicator:

       {indicatorVisibility &&
         (indicator && isValidElement(indicator) ? (
-          indicator
+          <span
+            className={cn(
+              'absolute flex h-3.5 w-3.5 items-center justify-center',
+              indicatorPosition === 'left' ? 'start-2' : 'end-2',
+            )}
+          >
+            <SelectPrimitive.ItemIndicator>{indicator}</SelectPrimitive.ItemIndicator>
+          </span>
         ) : (

If intentional (custom indicators always visible), consider documenting this behavior difference.

🧹 Nitpick comments (49)
packages/typegen/web/src/App.css (2)

21-27: Verify color contrast for secondary text.

The .config-info uses #a1a1aa which is a light gray on a dark background context. While likely readable, ensure this meets WCAG AA contrast requirements, especially for users with low vision. Consider testing with an accessibility checker.

Consider extracting key colors to CSS variables (e.g., --color-text-secondary: #a1a1aa) to enable theme customization and easier future adjustments.

:root {
  --color-text-primary: #e4e4e7;
  --color-text-secondary: #a1a1aa;
  --color-bg-dark: #18181b;
  --color-status-success: #16a34a;
  --color-status-error: #dc2626;
  /* ... other theme tokens ... */
}

.config-info {
  color: var(--color-text-secondary);
}

107-127: Verify error message color contrast.

The error and validation messages use #fca5a5 text on #7f1d1d background. While likely readable, the contrast ratio should be verified to ensure it meets WCAG AA requirements (4.5:1 for normal text). Both colors are in the red spectrum, which may reduce perceptual contrast.

As suggested earlier, consider using CSS variables for error states:

:root {
  --color-error-bg: #7f1d1d;
  --color-error-text: #fca5a5;
  --color-error-border: #991b1b;
}

.error-message,
.validation-errors {
  background: var(--color-error-bg);
  border-color: var(--color-error-border);
  color: var(--color-error-text);
}

This enables easier theme adjustments and ensures consistency across all error UI.

packages/typegen/web/src/components/tree/default.tsx (1)

76-76: Consider a more descriptive component name.

The default export is named Component, which is generic and doesn't convey the component's purpose. Consider renaming to something like CrmTreeExample or DefaultTreeDemo for better discoverability and documentation.

packages/typegen/web/src/components/ui/tree.tsx (1)

11-16: Use TreeInstance<T> from @headless-tree/core for proper typing of the tree prop.

The tree property is currently typed as any in both TreeContextValue and TreeProps, losing type safety and IDE support. Import TreeInstance from @headless-tree/core (which is already a dependency) and type the property as TreeInstance<T> to align with the existing generic typing pattern used with ItemInstance<T>.

packages/typegen/src/formatting.ts (2)

15-23: Behavioral nuance when getFileInfo is unavailable

Using prettier.getFileInfo?.(filePath) ?? { ignored: false } preserves ignore behavior when getFileInfo exists, and falls back to “never ignored” when it doesn’t. That’s reasonable for robustness, but note that in environments where getFileInfo is missing you’ll silently stop honoring .prettierignore for these files.

If that trade‑off isn’t desired, you might instead:

  • Fail fast when getFileInfo is undefined, or
  • Log a warning once so it’s obvious ignores aren’t applied.

The prettier.format usage with filepath looks correct and keeps parser inference wired through the file path.


9-27: Avoid silently swallowing formatting errors

The empty catch (error) {} means any Prettier/IO error will be completely silent, but await project.save(); will still run, potentially leaving partially formatted files with no signal to the caller.

Consider at least logging, or rethrowing after logging, e.g.:

-  } catch (error) {}
-  await project.save();
+  } catch (error) {
+    // Consider routing this through your logger instead of console
+    console.error("Failed to format and save source files", error);
+  }
+  await project.save();

Or, if you want per‑file resilience, move the try/catch inside the loop so one bad file doesn’t block formatting the rest.

packages/typegen/scripts/build.ts (1)

1-5: Track removal of this placeholder file.

This file exists only for git worktree compatibility, with the actual build logic moved to build-copy.js. Consider tracking its removal once the worktree is updated to avoid maintaining dead code.

Do you want me to open a new issue to track the removal of this placeholder file after the worktree update?

packages/typegen/web/src/components/switch/button.tsx (1)

5-5: Remove unused variable.

The id variable is generated but never used. The Switch component on line 10 doesn't reference this id.

Apply this diff:

 export default function SwitchDemo() {
-  const id = useId();
-
   return (

And remove the id prop from the Switch:

-        <Switch id={id} size="xl" className="w-full rounded-md h-9" thumbClassName="rounded-md" />
+        <Switch size="xl" className="w-full rounded-md h-9" thumbClassName="rounded-md" />
packages/typegen/web/src/components/ui/textarea.tsx (1)

29-35: Consider adding ref forwarding for form library compatibility.

The Textarea component doesn't forward refs, which may cause issues with form libraries like react-hook-form that need to attach refs to form elements. Most UI primitives that wrap native form elements should use React.forwardRef.

Apply this diff to add ref forwarding:

-function Textarea({
+const Textarea = React.forwardRef<
+  HTMLTextAreaElement,
+  React.ComponentProps<'textarea'> & VariantProps<typeof textareaVariants>
+>(function Textarea({
   className,
   variant,
   ...props
-}: React.ComponentProps<'textarea'> & VariantProps<typeof textareaVariants>) {
-  return <textarea data-slot="textarea" className={cn(textareaVariants({ variant }), className)} {...props} />;
-}
+}, ref) {
+  return <textarea ref={ref} data-slot="textarea" className={cn(textareaVariants({ variant }), className)} {...props} />;
+});
packages/typegen/web/src/components/LayoutSelector.tsx (2)

66-67: Type the error object instead of using any.

Casting to any bypasses TypeScript's type safety. Define a proper error type for better type safety and maintainability.

Define an error type:

type LayoutError = Error & {
  details?: {
    missing?: {
      server?: boolean;
      db?: boolean;
      auth?: boolean;
      password?: boolean;
    };
    fmErrorCode?: string | number;
    suspectedField?: 'server' | 'db' | 'auth';
  };
};

Then use it:

-  // Extract error details from the error object
-  const errorDetails = error && (error as any).details;
+  const errorDetails = error && (error as LayoutError).details;

205-219: Avoid string replacement for deriving form paths.

Using string replacement to derive the schema name path from the layout name path is fragile and error-prone. If the field naming convention changes, this will break silently.

Consider a more explicit approach:

                                // If schema name is undefined or empty, set it to the layout name
                                if (newValue) {
-                                  const schemaNamePath = path.replace(
-                                    ".layoutName",
-                                    ".schemaName",
-                                  ) as Path<FormData>;
+                                  const schemaNamePath = `config.${configIndex}.schemaName` as Path<FormData>;
                                  const currentSchemaName =
                                    getValues(schemaNamePath);

Or pass the schema name path as a prop alongside the layout name path for explicitness.

packages/typegen/web/src/components/ui/alert.tsx (3)

218-220: Type annotation mismatch: uses HTMLHeadingElement but renders a div.

AlertTitle declares its props as React.HTMLAttributes<HTMLHeadingElement>, but the component renders a <div>. Consider using HTMLDivElement for consistency, or render an actual heading element (e.g., <h4>) for semantic correctness.

-function AlertTitle({ className, ...props }: React.HTMLAttributes<HTMLHeadingElement>) {
-  return <div data-slot="alert-title" className={cn('grow tracking-tight', className)} {...props} />;
+function AlertTitle({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
+  return <div data-slot="alert-title" className={cn('grow tracking-tight', className)} {...props} />;
 }

238-246: Type annotation mismatch: uses HTMLParagraphElement but renders a div.

Both AlertDescription and AlertContent declare props as React.HTMLAttributes<HTMLParagraphElement>, but render <div> elements. Update the type annotations for consistency.

-function AlertDescription({ className, ...props }: React.HTMLAttributes<HTMLParagraphElement>) {
+function AlertDescription({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
   return (
     <div
       data-slot="alert-description"
...
-function AlertContent({ className, ...props }: React.HTMLAttributes<HTMLParagraphElement>) {
+function AlertContent({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
   return (
     <div
       data-slot="alert-content"

Also applies to: 248-256


230-236: AlertToolbar uses AlertIconProps but doesn't utilize variant props.

AlertToolbar accepts AlertIconProps (which includes variant, icon, appearance, size from VariantProps) but doesn't use any of these props. Consider using a simpler type like React.HTMLAttributes<HTMLDivElement> to avoid confusion.

-function AlertToolbar({ children, className, ...props }: AlertIconProps) {
+function AlertToolbar({ children, className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
   return (
     <div data-slot="alert-toolbar" className={cn(className)} {...props}>
       {children}
     </div>
   );
 }
packages/typegen/web/src/components/ui/command.tsx (1)

113-126: Misleading interface name ButtonArrowProps for CommandCheck.

The interface ButtonArrowProps is used for CommandCheck but the name suggests it's for a button arrow component. Consider renaming for clarity.

-interface ButtonArrowProps extends React.SVGProps<SVGSVGElement> {
+interface CommandCheckProps extends React.SVGProps<SVGSVGElement> {
   icon?: LucideIcon; // Allows passing any Lucide icon
 }

-function CommandCheck({ icon: Icon = Check, className, ...props }: ButtonArrowProps) {
+function CommandCheck({ icon: Icon = Check, className, ...props }: CommandCheckProps) {
   return (
packages/typegen/web/index.html (1)

8-29: Dark mode handling is functional; consider adding user preference persistence.

The inline script correctly applies dark mode based on system preferences before the page renders, preventing visual flashing. The implementation properly:

  • Detects system preference using matchMedia
  • Applies the dark class to documentElement
  • Listens for changes to system preference

Consider enhancing the dark mode logic to respect user preferences stored in localStorage:

 <script>
   // Apply dark mode class based on system preference before page renders
   (function () {
+    // Check for stored user preference first
+    const stored = localStorage.getItem('theme');
+    if (stored === 'dark') {
+      document.documentElement.classList.add("dark");
+      return;
+    } else if (stored === 'light') {
+      return;
+    }
+    
+    // Fall back to system preference
     const prefersDark = window.matchMedia(
       "(prefers-color-scheme: dark)",
     ).matches;
     if (prefersDark) {
       document.documentElement.classList.add("dark");
     }

     // Listen for changes to system preference
     window
       .matchMedia("(prefers-color-scheme: dark)")
       .addEventListener("change", (e) => {
+        // Only respond if user hasn't set a preference
+        if (localStorage.getItem('theme')) return;
+        
         if (e.matches) {
           document.documentElement.classList.add("dark");
         } else {
           document.documentElement.classList.remove("dark");
         }
       });
   })();
 </script>
packages/typegen/web/src/lib/utils.ts (1)

10-12: LGTM! Consider standardizing the cn utility across the codebase.

The implementation correctly combines clsx for conditional class normalization with twMerge for Tailwind conflict resolution. This is the recommended pattern for a class name utility.

However, there's an inconsistency in the codebase: apps/docs/src/lib/cn.ts exports twMerge directly without the clsx wrapper:

// apps/docs/src/lib/cn.ts
twMerge as cn

Your implementation here is more robust as it handles conditional classes properly. Consider updating the docs version to match this pattern for consistency.

#!/bin/bash
# Search for other cn utility implementations across the codebase
echo "=== Finding all cn utility definitions ==="
rg -n --type=ts --type=tsx -A3 'export (function|const) cn'

echo -e "\n=== Finding direct twMerge exports as cn ==="
rg -n --type=ts 'twMerge as cn'

echo -e "\n=== Finding cn utility imports ==="
rg -n --type=ts --type=tsx "import.*cn.*from"
packages/typegen/web/src/main.tsx (1)

7-15: Review aggressive retry configuration.

The retry configuration attempts 5 retries with exponential backoff up to 30 seconds. For a local development UI server, this seems excessive and could lead to poor UX if the server is down.

Consider reducing retries for local development:

 const queryClient = new QueryClient({
   defaultOptions: {
     queries: {
-      retry: 5,
+      retry: 2,
       retryDelay: (attemptIndex) => Math.min(1000 * 2 ** attemptIndex, 30000),
       refetchOnWindowFocus: false,
     },
   },
 });
packages/typegen/web/src/lib/envValues.ts (2)

7-9: Clarify return behavior when envName is undefined.

When envName is undefined, the function returns undefined inside the queryFn, but the query still succeeds with data: undefined. This could be confusing—consider whether the query should be disabled entirely or if this is the intended behavior.

If the intention is to always have the query disabled when envName is undefined, the current enabled: !!envName on line 18 already handles this. However, the early return on line 8 is redundant. Consider removing it:

   queryFn: async () => {
-     if (!envName) return undefined;
     const res = await client.api["env-names"].$get({
       query: { envName },
     });

Or if you want to keep the guard for type safety, make it explicit:

   queryFn: async () => {
-    if (!envName) return undefined;
+    // This should never happen due to enabled: !!envName, but guard for type safety
+    if (!envName) throw new Error("envName is required");
     const res = await client.api["env-names"].$get({

9-11: Rename endpoint to reflect its singular nature.

The endpoint /api/env-names uses a plural name but fetches a single environment variable value for the provided envName. This is inconsistent with the client-side naming (useEnvValue, query key envValue) and the response structure ({ value }). Rename to /api/env-value or /api/env-name for clarity.

packages/typegen/web/src/components/ServerEnvField.tsx (1)

42-42: Review fallback logic with || operator.

The expression envName || defaultValue will use defaultValue when envName is an empty string (""), which may not be the intended behavior. If an empty string is a valid env name that should be queried, use nullish coalescing instead.

If empty strings should be treated as "no env name", the current code is correct. However, if empty strings are valid env names, apply this diff:

- const { data: envValue, isLoading } = useEnvValue(envName || defaultValue);
+ const { data: envValue, isLoading } = useEnvValue(envName ?? defaultValue);

And update line 54:

-         {(envName || defaultValue) && (
+         {(envName ?? defaultValue) && (
packages/typegen/web/src/components/ConfigList.css (1)

80-81: Remove trailing blank lines.

There are extra blank lines at the end of the file.

packages/typegen/web/src/hooks/useConfig.ts (1)

53-55: Simplify isRetrying logic.

The expression (isLoadingConfig || isFetching) && isFetching simplifies to just isFetching. If the intent is to detect retries specifically, consider using the failureCount from useQuery instead.

  return {
    // ...
    isLoading: isLoadingConfig || isFetching,
-   isRetrying: (isLoadingConfig || isFetching) && isFetching,
+   isRetrying: isFetching && !isLoadingConfig, // fetching but not initial load
  };

Alternatively, extract failureCount from useQuery to accurately detect retry attempts.

packages/typegen/web/src/components/ConfigSummary.tsx (2)

4-19: Unnecessary optional chaining on required prop.

config is a required prop, so the optional chaining in config?.type on line 18 is unnecessary. Consider using config.type directly for consistency.

-       {config?.type.toUpperCase()}
+       {config.type.toUpperCase()}

30-32: Consider using a single export pattern.

The component has both a named export and a default export. Consider using only one pattern for consistency across the codebase.

packages/typegen/src/typegen.ts (1)

201-202: Redundant type check.

The condition config?.type === "fmdapi" is always true here because the function parameter is already typed as Extract<z.infer<typeof typegenConfigSingle>, { type: "fmdapi" }>. This check can be simplified.

      webviewerScriptName:
-       config?.type === "fmdapi" ? config.webviewerScriptName : undefined,
+       config.webviewerScriptName,
packages/typegen/web/src/components/ConfigList.tsx (1)

3-4: Consider using a typed interface instead of unknown[].

The configs: unknown[] type forces unsafe type assertions in getConfigLabel. Since this component is part of the config management UI, consider importing and using SingleConfig from config-utils.ts for better type safety.

+import { SingleConfig } from "../lib/config-utils";
+
 interface ConfigListProps {
-  configs: unknown[];
+  configs: SingleConfig[];
   onSelectConfig: (index: number) => void;
   onAddConfig: () => void;
 }
packages/typegen/web/src/App.tsx (2)

55-55: Remove console.log before merging.

Debug logging should be removed from production code.

   useEffect(() => {
-    console.log("configData from useEffect", configDataResponse);
     if (configDataResponse) {

88-89: Remove console.error or use proper error handling.

Consider using a toast notification or UI feedback for save failures instead of just logging to console, consistent with how success feedback might be shown elsewhere in the app.

packages/typegen/web/src/components/dialog/default.tsx (1)

28-30: Move schema definition outside the component.

FormSchema is recreated on every render. Moving it outside the component avoids unnecessary object recreation and improves performance.

+const FormSchema = z.object({
+  feedback: z.string().min(1, 'Feedback is required').max(200, 'Feedback cannot exceed 200 characters'),
+});
+
 export default function DialogDemo() {
   const [open, setOpen] = useState(false);
   const direction = useDirection();
 
-  const FormSchema = z.object({
-    feedback: z.string().min(1, 'Feedback is required').max(200, 'Feedback cannot exceed 200 characters'),
-  });
-
   const form = useForm<z.infer<typeof FormSchema>>({
packages/typegen/web/src/components/EnvVarField.tsx (1)

46-61: Consider extracting debounce logic into a reusable hook.

The manual timer management works correctly, but this pattern could be extracted into a useDebouncedValue hook for reuse across components. This is a minor refactor suggestion for maintainability.

// Example: useDebouncedValue.ts
function useDebouncedValue<T>(value: T, delay: number): T {
  const [debouncedValue, setDebouncedValue] = useState(value);
  
  useEffect(() => {
    const timer = setTimeout(() => setDebouncedValue(value), delay);
    return () => clearTimeout(timer);
  }, [value, delay]);
  
  return debouncedValue;
}
packages/typegen/web/src/components/useEnvVarIndicator.ts (2)

6-7: Consider using a proper type instead of any[] for better type safety.

Using any[] loses the benefits of TypeScript type checking. Consider importing and using SingleConfig or the appropriate form type to maintain type safety throughout the hook.

+import type { SingleConfig } from "../lib/config-utils";
+
 export function useEnvVarIndicator(index: number) {
-  const { control } = useFormContext<{ config: any[] }>();
+  const { control } = useFormContext<{ config: SingleConfig[] }>();

57-70: The hasCustomValues logic duplicates the auth checking from lines 31-47.

Consider simplifying by reusing the already-computed authEnvName:

   const hasCustomValues =
     (envNamesServer && envNamesServer.trim() !== "") ||
-    (envNamesDb && envNamesDb.trim() !== "") ||
-    (envNamesAuth &&
-      typeof envNamesAuth === "object" &&
-      (("apiKey" in envNamesAuth &&
-        envNamesAuth.apiKey &&
-        envNamesAuth.apiKey.trim() !== "") ||
-        ("username" in envNamesAuth &&
-          envNamesAuth.username &&
-          envNamesAuth.username.trim() !== "") ||
-        ("password" in envNamesAuth &&
-          envNamesAuth.password &&
-          envNamesAuth.password.trim() !== "")));
+    (envNamesDb && envNamesDb.trim() !== "") ||
+    !!authEnvName;
packages/typegen/web/src/components/EnvVarDialog.tsx (2)

30-32: Consider using a proper type instead of any[].

Same as in useEnvVarIndicator, using any[] loses type safety. Import and use SingleConfig type for consistency.

+import type { SingleConfig } from "../lib/config-utils";
+
   const { control, setValue, getValues } = useFormContext<{
-    config: any[];
+    config: SingleConfig[];
   }>();

92-122: Remove authTypeSelector from the dependency array to avoid potential update loops.

Including authTypeSelector in dependencies while also calling setAuthTypeSelector inside the effect is a code smell. The guard at line 119 prevents infinite loops, but the effect should only run when envNamesAuth changes.

-  }, [envNamesAuth, setValue, getValues, index, authTypeSelector]);
+  // eslint-disable-next-line react-hooks/exhaustive-deps
+  }, [envNamesAuth, setValue, getValues, index]);

Alternatively, use a ref to track the previous auth type if you need to compare values without triggering re-runs.

packages/typegen/src/server/index.ts (1)

88-96: Simplify return statement: remove unnecessary Promise.resolve.

The function is already async, so returning an object directly will automatically wrap it in a resolved promise.

-  // The serve function from @hono/node-server already starts listening
-  // We just need to return the server with a close method
-  return Promise.resolve({
+  return {
     port: actualPort,
     close: () => {
       server.close();
     },
-  });
+  };
packages/typegen/src/server/api.ts (2)

89-97: Consider returning 400 for malformed JSON input.

If JSON.parse(body) throws, the outer catch returns a 500 error. For client-side JSON syntax errors, a 400 (Bad Request) would be more appropriate.

   try {
     const body = await readRequestBody(req);
-    const data = JSON.parse(body);
+    let data;
+    try {
+      data = JSON.parse(body);
+    } catch {
+      return {
+        status: 400,
+        headers: { "Content-Type": "application/json" },
+        body: JSON.stringify({
+          success: false,
+          error: "Invalid JSON in request body",
+        }),
+      };
+    }

140-153: Consider adding a body size limit to prevent memory exhaustion.

The readRequestBody function accumulates all chunks without a size limit. For a local dev tool this is low-risk, but adding a reasonable limit (e.g., 1MB) would be safer.

+const MAX_BODY_SIZE = 1024 * 1024; // 1MB

 function readRequestBody(req: IncomingMessage): Promise<string> {
   return new Promise((resolve, reject) => {
     let body = "";
     req.on("data", (chunk) => {
       body += chunk.toString();
+      if (body.length > MAX_BODY_SIZE) {
+        req.destroy();
+        reject(new Error("Request body too large"));
+      }
     });
packages/typegen/web/src/components/ui/tooltip.tsx (1)

7-17: Note: Each Tooltip creates its own TooltipProvider.

This pattern means each tooltip instance has its own provider context, which isolates delayDuration and other settings per-tooltip rather than sharing app-wide defaults. This is a valid design choice for component isolation but differs from the typical pattern of a single app-level provider.

Also, the data-slot attribute on TooltipProvider (line 8) won't appear in the DOM since Provider doesn't render a DOM element.

Consider removing the unused data-slot from the Provider:

 function TooltipProvider({ delayDuration = 0, ...props }: React.ComponentProps<typeof TooltipPrimitive.Provider>) {
-  return <TooltipPrimitive.Provider data-slot="tooltip-provider" delayDuration={delayDuration} {...props} />;
+  return <TooltipPrimitive.Provider delayDuration={delayDuration} {...props} />;
 }
packages/typegen/web/src/hooks/useTestConnection.ts (1)

82-100: Consider typing the error attachment more explicitly.

Using (error as any).details is a common pattern but loses type safety. Consider creating a typed error class or using a discriminated union for the result.

// Alternative: Create a typed error class
class TestConnectionError extends Error {
  constructor(message: string, public details: TestConnectionResult) {
    super(message);
  }
}

Also, the type assertion on line 99 (result as TestConnectionResult) relies on the server response matching the interface. The hc client should already provide proper typing based on the API contract.

packages/typegen/src/server/app.ts (2)

246-256: Type assertion on client.layouts() due to inference issues.

The comment explains the type assertion is needed because "TypeScript has inference issues with DataApi return type." While this works, consider adding a more specific type guard or improving the DataApi types upstream to avoid as any casts.


100-184: Redundant Zod schema definitions in POST /api/config response handling.

The response schema is defined inline multiple times (lines 121-138, 149-162, 165-177). Consider extracting this to a reusable constant.

+const configResponseSchema = z.object({
+  success: z.boolean(),
+  error: z.string().optional(),
+  issues: z
+    .array(
+      z.object({
+        path: z.array(z.union([z.string(), z.number()])),
+        message: z.string(),
+      }),
+    )
+    .optional(),
+});

 // Then use it:
-const response = z.object({ ... }).parse({ success: false, ... });
+const response = configResponseSchema.parse({ success: false, ... });
packages/typegen/web/src/components/ui/badge.tsx (2)

186-209: Badge component type mismatch with BadgeProps interface.

The Badge function signature (lines 195-196) defines its own props type inline rather than using the defined BadgeProps interface. This causes the dotClassName property from BadgeProps to be unused.

 function Badge({
   className,
   variant,
   size,
   appearance,
   shape,
   asChild = false,
   disabled,
   ...props
-}: React.ComponentProps<"span"> &
-  VariantProps<typeof badgeVariants> & { asChild?: boolean }) {
+}: Omit<BadgeProps, 'dotClassName'>) {

211-227: BadgeButton uses span with role="button" instead of actual button element.

The component renders as span with role="button" but doesn't include keyboard event handlers (onKeyDown for Enter/Space). For full accessibility, interactive elements with role="button" should be keyboard accessible.

Consider using an actual button element when asChild is false, or add keyboard handlers:

-  const Comp = asChild ? SlotPrimitive.Slot : "span";
+  const Comp = asChild ? SlotPrimitive.Slot : "button";
   return (
     <Comp
       data-slot="badge-button"
       className={cn(badgeButtonVariants({ variant, className }))}
-      role="button"
+      type="button"
       {...props}
     />
   );
packages/typegen/web/src/components/ui/switch.tsx (1)

56-84: Size variant inconsistency between switch and thumb.

switchThumbVariants defines xs size (line 65), but switchVariants (lines 36-41) only defines sm, md, lg, xl. If size="xs" is passed, the thumb will apply the compound variant but the switch itself won't have corresponding dimensions.

Consider either adding xs to switchVariants or removing it from switchThumbVariants to maintain consistency.

packages/typegen/src/server/contract.ts (1)

1-7: Remove unused import.

typegenConfig is imported on line 2 but never used in this file.

-import { typegenConfigSingle, typegenConfig } from "../types";
+import { typegenConfigSingle } from "../types";
packages/typegen/web/src/components/ui/form.tsx (2)

101-116: Type mismatch between props and rendered element.

The props are typed as React.HTMLAttributes<HTMLParagraphElement> but the component renders a <div>. Consider aligning the type with the rendered element.

-function FormDescription({ className, ...props }: React.HTMLAttributes<HTMLParagraphElement>) {
+function FormDescription({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {

118-136: Type mismatch between props and rendered element.

Same issue as FormDescription - props typed as HTMLParagraphElement but renders a <div>.

-function FormMessage({ className, children, ...props }: React.HTMLAttributes<HTMLParagraphElement>) {
+function FormMessage({ className, children, ...props }: React.HTMLAttributes<HTMLDivElement>) {
packages/typegen/src/types.ts (1)

84-88: Consider the order of .default() and .optional().

The combination .default("schema").optional() may not behave as expected. With .default() first, undefined inputs become "schema", so the .optional() mainly affects type inference. If the intent is for the field to be optional with a default when omitted, the current order works but may confuse readers.

If the field should always have a value (defaulting to "schema"), consider removing .optional():

 const path = z
   .string()
   .default("schema")
-  .optional()
   .meta({ description: "The folder path to output the generated files" });

Alternatively, if undefined should be a valid explicit value, .optional().default("schema") would only apply the default when the key is missing, not when explicitly undefined.

type?: "text" | "password";
}

export function EnvVarField({
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Fix naming inconsistency.

The file is named ServerEnvField.tsx but exports EnvVarField. This inconsistency can cause confusion when importing or searching for the component.

Either rename the file to match the export:

git mv packages/typegen/web/src/components/ServerEnvField.tsx packages/typegen/web/src/components/EnvVarField.tsx

Or update the export to match the filename:

-export function EnvVarField({
+export function ServerEnvField({
🤖 Prompt for AI Agents
In packages/typegen/web/src/components/ServerEnvField.tsx around line 25 the
component is exported as EnvVarField which mismatches the filename
ServerEnvField.tsx; fix by making names consistent — either rename the file to
EnvVarField.tsx (git mv ...) so the filename matches the exported component, or
change the exported component name to ServerEnvField to match the file and
update any imports accordingly.

Comment on lines +6 to +45
@theme inline {
--radius-sm: calc(var(--radius) - 4px);
--radius-md: calc(var(--radius) - 2px);
--radius-lg: var(--radius);
--radius-xl: calc(var(--radius) + 4px);
--radius-2xl: calc(var(--radius) + 8px);
--radius-3xl: calc(var(--radius) + 12px);
--radius-4xl: calc(var(--radius) + 16px);
--color-background: var(--background);
--color-foreground: var(--foreground);
--color-card: var(--card);
--color-card-foreground: var(--card-foreground);
--color-popover: var(--popover);
--color-popover-foreground: var(--popover-foreground);
--color-primary: var(--primary);
--color-primary-foreground: var(--primary-foreground);
--color-secondary: var(--secondary);
--color-secondary-foreground: var(--secondary-foreground);
--color-muted: var(--muted);
--color-muted-foreground: var(--muted-foreground);
--color-accent: var(--accent);
--color-accent-foreground: var(--accent-foreground);
--color-destructive: var(--destructive);
--color-border: var(--border);
--color-input: var(--input);
--color-ring: var(--ring);
--color-chart-1: var(--chart-1);
--color-chart-2: var(--chart-2);
--color-chart-3: var(--chart-3);
--color-chart-4: var(--chart-4);
--color-chart-5: var(--chart-5);
--color-sidebar: var(--sidebar);
--color-sidebar-foreground: var(--sidebar-foreground);
--color-sidebar-primary: var(--sidebar-primary);
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
--color-sidebar-accent: var(--sidebar-accent);
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
--color-sidebar-border: var(--sidebar-border);
--color-sidebar-ring: var(--sidebar-ring);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Inconsistent CSS custom property naming between theme definition and usage.

The @theme inline block defines tokens with the --color- prefix (e.g., --color-background, --color-primary), but the :root and .dark sections use unprefixed names (e.g., --background, --primary). This mismatch means the theme tokens reference variables that don't exist at parse time. Either:

  1. Rename the variables in @theme to match the unprefixed names used in :root and .dark, or
  2. Update :root and .dark to use the --color- prefixed names.

Option 1 is recommended for consistency with standard CSS naming conventions.

 @theme inline {
-  --color-background: var(--background);
+  --background: var(--background);
-  --color-foreground: var(--foreground);
+  --foreground: var(--foreground);
   // ... and so on for all --color-* properties
 }

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In packages/typegen/web/src/index.css around lines 6-45, the @theme inline block
defines variables with a --color- prefix while :root and .dark use unprefixed
names (e.g., --background, --primary), causing mismatched references; fix by
renaming the tokens in the @theme block to the unprefixed names used elsewhere
(replace --color-background → --background, --color-primary → --primary, etc.)
so all variable names match, or alternatively update :root/.dark to use the
--color- prefixed names—prefer renaming the @theme variables to the unprefixed
form for consistency.

@pkg-pr-new
Copy link

pkg-pr-new bot commented Dec 15, 2025

Open in StackBlitz

@proofkit/better-auth

pnpm add https://pkg.pr.new/proofgeist/proofkit/@proofkit/better-auth@78

@proofkit/cli

pnpm add https://pkg.pr.new/proofgeist/proofkit/@proofkit/cli@78

create-proofkit

pnpm add https://pkg.pr.new/proofgeist/proofkit/create-proofkit@78

@proofkit/fmdapi

pnpm add https://pkg.pr.new/proofgeist/proofkit/@proofkit/fmdapi@78

@proofkit/fmodata

pnpm add https://pkg.pr.new/proofgeist/proofkit/@proofkit/fmodata@78

@proofkit/typegen

pnpm add https://pkg.pr.new/proofgeist/proofkit/@proofkit/typegen@78

@proofkit/webviewer

pnpm add https://pkg.pr.new/proofgeist/proofkit/@proofkit/webviewer@78

commit: bd77c9a

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (5)
packages/registry/tsdown.config.ts (1)

21-41: Consider logging a warning if expected declaration files are not found.

The code silently continues if dtsFile or dtsMapFile are not found. Since dts: true is explicitly enabled, the absence of these files might indicate a build problem. Adding a warning message would help diagnose issues.

Apply this diff to add warning logs:

   if (dtsFile) {
     fs.renameSync(
       path.join(distLib, dtsFile),
       path.join(distLib, "index.d.ts"),
     );
     console.log(`✅ Renamed ${dtsFile} to index.d.ts`);
+  } else {
+    console.warn("⚠️  No hashed .d.ts file found to rename");
   }
 
   if (dtsMapFile) {
     fs.renameSync(
       path.join(distLib, dtsMapFile),
       path.join(distLib, "index.d.ts.map"),
     );
     console.log(`✅ Renamed ${dtsMapFile} to index.d.ts.map`);
+  } else {
+    console.warn("⚠️  No hashed .d.ts.map file found to rename");
   }
packages/registry/package.json (2)

27-29: Build/dev script pipeline looks coherent; consider dev-ergonomics later

prebuildbuilddev wiring is consistent, and running validation before tsdown && publint --strict makes sense. The dev script does a full build and then re-runs tsdown + tsc --emitDeclarationOnly on every change, which is correct but may be heavier than necessary on large templates; if it becomes a pain, consider switching to dedicated --watch flows instead of rebuilding from scratch each time.


55-55: Zod v4 bump: verify continued use of zod/v3 and consider consolidating on v4 API

You’ve upgraded this package to zod@^4.2.0 while packages/registry/lib/types.ts still imports from "zod/v3". If that subpath is intentionally used as a v3‑compat entrypoint with Zod 4, that’s fine, but it’s worth double‑checking that:

  • "zod/v3" is still a supported and stable path in the version you’re targeting.
  • You’re not unintentionally mixing v3 semantics with v4 expectations elsewhere.

Longer term, you may want to migrate the registry types to the main Zod 4 API (import { z } from "zod") to avoid relying on compatibility shims.

packages/registry/scripts/validate.ts (1)

1-26: Prebuild validator wiring is solid; jiti usage is reasonable

The script cleanly resolves the validator path relative to itself, uses jiti to execute the TS source before build, and exits non‑zero on failure, which is exactly what you want for a prebuild gate. You might eventually simplify this by importing validateRegistry directly (letting tsx handle TS) instead of layering jiti, but the current approach is consistent and explicit.

packages/registry/lib/types.ts (1)

192-205: RegistryIndex schema and type are consistent; consider deriving the type from the schema

The RegistryIndex structural type matches what registryIndexSchema produces ({ name, title, category, description? }[] derived from templateMetadataSchema), which is good for keeping the index view in sync with template metadata.

To reduce duplication and avoid future drift, you could instead define the type from the schema:

export const registryIndexSchema = templateMetadataSchema
  .pick({ title: true, category: true, description: true })
  .extend({ name: z.string() })
  .array();

export type RegistryIndex = z.infer<typeof registryIndexSchema>;

Not required, but it keeps the contract DRY and guarantees the TS type always matches the Zod schema.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 455dcef and c511447.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (6)
  • packages/registry/lib/types.ts (2 hunks)
  • packages/registry/lib/validator.ts (1 hunks)
  • packages/registry/package.json (2 hunks)
  • packages/registry/scripts/validate.ts (1 hunks)
  • packages/registry/tsconfig.json (1 hunks)
  • packages/registry/tsdown.config.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
packages/registry/tsdown.config.ts (1)
apps/docs/scripts/bundle-registry-templates.js (2)
  • fs (3-3)
  • path (4-4)
packages/registry/scripts/validate.ts (1)
packages/registry/lib/validator.ts (1)
  • validateRegistry (107-168)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: build
  • GitHub Check: build
🔇 Additional comments (5)
packages/registry/tsconfig.json (1)

23-24: Verify test file exclusion pattern covers all test files in lib.

The exclude pattern *.test.ts only matches test files at the root level. If test files exist within lib (e.g., lib/utils.test.ts), they won't be excluded and will be included by the include: ["lib/**/*"] directive.

Confirm whether:

  1. Test files should be excluded from type-checking in this package
  2. Test files exist within lib/ and, if so, update the pattern to **/*.test.ts or lib/**/*.test.ts

If test files are intentionally included, this comment can be disregarded.

packages/registry/tsdown.config.ts (2)

15-16: LGTM!

Dynamic imports for Node.js built-in modules are appropriate in this async context.


8-8: The code correctly implements the expected behavior—tsdown 0.14.2 does append content hashes to declaration filenames by default (via the rolldown-plugin-dts plugin). The renaming logic to convert index-<hash>.d.ts to index.d.ts is necessary and properly handles the hashed output.

packages/registry/package.json (1)

46-50: Tooling version bumps look fine; align with repo-wide constraints

The publint/tsdown/tsx/TypeScript/vitest bumps look reasonable and consistent with a modern toolchain. Just make sure these versions match any central constraints (root package.json/pnpm workspace) so you don’t end up with divergent tooling behavior between packages.

packages/registry/lib/validator.ts (1)

5-5: ./types.js import: confirm jiti can resolve this before build

Switching the import to ./types.js is correct for the compiled ESM output in dist. However, scripts/validate.ts now loads lib/validator.ts via jiti before the build runs, when only types.ts exists. Please confirm that jiti’s resolver will successfully handle an import of ./types.js from a TS source file (i.e., that it will map this to the TS file rather than requiring an actual types.js next to it). If not, you may want to revert to an extensionless import ("./types") or adjust the prebuild script to point at the built output instead.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
packages/typegen/web/src/components/ConfigEditor.tsx (1)

150-189: Consider wiring the label to the actual switch element for better a11y

Right now FormLabel ends up targeting the FormControl’s first child (the wrapping <div> via formItemId), while the switch itself has a separate id={generateClientSwitchId}. For assistive tech it’s usually preferable for the label’s htmlFor to reference the focusable control.

One option is to make the Switch the direct child of FormControl (or another Slot), so the formItemId ends up on the switch instead of an intermediate <div>.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c511447 and 5d7353a.

📒 Files selected for processing (6)
  • packages/typegen/package.json (3 hunks)
  • packages/typegen/web/src/App.tsx (1 hunks)
  • packages/typegen/web/src/components/ConfigEditor.tsx (1 hunks)
  • packages/typegen/web/src/components/ui/form.tsx (1 hunks)
  • packages/typegen/web/src/lib/api.ts (1 hunks)
  • packages/typegen/web/src/main.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • packages/typegen/web/src/App.tsx
  • packages/typegen/web/src/main.tsx
  • packages/typegen/web/src/lib/api.ts
🧰 Additional context used
🧬 Code graph analysis (1)
packages/typegen/web/src/components/ConfigEditor.tsx (8)
packages/typegen/web/src/lib/config-utils.ts (1)
  • SingleConfig (4-4)
packages/typegen/web/src/components/EnvVarDialog.tsx (1)
  • EnvVarDialog (29-417)
packages/typegen/web/src/components/ui/form.tsx (5)
  • FormField (184-184)
  • FormItem (185-185)
  • FormLabel (186-186)
  • FormControl (182-182)
  • FormMessage (187-187)
packages/typegen/web/src/components/InfoTooltip.tsx (1)
  • InfoTooltip (4-15)
packages/typegen/web/src/components/ui/input.tsx (1)
  • Input (163-163)
packages/typegen/web/src/components/ui/select.tsx (5)
  • Select (224-224)
  • SelectTrigger (233-233)
  • SelectValue (234-234)
  • SelectContent (225-225)
  • SelectItem (228-228)
packages/typegen/web/src/components/ui/switch.tsx (3)
  • SwitchWrapper (191-191)
  • Switch (191-191)
  • SwitchIndicator (191-191)
packages/typegen/web/src/components/LayoutEditor.tsx (1)
  • LayoutEditor (12-85)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: build
  • GitHub Check: build
🔇 Additional comments (8)
packages/typegen/web/src/components/ConfigEditor.tsx (1)

36-38: Hook placement issue resolved; id now computed at component top level

Moving useId() to the top level (baseId / generateClientSwitchId) fixes the earlier Rules of Hooks violation in the Generate switch. The switch is now getting a stable id and there’s no hook call inside the FormField render callback.

Also applies to: 150-189

packages/typegen/web/src/components/ui/form.tsx (1)

74-96: Previous circular dependency issue between FormItem and useFormField resolved

FormItem now derives error directly from useFormContext and FormFieldContext instead of calling useFormField, so it no longer depends on FormItemContext being established before useFormField runs. That removes the earlier circular context issue noted in past reviews.

packages/typegen/package.json (6)

31-42: Exports look correct.

The ./api and ./api-app exports are properly differentiated, with each pointing to the appropriate module. This addresses the previous review concern about duplicate export paths.


9-10: The --no-open flag is properly implemented in the CLI at packages/typegen/src/cli.ts. The flag is defined via commander.js and correctly suppresses automatic browser opening when set (if (options.open !== false)). No changes needed.


12-12: No issues found. The scripts/build-copy.js file exists, is tracked in version control, and is correctly implemented. It safely copies built web assets from web/dist to dist/web with proper existence checks and error handling.


84-84: No action needed. The codebase already imports explicitly from "zod/v4" (packages/typegen/src/types.ts line 1) and uses modern Zod v4 patterns throughout (z.union, z.discriminatedUnion, z.preprocess, .default(), etc.). Zod v4 compatibility has already been addressed during implementation.


93-94: Vitest v4 upgrade is properly implemented and compatible.

The upgrade from Vitest 3.2.4 to 4.0.7 has been correctly applied:

  • vitest.config.ts uses the correct v4 API with defineConfig from "vitest/config"
  • Test file imports and assertions (including toMatchFileSnapshot()) are v4-compatible
  • Setup file for environment variables is compatible
  • No deprecated config options (poolMatchGlobs, environmentMatchGlobs, minWorkers, etc.) are present
  • No breaking change patterns detected (mock names, restoreAllMocks behavior, custom reporters/environments)

No action required.


68-69: Hono packages and related dependencies are compatible with each other and the project's Node.js >= 18 requirement:

  • @hono/node-server 1.19.7 is the official Node.js adapter for Hono v4.9.0 ✓
  • @hono/zod-validator 0.7.5 requires Zod (already present at v4.1.13) and runs on Node.js ✓
  • nodemon 3.1.11 and concurrently 8.2.2 both support Node.js 18+ ✓
  • open 10.1.0 is native ESM compatible with the project's "type": "module" configuration ✓

No version conflicts detected.

Comment on lines +5 to +8
import { Label } from "@/components/ui/label";
import { Slot } from "@radix-ui/react-slot";
import { Label as LabelPrimitive } from "radix-ui";
import {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, let's examine the actual file to see the imports and usage
cat -n packages/typegen/web/src/components/ui/form.tsx | head -120

Repository: proofgeist/proofkit

Length of output: 3845


🌐 Web query:

Radix UI react-label import pattern official documentation

💡 Result:

Official Radix docs show two equivalent import patterns:

  • Named export via the Radix meta package:
    import { Label } from "radix-ui"; then use <Label.Root />. [1]

  • Direct package (common in examples):
    import * as Label from "@radix-ui/react-label"; then use <Label.Root />. [2]

Sources: Radix Label docs and package page. [1][2]

References
[1] Radix Primitives — Label docs. https://www.radix-ui.com/primitives/docs/components/label
[2] @radix-ui/react-label (npm / package.json). https://www.npmjs.com/package/%40radix-ui/react-label


Fix type signature mismatch in FormLabel

The import pattern import { Label as LabelPrimitive } from "radix-ui" is valid per official Radix UI documentation (equivalent to import * as LabelPrimitive from "@radix-ui/react-label"). However, there's a type/implementation mismatch: the function signature declares props as React.ComponentProps<typeof LabelPrimitive.Root> but actually renders the custom <Label> component (from @/components/ui/label), not LabelPrimitive.Root.

Align the type signature with the actual implementation—either use LabelPrimitive.Root for rendering, or adjust the prop type to match the custom Label component's accepted props.

Also applies to: 98-113

🤖 Prompt for AI Agents
In packages/typegen/web/src/components/ui/form.tsx around lines 5-8 and 98-113,
the FormLabel component types are declared as React.ComponentProps<typeof
LabelPrimitive.Root> while the implementation actually renders the custom Label
imported from "@/components/ui/label"; update the type signature to match the
rendered component by replacing React.ComponentProps<typeof LabelPrimitive.Root>
with React.ComponentProps<typeof Label> (or alternatively change the rendered
element to LabelPrimitive.Root if you intend to use Radix's primitive), and
ensure any forwarded props and ref types align with the chosen component.

Comment on lines +26 to +28
const FormFieldContext = React.createContext<FormFieldContextValue>(
{} as FormFieldContextValue,
);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Guard useFormField against missing context; avoid using context values before validating

useFormField currently:

  • Creates FormFieldContext and FormItemContext with {} as ... defaults.
  • Immediately calls getFieldState(fieldContext.name, formState) and later checks if (!fieldContext), which will never trigger because {} is truthy.
  • Unconditionally destructures const { id } = itemContext, so when used outside a FormItem you’ll get id === undefined and undefined-form-item IDs.

This combination can lead to confusing runtime behavior (or errors) when FormLabel/FormControl/FormMessage are ever rendered outside the expected <FormField><FormItem>... nesting.

Consider tightening this:

-const FormFieldContext = React.createContext<FormFieldContextValue>(
-  {} as FormFieldContextValue,
-);
+const FormFieldContext = React.createContext<FormFieldContextValue | undefined>(
+  undefined,
+);

+const FormItemContext = React.createContext<FormItemContextValue | undefined>(
+  undefined,
+);

 const useFormField = () => {
   const fieldContext = React.useContext(FormFieldContext);
   const itemContext = React.useContext(FormItemContext);
   const { getFieldState, formState } = useFormContext();

-  const fieldState = getFieldState(fieldContext.name, formState);
-
-  if (!fieldContext) {
-    throw new Error("useFormField should be used within <FormField>");
-  }
-
-  const { id } = itemContext;
+  if (!fieldContext) {
+    throw new Error("useFormField should be used within <FormField>");
+  }
+  if (!itemContext) {
+    throw new Error("useFormField should be used within <FormItem>");
+  }
+
+  const fieldState = getFieldState(fieldContext.name, formState);
+  const { id } = itemContext;

   return {
     id,
     name: fieldContext.name,
     formItemId: `${id}-form-item`,
     formDescriptionId: `${id}-form-item-description`,
     formMessageId: `${id}-form-item-message`,
     ...fieldState,
   };
 };

This makes mis‑nesting fail fast with clear errors rather than undefined names/IDs leaking through.

Also applies to: 43-63, 66-72

🤖 Prompt for AI Agents
In packages/typegen/web/src/components/ui/form.tsx around lines 26-28 (and
similarly for the contexts/usages at lines 43-63 and 66-72): the code creates
FormFieldContext/FormItemContext with "{} as ..." defaults and then uses their
properties before verifying they exist, causing silent incorrect values or
undefined IDs when components are mis-nested. Change the contexts to be created
with undefined defaults (createContext<...|undefined>(undefined)); in
useFormField first read the contexts and immediately throw a clear Error if
fieldContext or itemContext are missing (e.g. "useFormField must be used within
<FormField>") before calling getFieldState or destructuring itemContext.id; only
call getFieldState and destructure id after the existence checks so mis-nesting
fails fast with a helpful error.

@eluce2 eluce2 changed the base branch from 12-07-orm to graphite-base/78 December 15, 2025 18:31
@eluce2 eluce2 force-pushed the 12-13-typegen_web_ui branch from 5d7353a to 940dd98 Compare December 15, 2025 18:32
@eluce2 eluce2 changed the base branch from graphite-base/78 to 12-07-orm December 15, 2025 18:32
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 13

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/typegen/src/cli.ts (1)

99-136: Normalize configParsed.data.config before passing it into generateTypedClients.

With typegenConfig now allowing either a single config or an array:

export const typegenConfig = z.object({
  config: z.union([z.array(typegenConfigSingle), typegenConfigSingle]),
});

configParsed.data.config can be SingleConfig | SingleConfig[], but runCodegen still passes it straight to generateTypedClients:

const result = await generateTypedClients(configParsed.data.config, {
  resetOverrides,
});

To avoid surprises (and keep generateTypedClients’s signature simple), consider normalizing here:

-  const result = await generateTypedClients(configParsed.data.config, {
+  const configs = Array.isArray(configParsed.data.config)
+    ? configParsed.data.config
+    : [configParsed.data.config];
+  const result = await generateTypedClients(configs, {
     resetOverrides,
   }).catch((err: unknown) => {

or explicitly update generateTypedClients’s API to accept the union and handle normalization internally.

♻️ Duplicate comments (6)
packages/registry/lib/types.ts (1)

150-160: Re-align TemplateMetadata with templateMetadataSchema to avoid unsound typing

TemplateMetadata extends z.infer<typeof registryItemSchema> (bringing in fields like name, type, docs, and the original files type), but templateMetadataSchema omits name, type, files, and docs and only re-adds files. Annotating the schema as z.ZodType<TemplateMetadata> means safeParse claims to return a TemplateMetadata that includes those omitted fields, even though they’re not validated or produced, and the files type is an intersection of two potentially different shapes. This is the same mismatch a prior review already flagged.

To keep the runtime shape and TypeScript type in sync, consider deriving the type from the schema instead of asserting it, e.g.:

-export type TemplateMetadata = z.infer<typeof registryItemSchema> & {
-  title: string;
-  description?: string;
-  category: z.infer<typeof categorySchema>;
-  files: TemplateFile[];
-  registryType: z.infer<typeof registryTypeSchema>;
-  postInstall?: PostInstallStep[];
-  minimumProofKitVersion?: string;
-  allowedFrameworks?: z.infer<typeof frameworkSchema>[];
-  schemaRequired?: boolean;
-};
-
-// Defines the metadata for a single template (_meta.ts)
-export const templateMetadataSchema: z.ZodType<TemplateMetadata> = registryItemSchema
-  .omit({ name: true, type: true, files: true, docs: true })
-  .extend({
+// Defines the metadata for a single template (_meta.ts)
+export const templateMetadataSchema = registryItemSchema
+  .omit({ name: true, type: true, files: true, docs: true })
+  .extend({
     title: z.string(),
     description: z.string().optional(),
     category: categorySchema,
     files: z.array(templateFileSchema),
     registryType: registryTypeSchema,
     postInstall: z
       .array(postInstallStepsSchema)
       .optional()
       .describe(
         "Steps that should be run by the ProofKit CLI after shadcn CLI is done",
       ),
     minimumProofKitVersion: z
       .string()
       .describe("The minimum version of ProofKit required to use this template")
       .optional(),
     allowedFrameworks: z.array(frameworkSchema).optional(),
     schemaRequired: z
       .boolean()
       .optional()
       .describe(
         "Whether this template requires a database schema to be selected",
       ),
   });
+
+export type TemplateMetadata = z.infer<typeof templateMetadataSchema>;

If you do need a richer “resolved” template type that includes name/docs, it may be cleaner to introduce a separate type for that rather than asserting it on the _meta.ts schema.

Also applies to: 163-188

packages/typegen/src/server/index.ts (1)

32-55: Block directory traversal when serving static assets

pathname is joined directly with WEB_DIR and then read from disk. A request like /../../../../../etc/passwd could escape the intended web root and serve arbitrary files readable by the process. This was already flagged in an earlier review and remains unresolved.

Apply a resolved-path guard and use the resolved path for I/O, for example:

-    const pathname =
-      url.pathname === "/" ? "index.html" : url.pathname.slice(1);
-    const filePath = join(WEB_DIR, pathname);
+    const pathname =
+      url.pathname === "/" ? "index.html" : url.pathname.slice(1);
+    const unsafePath = join(WEB_DIR, pathname);
+    const webRoot = resolve(WEB_DIR);
+    const filePath = resolve(unsafePath);
+
+    // Prevent path traversal outside WEB_DIR
+    if (
+      !filePath.startsWith(webRoot + require("path").sep) &&
+      filePath !== webRoot
+    ) {
+      return c.text("Forbidden", 403);
+    }

Then keep using filePath for existsSync/readFileSync.

packages/typegen/src/server/app.ts (2)

186-202: POST /api/run still doesn’t send a response after running typegen

The /run handler awaits generateTypedClients(config) and then calls await next() but never returns anything. If there’s no downstream middleware to handle this route, clients will see a 404/empty response after a potentially long-running operation. This was previously flagged and remains unresolved.

Consider making the handler self-contained and returning a simple JSON status (optionally including summary counts from generateTypedClients):

-    .post(
-      "/run",
-      zValidator(
-        "json",
-        z.object({
-          config: z.union([z.array(typegenConfigSingle), typegenConfigSingle]),
-        }),
-      ),
-      async (c, next) => {
-        const data = c.req.valid("json");
-        const config = data.config;
-
-        await generateTypedClients(config);
-        await next();
-      },
-    )
+    .post(
+      "/run",
+      zValidator(
+        "json",
+        z.object({
+          config: z.union([z.array(typegenConfigSingle), typegenConfigSingle]),
+        }),
+      ),
+      async (c) => {
+        const { config } = c.req.valid("json");
+        const summary = await generateTypedClients(config);
+
+        return c.json(
+          summary ?? {
+            successCount: 0,
+            errorCount: 0,
+            totalCount: 0,
+          },
+        );
+      },
+    )

Adjust the response shape to match whatever contract you’ve defined for the run endpoint.


298-308: Limit /api/env-names to an allowlist of safe keys

This endpoint currently exposes arbitrary process.env entries based solely on the user-controlled envName query param, which can leak secrets (API keys, tokens, DB credentials, etc.) if the server is reachable.

To tighten this up, restrict lookups to a known allowlist and optionally reject unknown names:

     .get(
       "/env-names",
       zValidator("query", z.object({ envName: z.string() })),
       async (c) => {
         const input = c.req.valid("query");
-
-        const value = process.env[input.envName];
-
-        return c.json({ value });
+        const allowedEnvNames = [
+          "FM_SERVER",
+          "FM_DATABASE",
+          "OTTO_API_KEY",
+          "FM_USERNAME",
+          "FM_PASSWORD",
+        ];
+
+        if (!allowedEnvNames.includes(input.envName)) {
+          return c.json(
+            { error: "Environment variable not allowed" },
+            400 as ContentfulStatusCode,
+          );
+        }
+
+        const value = process.env[input.envName];
+        return c.json({ value });
       },
     )

Align the allowlist with the actual keys you support in defaultEnvNames and related config.

packages/typegen/web/src/components/ui/form.tsx (2)

98-115: Align FormLabel prop types with the rendered Label component

FormLabel is typed as React.ComponentProps<typeof LabelPrimitive.Root> but actually renders the custom Label from "@/components/ui/label". That mismatch can let callers pass props accepted by the Radix primitive but not by your wrapper (or vice versa), and LabelPrimitive is never used at runtime.

Recommend typing against the component you render:

-import { Label } from "@/components/ui/label";
+import { Label } from "@/components/ui/label";
@@
 function FormLabel({
   className,
   ...props
-}: React.ComponentProps<typeof LabelPrimitive.Root>) {
+}: React.ComponentPropsWithoutRef<typeof Label>) {

(or, alternatively, render LabelPrimitive.Root directly if that’s what you intend).


19-28: Guard form contexts and avoid using them before validating

FormFieldContext / FormItemContext default to {} and useFormField calls getFieldState(fieldContext.name, formState) and destructures itemContext.id before any null/undefined checks. Mis-nesting (or forgetting a FormItem/FormField) will quietly pass undefined into getFieldState and produce undefined-* IDs instead of failing fast.

Consider tightening the contexts and guards like this:

-const FormFieldContext = React.createContext<FormFieldContextValue>(
-  {} as FormFieldContextValue,
-);
+const FormFieldContext = React.createContext<FormFieldContextValue | undefined>(
+  undefined,
+);
@@
-const useFormField = () => {
-  const fieldContext = React.useContext(FormFieldContext);
-  const itemContext = React.useContext(FormItemContext);
-  const { getFieldState, formState } = useFormContext();
-
-  const fieldState = getFieldState(fieldContext.name, formState);
-
-  if (!fieldContext) {
-    throw new Error("useFormField should be used within <FormField>");
-  }
-
-  const { id } = itemContext;
-
-  return {
-    id,
-    name: fieldContext.name,
-    formItemId: `${id}-form-item`,
-    formDescriptionId: `${id}-form-item-description`,
-    formMessageId: `${id}-form-item-message`,
-    ...fieldState,
-  };
-};
+const useFormField = () => {
+  const fieldContext = React.useContext(FormFieldContext);
+  const itemContext = React.useContext(FormItemContext);
+  const { getFieldState, formState } = useFormContext();
+
+  if (!fieldContext) {
+    throw new Error("useFormField must be used within <FormField>");
+  }
+  if (!itemContext) {
+    throw new Error("useFormField must be used within <FormItem>");
+  }
+
+  const fieldState = getFieldState(fieldContext.name, formState);
+  const { id } = itemContext;
+
+  return {
+    id,
+    name: fieldContext.name,
+    formItemId: `${id}-form-item`,
+    formDescriptionId: `${id}-form-item-description`,
+    formMessageId: `${id}-form-item-message`,
+    ...fieldState,
+  };
+};
@@
-type FormItemContextValue = {
-  id: string;
-};
-
-const FormItemContext = React.createContext<FormItemContextValue>(
-  {} as FormItemContextValue,
-);
+type FormItemContextValue = {
+  id: string;
+};
+
+const FormItemContext = React.createContext<FormItemContextValue | undefined>(
+  undefined,
+);
@@
 function FormItem({
   className,
   ...props
 }: React.HTMLAttributes<HTMLDivElement>) {
   const id = React.useId();
   const fieldContext = React.useContext(FormFieldContext);
   const { getFieldState, formState } = useFormContext();
-  const fieldState = fieldContext.name
-    ? getFieldState(fieldContext.name, formState)
-    : null;
+  const fieldState =
+    fieldContext?.name != null
+      ? getFieldState(fieldContext.name, formState)
+      : null;
   const error = fieldState?.error;

This makes mis-nesting fail loudly and avoids ever passing an undefined field name into react-hook-form.

Also applies to: 43-63, 66-72, 79-83

🧹 Nitpick comments (24)
packages/typegen/web/src/components/ui/badge.tsx (1)

172-184: Consider simplifying badgeButtonVariants or adding meaningful variants.

The badgeButtonVariants CVA configuration only includes a "default" variant with no conditional styling, which provides no value.

Option 1 (if no variants are needed): Remove the variant system and apply the classes directly:

-const badgeButtonVariants = cva(
-  "cursor-pointer transition-all inline-flex items-center justify-center leading-none size-3.5 [&>svg]:opacity-100! [&>svg]:size-3.5! p-0 rounded-md -me-0.5 opacity-60 hover:opacity-100",
-  {
-    variants: {
-      variant: {
-        default: "",
-      },
-    },
-    defaultVariants: {
-      variant: "default",
-    },
-  },
-);
+const badgeButtonClasses = "cursor-pointer transition-all inline-flex items-center justify-center leading-none size-3.5 [&>svg]:opacity-100! [&>svg]:size-3.5! p-0 rounded-md -me-0.5 opacity-60 hover:opacity-100";

Option 2 (if variants are planned): Add meaningful variants in a future iteration.

packages/typegen/web/src/components/ui/tree.tsx (1)

11-16: Consider improving type safety for the tree property.

The tree property is currently typed as any, which removes type safety. If the exact type from @headless-tree/react is available, consider using it. Otherwise, unknown would be safer than any.

Example refactor:

 interface TreeContextValue<T = any> {
   indent: number;
   currentItem?: ItemInstance<T>;
-  tree?: any;
+  tree?: unknown; // or import the proper type from @headless-tree/react
   toggleIconType?: ToggleIconType;
 }
 interface TreeProps extends React.HTMLAttributes<HTMLDivElement> {
   indent?: number;
-  tree?: any;
+  tree?: unknown; // or import the proper type from @headless-tree/react
   toggleIconType?: ToggleIconType;
 }

Also applies to: 29-33

packages/registry/lib/types.ts (1)

192-204: Derive RegistryIndex from registryIndexSchema (or a shared item schema) to prevent future drift

RegistryIndex manually re-specifies the item shape while registryIndexSchema is annotated as z.ZodType<RegistryIndex>. They’re consistent today, but this repeats the pattern that caused the TemplateMetadata mismatch and could drift over time.

Consider inverting the relationship so the type is inferred from the schema, e.g.:

const registryIndexItemSchema = templateMetadataSchema
  .pick({ title: true, category: true, description: true })
  .extend({ name: z.string() });

export const registryIndexSchema = registryIndexItemSchema.array();
export type RegistryIndex = z.infer<typeof registryIndexSchema>;

This keeps the runtime validation and TypeScript surface locked together with one source of truth.

packages/typegen/web/src/components/ui/input.tsx (4)

51-76: inputGroupVariants typing could be simplified until you add real variants

Right now inputGroupVariants is defined with variants: {} / defaultVariants: {}, so VariantProps<typeof inputGroupVariants> doesn’t add anything to the InputGroup prop type and may confuse readers into thinking there are configurable variants that don’t exist yet. You could simplify the signature to just the native div props until you actually introduce group variants.


78-161: Confirm intent of composing inputVariants into InputWrapper as well as Input

InputWrapper applies inputVariants({ variant }) to the wrapper itself, while the child Input also applies inputVariants({ variant }), and the wrapper then overrides a bunch of child styles via [&_[data-slot=input]]:* utilities. This is a clever way to share size/border/radius tokens between standalone inputs and wrapped/grouped inputs, but it does mean wrapper and child share a lot of base styling with overrides relying on Tailwind’s ordering.

If that’s intentional, it’s worth sanity‑checking a few combinations (standalone <Input>, <InputWrapper><Input /></InputWrapper>, with addons/buttons/datefield) to ensure there are no unexpected double borders or rings in your actual Tailwind build.


127-143: Consider adding ref forwarding to Input / InputAddon

Since these are low‑level primitives, callers will often want to attach refs (for focusing, form libraries, etc.). Right now they’re plain functions, so the ref has to go on a wrapper instead of the underlying DOM node. Converting these to forwardRef wrappers would improve ergonomics without changing the public surface otherwise.


163-163: Export set feels slightly asymmetric vs. defined variants

You export inputVariants and inputAddonVariants but keep inputGroupVariants / inputWrapperVariants internal. If consumers will ever need to style‑compose around groups/wrappers the way they can around raw inputs and addons, consider exporting those variant factories too; otherwise, this asymmetry is fine but might merit a short comment that group/wrapper variants are intentionally internal.

packages/typegen/web/src/components/ui/select.tsx (2)

191-209: Avoid leaking Radix ItemIndicator props onto the wrapper <span>

SelectIndicator is typed as React.ComponentProps<typeof SelectPrimitive.ItemIndicator>, but those props (e.g. forceMount, asChild) are currently spread onto the outer <span>, not the underlying ItemIndicator. That can both leak unknown attributes into the DOM and make Radix-specific props ineffective.

You can keep the same public API but forward props to ItemIndicator instead:

-function SelectIndicator({
-  children,
-  className,
-  ...props
-}: React.ComponentProps<typeof SelectPrimitive.ItemIndicator>) {
-  const { indicatorPosition } = React.useContext(SelectContext);
-
-  return (
-    <span
-      data-slot="select-indicator"
-      className={cn(
-        'absolute flex top-1/2 -translate-y-1/2 items-center justify-center',
-        indicatorPosition === 'left' ? 'start-2' : 'end-2',
-        className,
-      )}
-      {...props}
-    >
-      <SelectPrimitive.ItemIndicator>{children}</SelectPrimitive.ItemIndicator>
-    </span>
-  );
-}
+function SelectIndicator({
+  children,
+  className,
+  ...props
+}: React.ComponentPropsWithoutRef<typeof SelectPrimitive.ItemIndicator>) {
+  const { indicatorPosition } = React.useContext(SelectContext);
+
+  return (
+    <span
+      data-slot="select-indicator"
+      className={cn(
+        'absolute flex top-1/2 -translate-y-1/2 items-center justify-center',
+        indicatorPosition === 'left' ? 'start-2' : 'end-2',
+      )}
+    >
+      <SelectPrimitive.ItemIndicator className={className} {...props}>
+        {children}
+      </SelectPrimitive.ItemIndicator>
+    </span>
+  );
+}

This way all Radix ItemIndicator props behave as expected, and only semantic HTML attributes land on the <span>.


35-221: Consider forwarding refs for all Radix wrappers

All wrappers (SelectGroup, SelectValue, SelectTrigger, SelectContent, SelectItem, etc.) are typed with React.ComponentProps<typeof ...>, which exposes a ref prop in TS, but since they’re plain function components, that ref can’t actually be used. To avoid surprising consumers (and to enable focus/measurement hooks), consider adopting React.forwardRef for these wrappers.

For example, for SelectTrigger:

-export interface SelectTriggerProps
-  extends React.ComponentProps<typeof SelectPrimitive.Trigger>,
-    VariantProps<typeof selectTriggerVariants> {}
-
-function SelectTrigger({ className, children, size, ...props }: SelectTriggerProps) {
-  return (
-    <SelectPrimitive.Trigger
-      data-slot="select-trigger"
-      className={cn(selectTriggerVariants({ size }), className)}
-      {...props}
-    >
-      {children}
-      <SelectPrimitive.Icon asChild>
-        <ChevronDown className="h-4 w-4 opacity-60 -me-0.5" />
-      </SelectPrimitive.Icon>
-    </SelectPrimitive.Trigger>
-  );
-}
+export interface SelectTriggerProps
+  extends React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>,
+    VariantProps<typeof selectTriggerVariants> {}
+
+const SelectTrigger = React.forwardRef<
+  React.ElementRef<typeof SelectPrimitive.Trigger>,
+  SelectTriggerProps
+>(({ className, children, size, ...props }, ref) => (
+  <SelectPrimitive.Trigger
+    ref={ref}
+    data-slot="select-trigger"
+    className={cn(selectTriggerVariants({ size }), className)}
+    {...props}
+  >
+    {children}
+    <SelectPrimitive.Icon asChild>
+      <ChevronDown className="h-4 w-4 opacity-60 -me-0.5" />
+    </SelectPrimitive.Icon>
+  </SelectPrimitive.Trigger>
+));
+
+SelectTrigger.displayName = 'SelectTrigger';

You can mirror this pattern for the other wrappers as needed.

packages/typegen/src/typegen.ts (1)

201-202: Redundant type check—config.type is always "fmdapi" here.

Since the function signature on line 60 constrains config to Extract<..., { type: "fmdapi" }>, the condition config?.type === "fmdapi" is always true. This check is unnecessary but harmless—it may have been added for defensive coding or future flexibility.

If the intent is purely defensive, consider simplifying:

-      webviewerScriptName:
-        config?.type === "fmdapi" ? config.webviewerScriptName : undefined,
+      webviewerScriptName: config.webviewerScriptName,
packages/typegen/web/src/components/ui/textarea.tsx (1)

8-14: Duplicate bg-background class in base styles.

The base styles contain bg-background twice on line 10, which is redundant.

-    w-full bg-background border border-input bg-background text-foreground shadow-xs shadow-black/5 transition-[color,box-shadow] 
+    w-full bg-background border border-input text-foreground shadow-xs shadow-black/5 transition-[color,box-shadow] 
packages/typegen/web/src/components/dialog/default.tsx (3)

28-30: Consider moving FormSchema outside the component.

Defining the schema inside the component causes it to be recreated on every render. While acceptable for a demo, moving it outside improves performance and follows common patterns.

+const FormSchema = z.object({
+  feedback: z.string().min(1, 'Feedback is required').max(200, 'Feedback cannot exceed 200 characters'),
+});
+
 export default function DialogDemo() {
   const [open, setOpen] = useState(false);
   const direction = useDirection();
 
-  const FormSchema = z.object({
-    feedback: z.string().min(1, 'Feedback is required').max(200, 'Feedback cannot exceed 200 characters'),
-  });
-
   const form = useForm<z.infer<typeof FormSchema>>({

38-50: onSubmit ignores form data parameter.

The handler receives validated form data but doesn't use it. If the feedback content should be submitted (e.g., to an API), the parameter should be utilized. If this is intentional for the demo, consider adding an underscore prefix or a comment.

-  function onSubmit() {
+  function onSubmit(_data: z.infer<typeof FormSchema>) {
+    // TODO: Send _data.feedback to API

22-22: Multiple web components use inconsistent zod imports—consider updating to zod/v4 for consistency.

This file imports from 'zod' while core typegen files (typegen.ts, types.ts, server files) import from 'zod/v4'. Other component files like form/default.tsx, ServerEnvField.tsx, and EnvVarField.tsx have the same inconsistency. Consider updating the import to 'zod/v4' to align with the package's convention.

-import { z } from 'zod';
+import { z } from 'zod/v4';
packages/typegen/web/src/components/ui/accordion.tsx (1)

143-156: className applied to both AccordionPrimitive.Content and inner div.

The className prop is applied twice—to the Content wrapper (line 150) and the inner div (line 153). This may cause unintended style duplication or conflicts.

If the intent is to allow custom styling on the content container, apply className only to the inner div:

     <AccordionPrimitive.Content
       data-slot="accordion-content"
-      className={cn(accordionContentVariants({ variant }), className)}
+      className={cn(accordionContentVariants({ variant }))}
       {...rest}
     >
       <div className={cn('pb-5 pt-0', className)}>{children}</div>
packages/typegen/web/src/components/ui/alert.tsx (1)

193-193: AlertIconProps reused for unrelated components.

AlertToolbar and AlertContent use AlertIconProps as their prop type, but these components don't semantically relate to icons and don't use the variant props. Consider defining separate prop types or using a more generic base type.

-function AlertToolbar({ children, className, ...props }: AlertIconProps) {
+function AlertToolbar({ children, className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
-function AlertContent({ className, ...props }: React.HTMLAttributes<HTMLParagraphElement>) {
+function AlertContent({ className, children, ...props }: React.HTMLAttributes<HTMLDivElement>) {

Note: AlertContent already uses HTMLParagraphElement but renders a div—this is a semantic type mismatch.

Also applies to: 230-236, 248-256

packages/typegen/web/src/components/ui/command.tsx (1)

24-35: Consider adding an accessible dialog title instead of an empty hidden DialogTitle.

Right now DialogTitle is rendered with className="hidden" and no content, so the dialog has no accessible name. If you intended a screen-reader-only title, consider rendering text with a visually-hidden class (e.g. sr-only) or wiring an aria-label on DialogContent; otherwise you can safely drop DialogTitle altogether.

packages/typegen/src/types.ts (1)

84-139: Minor schema cleanups for defaults and single-config support.

Two small improvements you might want to consider:

  • path:

    const path = z
      .string()
      .default("schema")
      .optional();

    The .optional() is redundant because .default("schema") already handles undefined. You can simplify to:

    const path = z
      .string()
      .default("schema")
      .meta({ description: "The folder path to output the generated files" });
  • typegenConfig.config:

    export const typegenConfig = z.object({
      config: z.union([z.array(typegenConfigSingle), typegenConfigSingle]),
    });

    This means callers now see config as SingleConfig | SingleConfig[]. Ensure downstream code (e.g. generateTypedClients in typegen.ts and runCodegen in cli.ts) normalizes this to an array or accepts both shapes; otherwise single-config files will parse but still break at runtime.

packages/typegen/web/src/App.tsx (1)

53-69: Minor cleanup: avoid logging noise and guard configs against undefined.

Two small polish tweaks:

  • The console.log("configData from useEffect", configDataResponse); in useEffect is useful during development but will be noisy in production; consider removing or gating behind a debug flag.
  • const configs = form.watch("config"); can produce undefined briefly; using const configs = form.watch("config") ?? []; makes downstream access (e.g. configs[index] when mapping fields) safer.

Neither is blocking but they’ll tighten up runtime behavior a bit.

packages/typegen/web/src/lib/api.ts (1)

1-43: Config client error handling looks solid; consider tightening response typing later.

Both getConfig and saveConfig correctly check res.ok and surface meaningful error messages (including per-path issues for saves), which should make UI failures much easier to debug. Longer-term you might want to type the json() responses using the server contract types (e.g. GetConfigResponse, PostConfigResponse) instead of any, but that’s purely a refinement.

packages/typegen/src/cli.ts (1)

252-274: Silent env parse is intentional but note that syntax errors in .env are now only surfaced later.

parseEnvs now ignores envRes.error entirely, relying on later code to complain about missing env vars instead of failing fast when .env parsing fails. This matches the “should fail silently” comment, but just be aware that invalid .env syntax won’t cause the CLI to exit anymore; only downstream checks will catch it.

packages/typegen/web/src/components/EnvVarDialog.tsx (1)

164-208: Clean up unreachable "none" branch in auth type select

authTypeSelector and the onValueChange handler support a "none" mode that clears envNames.auth, but the <Select> only exposes "apiKey" and "username" options. That makes the "none" branch effectively dead code.

If “no auth” isn’t a real state, you can simplify by dropping "none" from the union and the corresponding else branch. If you do plan to support it, consider adding an explicit “No auth” option so this path is actually reachable.

packages/typegen/web/src/components/ui/switch.tsx (1)

8-19: Make SwitchContext guard effective so misuse is caught in development

SwitchContext is created with a non-null default { permanent: false }, so useSwitchContext’s if (!context) check will never fire. That means Switch/SwitchIndicator can be rendered outside SwitchWrapper without any warning, even though the error message implies otherwise.

You can make the guard actually work and clarify the message like this:

-const SwitchContext = React.createContext<{ permanent: boolean }>({
-  permanent: false,
-});
+const SwitchContext = React.createContext<{ permanent: boolean } | undefined>(
+  undefined,
+);

const useSwitchContext = () => {
  const context = React.useContext(SwitchContext);
  if (!context) {
-    throw new Error("SwitchIndicator must be used within a Switch component");
+    throw new Error(
+      "Switch and SwitchIndicator must be used within a SwitchWrapper",
+    );
  }
  return context;
};

This keeps the permanent-flag plumbing intact while ensuring misuse is surfaced early.

Also applies to: 130-147, 149-189

packages/typegen/web/src/components/ui/form.tsx (1)

117-134: Tighten ARIA wiring and DOM typings for description/message

Two small refinements here:

  1. FormDescription / FormMessage are typed as HTMLParagraphElement props but render <div> elements, which can confuse refs and stricter typing.
  2. When error is present, FormDescription returns null, but FormControl still includes formDescriptionId in aria-describedby, so assistive tech can reference a non-existent node.

You can make both more consistent like this:

 function FormControl({ ...props }: React.ComponentProps<typeof Slot>) {
   const { error, formItemId, formDescriptionId, formMessageId } =
     useFormField();
@@
-    <Slot
+    <Slot
       data-slot="form-control"
       id={formItemId}
-      aria-describedby={
-        !error
-          ? `${formDescriptionId}`
-          : `${formDescriptionId} ${formMessageId}`
-      }
+      aria-describedby={error ? formMessageId : formDescriptionId}
       aria-invalid={!!error}
       {...props}
     />
@@
 function FormDescription({
   className,
   ...props
-}: React.HTMLAttributes<HTMLParagraphElement>) {
+}: React.HTMLAttributes<HTMLDivElement>) {
@@
 function FormMessage({
   className,
   children,
   ...props
-}: React.HTMLAttributes<HTMLParagraphElement>) {
+}: React.HTMLAttributes<HTMLDivElement>) {

That keeps aria-describedby pointing only at elements that are actually rendered and aligns the TS types with the actual DOM nodes.

Also applies to: 136-178

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5d7353a and 940dd98.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (88)
  • apps/demo/package.json (2 hunks)
  • apps/docs/package.json (2 hunks)
  • package.json (1 hunks)
  • packages/better-auth/package.json (1 hunks)
  • packages/cli/package.json (4 hunks)
  • packages/cli/src/utils/formatting.ts (2 hunks)
  • packages/fmdapi/package.json (2 hunks)
  • packages/fmodata/package.json (1 hunks)
  • packages/fmodata/src/client/error-parser.ts (1 hunks)
  • packages/fmodata/tests/tsconfig.build.json (1 hunks)
  • packages/registry/lib/types.ts (2 hunks)
  • packages/registry/lib/validator.ts (1 hunks)
  • packages/registry/package.json (2 hunks)
  • packages/registry/scripts/validate.ts (1 hunks)
  • packages/registry/tsconfig.json (1 hunks)
  • packages/registry/tsdown.config.ts (1 hunks)
  • packages/typegen/package.json (3 hunks)
  • packages/typegen/proofkit-typegen.config.jsonc (1 hunks)
  • packages/typegen/scripts/build-copy.js (1 hunks)
  • packages/typegen/scripts/build.ts (1 hunks)
  • packages/typegen/src/buildSchema.ts (2 hunks)
  • packages/typegen/src/cli.ts (5 hunks)
  • packages/typegen/src/formatting.ts (2 hunks)
  • packages/typegen/src/server/api.ts (1 hunks)
  • packages/typegen/src/server/app.ts (1 hunks)
  • packages/typegen/src/server/contract.ts (1 hunks)
  • packages/typegen/src/server/createDataApiClient.ts (1 hunks)
  • packages/typegen/src/server/index.ts (1 hunks)
  • packages/typegen/src/typegen.ts (2 hunks)
  • packages/typegen/src/types.ts (1 hunks)
  • packages/typegen/vite.config.ts (1 hunks)
  • packages/typegen/web/components.json (1 hunks)
  • packages/typegen/web/index.html (1 hunks)
  • packages/typegen/web/package.json (1 hunks)
  • packages/typegen/web/src/App.tsx (1 hunks)
  • packages/typegen/web/src/components/ConfigEditor.tsx (1 hunks)
  • packages/typegen/web/src/components/ConfigList.css (1 hunks)
  • packages/typegen/web/src/components/ConfigList.tsx (1 hunks)
  • packages/typegen/web/src/components/ConfigSummary.tsx (1 hunks)
  • packages/typegen/web/src/components/EnvVarDialog.tsx (1 hunks)
  • packages/typegen/web/src/components/EnvVarField.tsx (1 hunks)
  • packages/typegen/web/src/components/InfoTooltip.tsx (1 hunks)
  • packages/typegen/web/src/components/LayoutEditor.tsx (1 hunks)
  • packages/typegen/web/src/components/LayoutItemEditor.tsx (1 hunks)
  • packages/typegen/web/src/components/LayoutSelector.tsx (1 hunks)
  • packages/typegen/web/src/components/ServerEnvField.tsx (1 hunks)
  • packages/typegen/web/src/components/badge/circle.tsx (1 hunks)
  • packages/typegen/web/src/components/button/loading.tsx (1 hunks)
  • packages/typegen/web/src/components/combobox/default.tsx (1 hunks)
  • packages/typegen/web/src/components/dialog/default.tsx (1 hunks)
  • packages/typegen/web/src/components/form/default.tsx (1 hunks)
  • packages/typegen/web/src/components/input/addon.tsx (1 hunks)
  • packages/typegen/web/src/components/select/default.tsx (1 hunks)
  • packages/typegen/web/src/components/switch/button.tsx (1 hunks)
  • packages/typegen/web/src/components/switch/default.tsx (1 hunks)
  • packages/typegen/web/src/components/tree/default.tsx (1 hunks)
  • packages/typegen/web/src/components/ui/accordion.tsx (1 hunks)
  • packages/typegen/web/src/components/ui/alert.tsx (1 hunks)
  • packages/typegen/web/src/components/ui/badge.tsx (1 hunks)
  • packages/typegen/web/src/components/ui/button.tsx (1 hunks)
  • packages/typegen/web/src/components/ui/command.tsx (1 hunks)
  • packages/typegen/web/src/components/ui/dialog.tsx (1 hunks)
  • packages/typegen/web/src/components/ui/form.tsx (1 hunks)
  • packages/typegen/web/src/components/ui/input.tsx (1 hunks)
  • packages/typegen/web/src/components/ui/label.tsx (1 hunks)
  • packages/typegen/web/src/components/ui/popover.tsx (1 hunks)
  • packages/typegen/web/src/components/ui/select.tsx (1 hunks)
  • packages/typegen/web/src/components/ui/switch-field.tsx (1 hunks)
  • packages/typegen/web/src/components/ui/switch.tsx (1 hunks)
  • packages/typegen/web/src/components/ui/textarea.tsx (1 hunks)
  • packages/typegen/web/src/components/ui/tooltip.tsx (1 hunks)
  • packages/typegen/web/src/components/ui/tree.tsx (1 hunks)
  • packages/typegen/web/src/components/useEnvVarIndicator.ts (1 hunks)
  • packages/typegen/web/src/hooks/useConfig.ts (1 hunks)
  • packages/typegen/web/src/hooks/useTestConnection.ts (1 hunks)
  • packages/typegen/web/src/index.css (1 hunks)
  • packages/typegen/web/src/lib/api.ts (1 hunks)
  • packages/typegen/web/src/lib/config-utils.ts (1 hunks)
  • packages/typegen/web/src/lib/envValues.ts (1 hunks)
  • packages/typegen/web/src/lib/schema.ts (1 hunks)
  • packages/typegen/web/src/lib/utils.ts (1 hunks)
  • packages/typegen/web/src/main.tsx (1 hunks)
  • packages/typegen/web/tsconfig.app.json (1 hunks)
  • packages/typegen/web/tsconfig.json (1 hunks)
  • packages/typegen/web/tsconfig.node.json (1 hunks)
  • packages/typegen/web/vite.config.ts (1 hunks)
  • packages/webviewer/package.json (1 hunks)
  • pnpm-workspace.yaml (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (51)
  • packages/typegen/scripts/build.ts
  • packages/typegen/web/package.json
  • packages/typegen/web/tsconfig.app.json
  • packages/typegen/web/src/main.tsx
  • packages/typegen/src/formatting.ts
  • packages/typegen/web/src/components/switch/button.tsx
  • packages/typegen/web/src/components/ui/button.tsx
  • packages/typegen/web/tsconfig.node.json
  • packages/typegen/web/src/components/ServerEnvField.tsx
  • packages/typegen/web/src/components/badge/circle.tsx
  • packages/typegen/web/src/components/LayoutEditor.tsx
  • packages/typegen/web/src/lib/envValues.ts
  • apps/demo/package.json
  • packages/typegen/web/components.json
  • packages/typegen/web/src/components/combobox/default.tsx
  • packages/fmodata/src/client/error-parser.ts
  • pnpm-workspace.yaml
  • packages/fmodata/package.json
  • packages/typegen/web/src/components/ConfigSummary.tsx
  • packages/typegen/web/src/components/EnvVarField.tsx
  • packages/typegen/web/src/components/input/addon.tsx
  • packages/registry/tsconfig.json
  • packages/fmodata/tests/tsconfig.build.json
  • packages/typegen/web/src/hooks/useConfig.ts
  • packages/typegen/web/src/components/ConfigList.tsx
  • packages/typegen/vite.config.ts
  • packages/typegen/web/src/components/ui/label.tsx
  • packages/typegen/web/src/components/ui/popover.tsx
  • packages/webviewer/package.json
  • packages/typegen/web/src/index.css
  • packages/typegen/web/vite.config.ts
  • packages/typegen/web/src/lib/utils.ts
  • packages/typegen/web/src/components/InfoTooltip.tsx
  • package.json
  • packages/typegen/scripts/build-copy.js
  • packages/typegen/web/src/components/ConfigList.css
  • packages/registry/tsdown.config.ts
  • packages/cli/src/utils/formatting.ts
  • packages/typegen/web/index.html
  • packages/typegen/web/src/components/form/default.tsx
  • packages/typegen/proofkit-typegen.config.jsonc
  • packages/typegen/web/src/components/ui/tooltip.tsx
  • packages/typegen/src/buildSchema.ts
  • packages/typegen/package.json
  • packages/typegen/web/src/components/useEnvVarIndicator.ts
  • packages/registry/scripts/validate.ts
  • packages/typegen/src/server/api.ts
  • packages/typegen/web/src/hooks/useTestConnection.ts
  • packages/typegen/web/src/components/select/default.tsx
  • packages/typegen/web/src/components/ui/dialog.tsx
  • packages/typegen/web/src/lib/config-utils.ts
🧰 Additional context used
🧬 Code graph analysis (17)
packages/typegen/web/src/components/switch/default.tsx (3)
packages/typegen/web/src/components/switch/button.tsx (1)
  • SwitchDemo (4-20)
packages/typegen/web/src/components/ui/switch.tsx (1)
  • Switch (191-191)
packages/typegen/web/src/components/ui/label.tsx (1)
  • Label (31-31)
packages/typegen/src/cli.ts (1)
packages/typegen/src/server/index.ts (1)
  • startServer (23-96)
packages/typegen/web/src/components/button/loading.tsx (1)
packages/typegen/web/src/components/ui/button.tsx (1)
  • Button (423-423)
packages/typegen/web/src/components/ui/textarea.tsx (1)
packages/typegen/web/src/lib/utils.ts (1)
  • cn (10-12)
packages/typegen/src/server/app.ts (5)
packages/typegen/src/server/api.ts (1)
  • ApiContext (8-11)
packages/fmdapi/src/client-types.ts (1)
  • LayoutOrFolder (262-262)
packages/typegen/src/types.ts (2)
  • typegenConfig (137-139)
  • typegenConfigSingle (130-135)
packages/typegen/src/typegen.ts (1)
  • generateTypedClients (28-57)
packages/typegen/src/server/createDataApiClient.ts (2)
  • createDataApiClient (192-251)
  • createClientFromConfig (41-184)
packages/typegen/src/server/index.ts (1)
packages/typegen/src/server/app.ts (1)
  • createApiApp (58-428)
packages/typegen/web/src/components/ui/accordion.tsx (1)
packages/typegen/web/src/lib/utils.ts (1)
  • cn (10-12)
packages/typegen/src/server/createDataApiClient.ts (2)
packages/typegen/src/types.ts (2)
  • typegenConfigSingle (130-135)
  • typegenConfig (137-139)
packages/typegen/src/server/api.ts (1)
  • ApiContext (8-11)
packages/typegen/web/src/components/ui/select.tsx (1)
packages/typegen/web/src/lib/utils.ts (1)
  • cn (10-12)
packages/typegen/src/server/contract.ts (3)
packages/typegen/web/src/lib/config-utils.ts (2)
  • SingleConfig (4-4)
  • ConfigsArray (7-7)
packages/typegen/src/types.ts (1)
  • typegenConfigSingle (130-135)
packages/typegen/web/src/lib/schema.ts (1)
  • typegenConfigSingle (3-3)
packages/typegen/web/src/components/ui/tree.tsx (1)
packages/typegen/web/src/lib/utils.ts (1)
  • cn (10-12)
packages/typegen/web/src/components/ui/alert.tsx (1)
packages/typegen/web/src/lib/utils.ts (1)
  • cn (10-12)
packages/typegen/web/src/components/ui/switch.tsx (1)
packages/typegen/web/src/lib/utils.ts (1)
  • cn (10-12)
packages/typegen/web/src/components/ui/form.tsx (2)
packages/typegen/web/src/lib/utils.ts (1)
  • cn (10-12)
packages/typegen/web/src/components/ui/label.tsx (1)
  • Label (31-31)
packages/typegen/src/typegen.ts (2)
packages/typegen/src/types.ts (1)
  • typegenConfigSingle (130-135)
packages/typegen/web/src/lib/schema.ts (1)
  • typegenConfigSingle (3-3)
packages/typegen/web/src/components/ui/command.tsx (2)
packages/typegen/web/src/lib/utils.ts (1)
  • cn (10-12)
packages/typegen/web/src/components/ui/dialog.tsx (3)
  • Dialog (128-128)
  • DialogContent (131-131)
  • DialogTitle (137-137)
packages/typegen/src/types.ts (2)
apps/docs/scripts/bundle-registry-templates.js (1)
  • path (4-4)
packages/typegen/web/src/lib/schema.ts (1)
  • typegenConfigSingle (3-3)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (10)
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: build
  • GitHub Check: build
  • GitHub Check: build
🔇 Additional comments (40)
packages/typegen/web/tsconfig.json (3)

26-27: Include and references configured appropriately.

The inclusion of both src and ../src enables the path aliases to resolve correctly, and the project reference to tsconfig.node.json follows standard conventions for monorepo tooling configuration.


9-9: No concerns. The allowImportingTsExtensions setting is correctly configured for this Vite setup. With noEmit: true in tsconfig.json, TypeScript is used only for type checking, and Vite handles all transpilation. The final production bundle contains .js files, not .ts files, so direct .ts imports in the final output are not a concern.


2-24: Path aliases and React JSX configuration are correct.

The path mappings all point to valid files and directories. The web subproject uses React 19.1.1, which fully supports the react-jsx JSX transform (introduced in React 17), so the compiler option is appropriately configured. The overall TypeScript configuration for the web UI is sound.

packages/typegen/web/src/components/button/loading.tsx (1)

7-28: Component behavior and cleanup look good

The client component setup, state toggling logic, and interval cleanup in useEffect are all idiomatic and safe. The loading/disabled rendering on the Button also reads clearly.

packages/typegen/web/src/components/ui/badge.tsx (2)

22-170: LGTM! Comprehensive variant configuration.

The badgeVariants CVA configuration is well-structured with comprehensive variant options, compound variants for different appearance modes, and good use of CSS custom properties with fallbacks.


229-240: LGTM! Clean implementation.

The BadgeDot component is simple, well-implemented, and correctly uses currentColor to inherit the parent badge's color.

apps/docs/package.json (1)

43-43: Verify Zod v4 migration in source code; Vitest v4 compatibility in tests.

The dependency updates include Zod v4 (major version bump with breaking changes) and Vitest v4. Per the library documentation, Zod v4 introduces:

  • Unified error parameter for error customization (replacing fragmented error handling)
  • Changed default value behavior (short-circuits on undefined)
  • Object schema improvements affecting optional field defaults
  • Number validation changes (rejects infinite values, only safe integers)

Ensure all source code using Zod has been migrated to v4 API patterns, and that tests pass with Vitest v4.

Also applies to: 58-59

packages/better-auth/package.json (1)

61-61: Verify Zod v4 usage in better-auth source code.

This is a core dependency that other packages rely on. Ensure that zod v4 API patterns have been applied throughout the authentication module.

Also applies to: 69-70

packages/fmdapi/package.json (1)

61-61: Verify Zod v4 migration in fmdapi Data API client.

As a public dependency (exported as ., ./utils/*, ./tokenStore/*, ./adapters/*), ensure all schema validations and error handling use Zod v4 API.

Also applies to: 75-76

packages/registry/package.json (1)

55-55: Zod v4.2.0 in registry requires migration verification.

Registry's validation script (scripts/validate.ts) must use Zod v4 API. Additionally, any schema definitions in the registry lib that are exported to downstream packages must be compatible with Zod v4.

packages/cli/package.json (1)

70-70: Multiple major version bumps in CLI — verify end-to-end functionality.

Several significant dependency updates beyond the standard Zod v4/TypeScript/Vitest bumps:

  • es-toolkit: 1.15.11.38.0 — verify API compatibility in CLI utilities
  • @vitest/coverage-v8: 1.4.02.1.8 (major version) — confirm test coverage works
  • @tanstack/react-query: 5.49.25.76.1 — minor bump but used in templates
  • Vitest: 3.2.44.0.7 — major version jump

Since the CLI generates project templates and runs code generation, incompatibilities could break user project scaffolding. Recommend thorough CLI functional testing, especially around:

  • Template generation and TypeScript codegen
  • Test command execution
  • Plugin initialization

Also applies to: 99-99, 111-111, 127-128

packages/typegen/web/src/components/ui/tree.tsx (5)

35-53: LGTM!

The Tree component implementation is well-structured with proper defensive checks, prop merging, and CSS variable setup.


61-100: LGTM!

The TreeItem component demonstrates good practices with defensive method checks, proper prop merging, accessibility attributes, and flexible rendering via the Slot pattern.


106-137: LGTM!

TreeItemLabel has good fallback logic, proper warning for missing items, and clear icon rendering based on toggle type and folder state.


139-158: LGTM!

TreeDragLine component properly validates the tree context and getDragLineStyle method availability with appropriate warnings.


7-7: The import is correct. radix-ui (v1.4.3) is a legitimate monorepo entrypoint package that includes Radix UI Slot. The web search confirms that Slot can be imported from either @radix-ui/react-slot or the radix-ui entrypoint, and the code correctly uses SlotPrimitive.Slot syntax.

packages/typegen/web/src/components/tree/default.tsx (3)

1-11: LGTM!

Clean imports and straightforward interface definition for the tree data structure.


13-74: LGTM!

The static CRM data structure is well-organized and provides a comprehensive example of a hierarchical tree.


92-105: LGTM!

The rendering logic is clean and properly uses keys for the list of tree items.

packages/registry/lib/validator.ts (1)

5-5: Explicit .js extension on the schema import looks correct for ESM builds

Switching the import to ./types.js matches the emitted filename for TS→ESM output and aligns with the rest of this module’s ESM usage (import.meta.url, jiti). No further changes needed here.

packages/typegen/web/src/components/ui/input.tsx (1)

5-49: Base input and addon variants look consistent and robust

The sizing, disabled/read‑only, file input, and aria-invalid handling in inputVariants and inputAddonVariants are coherent and should give you good standalone ergonomics for both raw inputs and add‑ons. I don’t see functional issues here.

packages/typegen/web/src/components/ui/select.tsx (5)

3-8: Verify Radix Select import alias

import { Select as SelectPrimitive } from 'radix-ui'; assumes a local barrel or alias that re-exports the Radix Select namespace. If you’re using the upstream package directly, this would typically look like:

-import { Select as SelectPrimitive } from 'radix-ui';
+import * as SelectPrimitive from '@radix-ui/react-select';

Please confirm the module/alias setup so this doesn’t turn into a “module not found” or incorrect-type issue at runtime.


10-33: Context-driven root wrapper looks solid

The SelectContext + Select root wrapper cleanly centralize indicatorPosition, indicatorVisibility, and indicator while keeping all descendants in a single provider scope. Default values are sane and the root still just delegates to SelectPrimitive.Root via props.


43-84: Trigger variants and API look good

The selectTriggerVariants setup and SelectTrigger wrapper give a compact, size-based API with good focus/invalid state coverage, and the SelectTriggerProps typing matches the intended variant + Radix props surface. Nothing blocking here.


86-146: Content/portal composition is well-structured

SelectContent correctly wraps in a portal, applies animated state/side classes, and reuses the scroll buttons with consistent slot data. The position === 'popper' branch aligning viewport size with trigger height/width is a nice touch.


158-188: Double-check outline-hidden utility

In SelectItem’s class list, outline-hidden is not a standard Tailwind utility out of the box (vs outline-none). If you don’t have a custom plugin for it, it will be a no-op:

className={cn(
  'relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 text-sm outline-hidden text-foreground ...',
  ...
)}

If this isn’t intentional, consider swapping it for outline-none or removing it.

packages/typegen/src/typegen.ts (2)

44-56: LGTM! Clean array normalization and type filtering.

The config normalization and iteration logic is sound. Processing only "fmdapi" type with a warning for unsupported types provides good forward compatibility for when other config types are implemented.


60-60: Good type narrowing using Extract.

The type constraint ensures generateTypedClientsSingle only accepts "fmdapi" configurations, providing compile-time safety that aligns with the runtime check in generateTypedClients.

packages/typegen/web/src/components/ui/textarea.tsx (1)

29-35: LGTM! Clean component implementation.

The Textarea component follows the established pattern with proper prop forwarding, variant support, and className composition via cn. The data-slot attribute enables slot-based styling.

packages/typegen/web/src/components/ui/accordion.tsx (2)

86-91: Type mismatch: indicator prop excludes 'none' but context type includes it.

The Accordion component's indicator prop (line 89) only allows 'arrow' | 'plus', but AccordionContextType (line 77) includes 'none'. This means 'none' can only come from the default context value, which may be intentional for internal use. If 'none' should be externally settable, update the prop type.

Is the 'none' indicator variant intended to be user-facing? If so:

     VariantProps<typeof accordionRootVariants> & {
-      indicator?: 'arrow' | 'plus';
+      indicator?: 'arrow' | 'plus' | 'none';
     },

122-141: LGTM! Well-structured trigger with conditional indicator rendering.

The trigger correctly consumes context for variant and indicator, applies appropriate styles, and conditionally renders the icon based on the indicator type. The CSS transforms for open/closed states are properly handled.

packages/typegen/web/src/components/ui/alert.tsx (1)

195-216: LGTM! Well-implemented Alert component with close functionality.

The Alert component correctly composes variants, handles the close button with proper accessibility (aria-label), and follows the slot-based pattern consistently. The role="alert" ensures screen reader accessibility.

packages/typegen/web/src/lib/schema.ts (1)

1-3: Scoped re-export for browser usage looks good.

Re-exporting only typegenConfigSingle as configSchema from the types entrypoint cleanly avoids bundling the node-only generator into the web app. Nice separation of concerns.

packages/typegen/web/src/components/switch/default.tsx (1)

1-11: LGTM on the Switch demo component.

Simple, accessible wiring between Switch and Label with id/htmlFor; nothing concerning here.

packages/typegen/web/src/components/ui/switch-field.tsx (1)

1-71: Well-structured, accessible switch field.

Nice composition of Switch, Label, InfoTooltip, and description with aria-describedby; props cover the common needs cleanly and the generated id fallback looks solid.

packages/typegen/src/cli.ts (2)

138-173: Generate command env handling and deprecation behavior look consistent.

The generate command now always calls parseEnvs but logs a deprecation notice when --skip-env-check is used, and parseEnvs itself fails silently if it can’t load env files. That’s a reasonable balance between backward compatibility and a smoother DX for misconfigured environments.


186-248: ui command wiring and shutdown handling are solid.

The ui command correctly:

  • Resolves the config path (with fallback),
  • Loads env vars before starting,
  • Validates the optional --port,
  • Starts the server via startServer,
  • Optionally opens the browser, and
  • Handles SIGINT/SIGTERM for graceful shutdown.

This looks like a clean integration of the new UI server into the CLI.

packages/typegen/web/src/components/ConfigEditor.tsx (1)

147-183: Verify generateClient semantics between top-level switch and per-layout select

The top-level “Generate” switch maps checked to “Types Only” and unchecked to “Full Client”, while LayoutItemEditor maps generateClient === true to “Generate Client” and false to “Types Only”. Please confirm that the stored boolean meaning is consistent across config-level and layout-level (e.g., true always means “full client” or always “types only”) and adjust labels/mapping as needed to avoid inverted behavior.

packages/typegen/web/src/components/LayoutItemEditor.tsx (1)

30-205: LayoutItemEditor form wiring and tri-state config look solid

The per-layout editor correctly wires into the config array, keeps header labels in sync via watch, and handles valueLists, strictNumbers, and generateClient (default/true/false) in a predictable way. I don’t see correctness or UX blockers here.

packages/typegen/src/server/contract.ts (1)

1-75: API contract schemas and inferred types look coherent

The Zod contracts here (single vs array config unions, config/run/layout/env-names responses, and the re-exported SingleConfig/ConfigsArray aliases) are internally consistent and match the shapes implied by the existing typegenConfigSingle schema. I don’t see any blocking issues in this file.

Comment on lines 27 to 82
const envNames = z
.object({
server: z.string(),
db: z.string(),
server: z
.string()
.optional()
.transform((val) => (val === "" ? undefined : val)),
db: z
.string()
.optional()
.transform((val) => (val === "" ? undefined : val)),
auth: z.union([
z
.object({
apiKey: z.string(),
apiKey: z
.string()
.optional()
.transform((val) => (val === "" ? undefined : val)),
})
.partial(),
.optional()
.transform((val) => {
if (val && Object.values(val).every((v) => v === undefined)) {
return undefined;
}
return val ?? undefined;
}),
z
.object({
username: z.string(),
password: z.string(),
username: z
.string()
.optional()
.transform((val) => (val === "" ? undefined : val)),
password: z
.string()
.optional()
.transform((val) => (val === "" ? undefined : val)),
})
.partial(),
.optional()
.transform((val) => {
if (val && Object.values(val).every((v) => v === undefined)) {
return undefined;
}
return val ?? undefined;
}),
]),
})
.partial()
.optional()
.transform((val) => {
if (val && Object.values(val).every((v) => v === undefined)) {
return undefined;
}
return val ?? undefined;
})
.meta({
description:
"If you're using other environment variables than the default, custom the NAMES of them here for the typegen to lookup their values when it runs.",
});
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

envNames.auth is effectively required now; this may break existing configs.

envNames’s shape makes auth a required key:

const envNames = z
  .object({
    server: ...,
    db: ...,
    auth: z.union([...]), // no .optional() here
  })
  .optional()
  .transform(...);

Even though each auth variant is optional().transform(...), the property itself is required by the object schema, so configs like:

{
  "envNames": {
    "server": "FM_HOST",
    "db": "FM_DB"
    // no "auth"
  }
}

will now fail validation. That’s a behavior change compared to the more permissive interpretation where auth could be omitted entirely.

If you want auth to remain optional while still collapsing empty auth objects to undefined, consider:

auth: z
  .union([
    // existing variants
  ])
  .optional()

or wrapping the existing union in an outer .optional() so both the property and the inner objects can be omitted/emptied without errors.

🤖 Prompt for AI Agents
In packages/typegen/src/types.ts around lines 27 to 82, the object schema
defines auth as a required key because the z.union(...) is not marked optional;
make the auth property optional so configs that omit auth still validate. Change
the schema so auth is either the existing union wrapped with .optional() (i.e.
z.union([...]).optional()) or wrap the entire union in an outer .optional(), and
keep the current per-variant .optional().transform(...) logic so empty objects
still collapse to undefined; update any downstream typing if necessary.

Comment on lines +70 to +77
// Run typegen mutation
const runTypegenMutation = useMutation({
mutationFn: async () => {
await client.api.run.$post({
json: { config: configs },
});
},
});
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cat -n packages/typegen/web/src/App.tsx | head -100 | tail -40

Repository: proofgeist/proofkit

Length of output: 1514


🏁 Script executed:

# Find the saveConfig implementation to compare error handling
rg "saveConfig" packages/typegen/web/src/ -B 3 -A 10

Repository: proofgeist/proofkit

Length of output: 2835


🏁 Script executed:

# Check how client API calls are structured elsewhere
rg "client\.api\." packages/typegen/web/src/App.tsx -B 2 -A 5

Repository: proofgeist/proofkit

Length of output: 229


🏁 Script executed:

# Search for Response handling patterns in the codebase
rg "\.ok\b" packages/typegen/web/src/ -B 2 -A 2

Repository: proofgeist/proofkit

Length of output: 3540


🏁 Script executed:

# Check the client configuration to understand the API client type
fd "client\." packages/typegen/web/src -o | head -20
rg "import.*client" packages/typegen/web/src/App.tsx

Repository: proofgeist/proofkit

Length of output: 232


🏁 Script executed:

# Check how runTypegenMutation errors are handled in the UI
rg "runTypegenMutation" packages/typegen/web/src/App.tsx -A 5 -B 2

Repository: proofgeist/proofkit

Length of output: 1320


🏁 Script executed:

# See the full handleRunTypegen function
sed -n '92,110p' packages/typegen/web/src/App.tsx

Repository: proofgeist/proofkit

Length of output: 570


🏁 Script executed:

# Check if there's any error display for runTypegenMutation in the component
rg "runTypegenMutation\.(error|isError)" packages/typegen/web/src/App.tsx

Repository: proofgeist/proofkit

Length of output: 45


Missing error check on runTypegenMutation allows silent failure if the server returns an error status.

client.api.run.$post returns a Response object, but the mutation doesn't check res.ok, treating all responses (including 4xx/5xx errors) as success:

const runTypegenMutation = useMutation({
  mutationFn: async () => {
    await client.api.run.$post({
      json: { config: configs },
    });
  },
});

This is inconsistent with other API calls in the codebase—saveConfig, getConfig, and useTestConnection all check res.ok and throw errors on failure. Without this check, users receive no feedback if the typegen run fails on the server.

Add an error check matching the saveConfig pattern:

 const runTypegenMutation = useMutation({
-  mutationFn: async () => {
-    await client.api.run.$post({
-      json: { config: configs },
-    });
-  },
+  mutationFn: async () => {
+    const res = await client.api.run.$post({
+      json: { config: configs },
+    });
+
+    if (!res.ok) {
+      const errorData = (await res.json().catch(() => ({}))) as {
+        error?: string;
+      };
+      throw new Error(
+        errorData.error ||
+          `Failed to run typegen: ${res.status} ${res.statusText}`,
+      );
+    }
+
+    return res.json().catch(() => null);
+  },
 });
🤖 Prompt for AI Agents
In packages/typegen/web/src/App.tsx around lines 70 to 77, the mutation calls
client.api.run.$post but does not inspect the Response, allowing 4xx/5xx
responses to be treated as success; change the mutationFn to capture the
Response, check res.ok, and if not ok throw an Error that includes the status
and response body (similar to saveConfig), otherwise return the parsed JSON on
success so callers get consistent error handling and feedback.

Comment on lines +71 to +75
{
variant: 'mono',
appearance: 'solid',
className: 'bg-zinc-950 text-white dark:bg-zinc-300 dark:text-black *:data-slot-[alert=close]:text-white',
},
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Likely typo in CSS selector for mono variant.

The selector *:data-slot-[alert=close] on line 74 appears malformed. Based on the pattern used elsewhere (e.g., [&_[data-slot=alert-close]]), this should likely be:

-      className: 'bg-zinc-950 text-white dark:bg-zinc-300 dark:text-black *:data-slot-[alert=close]:text-white',
+      className: 'bg-zinc-950 text-white dark:bg-zinc-300 dark:text-black [&_[data-slot=alert-close]]:text-white',
📝 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.

Suggested change
{
variant: 'mono',
appearance: 'solid',
className: 'bg-zinc-950 text-white dark:bg-zinc-300 dark:text-black *:data-slot-[alert=close]:text-white',
},
{
variant: 'mono',
appearance: 'solid',
className: 'bg-zinc-950 text-white dark:bg-zinc-300 dark:text-black [&_[data-slot=alert-close]]:text-white',
},
🤖 Prompt for AI Agents
In packages/typegen/web/src/components/ui/alert.tsx around lines 71 to 75, the
CSS selector `*:data-slot-[alert=close]` is malformed; replace it with the
correct pattern used elsewhere (e.g. `[&_[data-slot=alert-close]]`) so the mono
variant targets the alert close slot properly—update the className string to use
`[&_[data-slot=alert-close]]:text-white` (or the equivalent selector used
project-wide) preserving other classes like bg-zinc-950 text-white
dark:bg-zinc-300 dark:text-black.

extends React.HTMLAttributes<HTMLDivElement>,
VariantProps<typeof badgeVariants> {
asChild?: boolean;
dotClassName?: string;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Remove unused dotClassName prop.

The dotClassName prop is declared in BadgeProps but is never used in the Badge component implementation (lines 186-209).

If this prop is not needed, apply this diff:

 export interface BadgeProps
   extends React.HTMLAttributes<HTMLDivElement>,
     VariantProps<typeof badgeVariants> {
   asChild?: boolean;
-  dotClassName?: string;
   disabled?: boolean;
 }

If it's intended for future use with BadgeDot, please add the implementation or document its intended usage.

📝 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.

Suggested change
dotClassName?: string;
export interface BadgeProps
extends React.HTMLAttributes<HTMLDivElement>,
VariantProps<typeof badgeVariants> {
asChild?: boolean;
disabled?: boolean;
}
🤖 Prompt for AI Agents
In packages/typegen/web/src/components/ui/badge.tsx around line 10, the
BadgeProps type declares an unused prop dotClassName; remove dotClassName from
the BadgeProps declaration (and any exported types/interfaces) and any related
references so props match the component implementation (lines ~186-209), or if
the prop is intended to style the internal BadgeDot, wire it through: add a
dotClassName prop to the Badge component signature and pass it into the BadgeDot
element as its className (or merge it with existing classes) and update the type
and any usages accordingly.

Comment on lines +14 to +18
export interface BadgeButtonProps
extends React.ButtonHTMLAttributes<HTMLDivElement>,
VariantProps<typeof badgeButtonVariants> {
asChild?: boolean;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Fix the generic type parameter for ButtonHTMLAttributes.

BadgeButtonProps extends ButtonHTMLAttributes<HTMLDivElement> but should extend ButtonHTMLAttributes<HTMLButtonElement> since it represents button attributes.

Apply this diff:

 export interface BadgeButtonProps
-  extends React.ButtonHTMLAttributes<HTMLDivElement>,
+  extends React.ButtonHTMLAttributes<HTMLButtonElement>,
     VariantProps<typeof badgeButtonVariants> {
   asChild?: boolean;
 }
📝 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.

Suggested change
export interface BadgeButtonProps
extends React.ButtonHTMLAttributes<HTMLDivElement>,
VariantProps<typeof badgeButtonVariants> {
asChild?: boolean;
}
export interface BadgeButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof badgeButtonVariants> {
asChild?: boolean;
}
🤖 Prompt for AI Agents
In packages/typegen/web/src/components/ui/badge.tsx around lines 14 to 18, the
BadgeButtonProps incorrectly extends React.ButtonHTMLAttributes<HTMLDivElement>;
change the generic type parameter to
React.ButtonHTMLAttributes<HTMLButtonElement> so the props match a button
element. Update the extends clause to use HTMLButtonElement and confirm any
usages/consumers still compile.

Comment on lines +186 to +209
function Badge({
className,
variant,
size,
appearance,
shape,
asChild = false,
disabled,
...props
}: React.ComponentProps<"span"> &
VariantProps<typeof badgeVariants> & { asChild?: boolean }) {
const Comp = asChild ? SlotPrimitive.Slot : "span";

return (
<Comp
data-slot="badge"
className={cn(
badgeVariants({ variant, size, appearance, shape, disabled }),
className,
)}
{...props}
/>
);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Use the exported BadgeProps interface for type consistency.

The Badge component defines its props inline (lines 195-196) instead of using the exported BadgeProps interface. This creates potential for the interface and implementation to drift apart.

Apply this diff:

 function Badge({
   className,
   variant,
   size,
   appearance,
   shape,
   asChild = false,
   disabled,
   ...props
-}: React.ComponentProps<"span"> &
-  VariantProps<typeof badgeVariants> & { asChild?: boolean }) {
+}: BadgeProps) {
   const Comp = asChild ? SlotPrimitive.Slot : "span";
🤖 Prompt for AI Agents
In packages/typegen/web/src/components/ui/badge.tsx around lines 186 to 209, the
Badge component currently declares its props inline instead of using the
exported BadgeProps interface; update the Badge function signature to accept
BadgeProps (imported if necessary) and remove the inline prop type so the
component's props use the shared exported interface, ensuring types remain
consistent and preventing drift between the implementation and the exported
type.

Comment on lines +211 to +227
function BadgeButton({
className,
variant,
asChild = false,
...props
}: React.ComponentProps<"button"> &
VariantProps<typeof badgeButtonVariants> & { asChild?: boolean }) {
const Comp = asChild ? SlotPrimitive.Slot : "span";
return (
<Comp
data-slot="badge-button"
className={cn(badgeButtonVariants({ variant, className }))}
role="button"
{...props}
/>
);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Fix element type and semantic mismatch in BadgeButton.

The component has semantic and type inconsistencies:

  1. Props are typed as React.ComponentProps<"button"> (line 216) but the component renders as "span" (line 218)
  2. Using role="button" on a span doesn't provide proper button semantics (missing keyboard interaction, focus management, etc.)

Recommended fix: Render as an actual button element:

 function BadgeButton({
   className,
   variant,
   asChild = false,
   ...props
 }: React.ComponentProps<"button"> &
   VariantProps<typeof badgeButtonVariants> & { asChild?: boolean }) {
-  const Comp = asChild ? SlotPrimitive.Slot : "span";
+  const Comp = asChild ? SlotPrimitive.Slot : "button";
   return (
     <Comp
       data-slot="badge-button"
       className={cn(badgeButtonVariants({ variant, className }))}
-      role="button"
       {...props}
     />
   );
 }

If it must remain a span for styling reasons, update the type signature to React.ComponentProps<"span"> and ensure proper keyboard event handlers are added for accessibility.

📝 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.

Suggested change
function BadgeButton({
className,
variant,
asChild = false,
...props
}: React.ComponentProps<"button"> &
VariantProps<typeof badgeButtonVariants> & { asChild?: boolean }) {
const Comp = asChild ? SlotPrimitive.Slot : "span";
return (
<Comp
data-slot="badge-button"
className={cn(badgeButtonVariants({ variant, className }))}
role="button"
{...props}
/>
);
}
function BadgeButton({
className,
variant,
asChild = false,
...props
}: React.ComponentProps<"button"> &
VariantProps<typeof badgeButtonVariants> & { asChild?: boolean }) {
const Comp = asChild ? SlotPrimitive.Slot : "button";
return (
<Comp
data-slot="badge-button"
className={cn(badgeButtonVariants({ variant, className }))}
{...props}
/>
);
}
🤖 Prompt for AI Agents
In packages/typegen/web/src/components/ui/badge.tsx around lines 211 to 227, the
component types and rendered element disagree: props are typed as
React.ComponentProps<"button"> but the component defaults to rendering a "span"
with role="button", which breaks semantics and accessibility. Fix by rendering
an actual <button> by changing the default Comp from "span" to "button" (or if
you must keep a span for styling, change the prop type to
React.ComponentProps<"span"> and add proper keyboard handlers, focusable
tabIndex, and aria attributes to emulate button behavior); update the
VariantProps usage and any callers if their expectations change to ensure types
and runtime element match.

- Upgraded React and React-DOM to version 19.2.3 across various packages for improved performance and features.
- Added support for OData configuration in the typegen package, allowing users to create OData clients from configuration.
- Enhanced error handling in the API client creation process to provide more informative responses.
- Introduced a confirmation dialog for removing configurations in the ConfigEditor component.
- Improved UI components with additional icons and better state management for loading and error states.
- Updated TypeScript configurations to include ES2022 features for better error handling support.
- Added a background class to AccordionItem in App.tsx for improved styling.
- Cleaned up TypeScript configuration files by removing unnecessary whitespace.
- Ensured consistency in component files by removing trailing whitespace in various components.
- Removed unnecessary whitespace in error-parser.ts and TypeScript configuration files.
- Added error logging for unsupported API key authentication in listDatabaseNames() method.
- Improved error handling in createApiApp function to provide more detailed connection error messages.
- Updated ConfigEditor component to conditionally render fields based on config type, enhancing user experience.
- Added console logs for debugging connection parameters in createDataApiClient function.

await generateTypedClients(config);
await next();
},
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: API run endpoint missing response return

The /api/run POST endpoint calls await next() after executing generateTypedClients but doesn't return a response. In Hono, routes need to return a response to the client. Since there's no subsequent handler and no return c.json(...) statement, the HTTP request will fail or hang. All other endpoints in this file properly return responses via c.json().

Fix in Cursor Fix in Web

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 9

♻️ Duplicate comments (6)
packages/typegen/web/src/App.tsx (2)

76-82: Missing error check on runTypegenMutation response.

The mutation doesn't check res.ok, treating all HTTP responses (including 4xx/5xx errors) as success. This is inconsistent with other API calls in the codebase that properly check response status.

Add response validation:

 const runTypegenMutation = useMutation({
   mutationFn: async () => {
-    await client.api.run.$post({
+    const res = await client.api.run.$post({
       json: { config: configs },
     });
+
+    if (!res.ok) {
+      const errorData = (await res.json().catch(() => ({}))) as {
+        error?: string;
+      };
+      throw new Error(
+        errorData.error ||
+          `Failed to run typegen: ${res.status} ${res.statusText}`,
+      );
+    }
+
+    return res.json().catch(() => null);
   },
 });

84-96: handleSaveAll swallows errors, allowing handleRunTypegen to proceed with stale config.

The try-catch in handleSaveAll catches and logs errors but doesn't propagate them. This means handleRunTypegen (line 102) will always proceed even when saving fails, potentially running typegen with invalid or stale configuration.

Remove the try-catch to allow errors to propagate:

 const handleSaveAll = form.handleSubmit(async (data) => {
-  try {
-    await saveMutation.mutateAsync(data.config);
-    const currentConfigs = form.getValues("config");
-    form.reset({ config: currentConfigs });
-  } catch (err) {
-    // Error is handled by the mutation
-    console.error("Failed to save configs:", err);
-  }
+  await saveMutation.mutateAsync(data.config);
+  const currentConfigs = form.getValues("config");
+  form.reset({ config: currentConfigs });
 });
packages/typegen/src/server/app.ts (2)

189-203: POST /api/run endpoint calls next() but doesn't return a response.

After calling generateTypedClients, the handler invokes await next() without returning a response. Since there's no subsequent middleware, this will cause the request to hang or return an empty response.

Replace the next() call with a proper response:

     .post(
       "/run",
       zValidator(
         "json",
         z.object({
           config: z.union([z.array(typegenConfigSingle), typegenConfigSingle]),
         }),
       ),
-      async (c, next) => {
+      async (c) => {
         const data = c.req.valid("json");
         const config = data.config;

         await generateTypedClients(config);
-        await next();
+        return c.json({ success: true });
       },
     )

301-310: GET /api/env-names exposes arbitrary environment variables.

The endpoint allows querying any environment variable by name without restriction, potentially exposing sensitive information like secrets, API keys, or internal configuration.

Implement an allowlist to restrict which environment variables can be queried:

     .get(
       "/env-names",
       zValidator("query", z.object({ envName: z.string() })),
       async (c) => {
         const input = c.req.valid("query");
+        
+        // Only allow querying known env var names
+        const allowedEnvNames = [
+          "FM_SERVER", "FM_DATABASE", "OTTO_API_KEY",
+          "FM_USERNAME", "FM_PASSWORD"
+        ];
+        if (!allowedEnvNames.includes(input.envName)) {
+          return c.json({ error: "Environment variable not allowed" }, 400);
+        }

         const value = process.env[input.envName];

         return c.json({ value });
       },
     )
packages/typegen/src/server/createDataApiClient.ts (1)

75-83: Remove credential logging to prevent security leak.

As flagged in the previous review, console.error logs raw environment values including apiKey and username, which leaks sensitive credentials into stderr and log aggregators. The structured error object returned on lines 104-123 already provides safe boolean flags for missing variables.

Remove the console.error statement:

-  if (!server || !db || (!apiKey && !username)) {
-    console.error("Missing required environment variables", {
-      server,
-      db,
-      apiKey,
-      username,
-    });
-
+  if (!server || !db || (!apiKey && !username)) {
     // Build missing details object
packages/typegen/src/types.ts (1)

27-82: Auth field is still effectively required, breaking existing configs.

As flagged in the previous review, the auth property on line 37 is not marked .optional(), making it a required field even though each variant uses .optional(). This means configs that omit the auth field entirely will fail validation, which is a breaking change for existing configurations.

Make the auth property optional:

   .object({
     server: z
       .string()
       .optional()
       .transform((val) => (val === "" ? undefined : val)),
     db: z
       .string()
       .optional()
       .transform((val) => (val === "" ? undefined : val)),
-    auth: z.union([
+    auth: z.union([
       z
         .object({
           apiKey: z
             .string()
             .optional()
             .transform((val) => (val === "" ? undefined : val)),
         })
         .optional()
         .transform((val) => {
           if (val && Object.values(val).every((v) => v === undefined)) {
             return undefined;
           }
           return val ?? undefined;
         }),
       z
         .object({
           username: z
             .string()
             .optional()
             .transform((val) => (val === "" ? undefined : val)),
           password: z
             .string()
             .optional()
             .transform((val) => (val === "" ? undefined : val)),
         })
         .optional()
         .transform((val) => {
           if (val && Object.values(val).every((v) => v === undefined)) {
             return undefined;
           }
           return val ?? undefined;
         }),
-    ]),
+    ]).optional(),
   })
🧹 Nitpick comments (9)
packages/typegen/web/src/components/theme-provider.tsx (2)

33-49: Add listener for system theme preference changes.

The current implementation only checks the system theme preference once. If a user changes their system theme while the app is running with "system" selected, the UI won't update to reflect the change.

Apply this diff to add a listener:

  useEffect(() => {
    const root = window.document.documentElement;

    root.classList.remove("light", "dark");

    if (theme === "system") {
      const systemTheme = window.matchMedia("(prefers-color-scheme: dark)")
        .matches
        ? "dark"
        : "light";

      root.classList.add(systemTheme);
+
+      const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
+      const handleChange = (e: MediaQueryListEvent) => {
+        root.classList.remove("light", "dark");
+        root.classList.add(e.matches ? "dark" : "light");
+      };
+
+      mediaQuery.addEventListener("change", handleChange);
+      return () => mediaQuery.removeEventListener("change", handleChange);
-      return;
    }

    root.classList.add(theme);
  }, [theme]);

66-73: Context guard is technically unnecessary.

The check context === undefined will never be true since ThemeProviderContext is always initialized with initialState. However, this is a common defensive pattern and the error message is still useful for debugging, so it can remain as-is.

packages/typegen/web/src/components/ui/switch-field.tsx (1)

57-68: Consider InfoTooltip placement for accessibility.

The label association and description implementation are correct. However, the InfoTooltip is currently rendered inside the Label element (line 61), which could potentially cause accessibility issues if InfoTooltip contains interactive elements (nested interactive elements reduce accessibility).

Consider restructuring to place the InfoTooltip adjacent to the label rather than inside it:

-        <Label htmlFor={switchId}>
-          {label} {infoTooltip && <InfoTooltip label={infoTooltip} />}
-        </Label>
+        <div className="flex items-center gap-1">
+          <Label htmlFor={switchId}>{label}</Label>
+          {infoTooltip && <InfoTooltip label={infoTooltip} />}
+        </div>

This change would only be necessary if InfoTooltip contains interactive elements like buttons or links.

packages/typegen/web/src/components/ConfigEditor.tsx (1)

50-56: Consider using useWatch consistently for reactive form field subscriptions.

The component mixes watch (line 50) and useWatch (lines 53-56) for reading form values. While both work, using useWatch consistently provides better performance through granular subscriptions and avoids potential stale closure issues.

Apply this diff for consistency:

   const {
     control,
     formState: { errors },
     setValue,
-    watch,
   } = useFormContext<{ config: SingleConfig[] }>();

   const baseId = useId();
   const generateClientSwitchId = `${baseId}-generate-client`;
-  const configType = watch(`config.${index}.type` as const);
+  const configType = useWatch({
+    control,
+    name: `config.${index}.type` as const,
+  });
packages/typegen/src/fmodata/downloadMetadata.ts (1)

54-54: Remove redundant return statement.

The explicit return; is unnecessary in a Promise<void> function.

Apply this diff:

   await mkdir(outputDir, { recursive: true });
   await writeFile(resolvedPath, fullMetadata, "utf-8");
-
-  return;
 }
packages/typegen/web/src/components/EnvVarDialog.tsx (1)

185-219: Consider extracting auth value preservation logic.

The auth type switching logic (lines 189-212) contains repetitive nested conditionals to preserve existing values. This could be extracted into a helper function to improve readability and reduce duplication.

Example refactor:

const getAuthValue = (key: 'apiKey' | 'username' | 'password') => {
  return currentAuth && 
    typeof currentAuth === "object" && 
    key in currentAuth 
    ? currentAuth[key] 
    : "";
};

// Then use:
if (value === "apiKey") {
  setValue(`config.${index}.envNames.auth` as const, {
    apiKey: getAuthValue('apiKey'),
  });
} else if (value === "username") {
  setValue(`config.${index}.envNames.auth` as const, {
    username: getAuthValue('username'),
    password: getAuthValue('password'),
  });
}
packages/typegen/web/src/hooks/useTestConnection.ts (2)

59-65: Use structured query key instead of JSON.stringify.

JSON.stringify is used to create a stable key for envNames, but this can be unreliable due to key ordering issues. React Query recommends using structured arrays for query keys, which are compared by deep equality.

Apply this refactor:

-  // Create a stable key for envNames to use in queryKey
-  const envNamesKey = envNames
-    ? JSON.stringify({
-        server: envNames.server,
-        db: envNames.db,
-        auth: envNames.auth,
-      })
-    : "";
+  // Use structured key for better React Query caching
+  const envNamesKey = envNames
+    ? {
+        server: envNames.server,
+        db: envNames.db,
+        auth: envNames.auth,
+      }
+    : null;

Then update the query key:

-      queryKey: ["testConnection", configIndex, envNamesKey],
+      queryKey: ["testConnection", configIndex, envNamesKey],

React Query will handle deep equality comparison automatically.


86-95: Consider using a custom error class for type safety.

The code attaches detailed error information to the error object using (error as any).details, which bypasses type safety. While functional, this could be improved with a custom error class.

Example custom error class:

class TestConnectionError extends Error {
  constructor(
    message: string,
    public details: TestConnectionResult
  ) {
    super(message);
    this.name = 'TestConnectionError';
  }
}

// Then in queryFn:
if (!res.ok) {
  const errorData = await res.json().catch(() => ({})) as TestConnectionResult;
  throw new TestConnectionError(
    errorData.error || errorData.message || "Test connection failed",
    errorData
  );
}

// And in the return:
const errorDetails: TestConnectionResult | undefined =
  isError && error instanceof TestConnectionError
    ? error.details
    : data && !data.ok
      ? data
      : undefined;
packages/typegen/src/server/createDataApiClient.ts (1)

47-150: Eliminate code duplication with getEnvValues.ts.

The getEnvVarsFromConfig function duplicates logic from packages/typegen/src/getEnvValues.ts, which provides getEnvValues and validateEnvValues functions. This duplication increases maintenance burden and risk of inconsistencies.

Refactor to use the existing utilities:

import { getEnvValues, validateEnvValues } from "../getEnvValues";

function getEnvVarsFromConfig(
  envNames: SingleConfig["envNames"],
): EnvVarsResult {
  const envValues = getEnvValues(envNames);
  const validation = validateEnvValues(envValues, envNames);
  
  if (!validation.success) {
    // Map the validation error to CreateClientError format
    return {
      error: validation.errorMessage,
      statusCode: 400,
      kind: "missing_env" as const,
      // ... map other fields from validation result
    };
  }
  
  return {
    server: validation.server,
    db: validation.db,
    authType: "apiKey" in validation.auth ? "apiKey" : "username",
    auth: validation.auth,
  };
}

This eliminates ~100 lines of duplicated logic and ensures consistency.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 940dd98 and 33e7146.

⛔ Files ignored due to path filters (2)
  • packages/typegen/web/public/proofkit-horiz.png is excluded by !**/*.png
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (32)
  • packages/fmodata/src/client/filemaker-odata.ts (1 hunks)
  • packages/fmodata/tests/tsconfig.build.json (1 hunks)
  • packages/typegen/package.json (3 hunks)
  • packages/typegen/proofkit-typegen.config.jsonc (1 hunks)
  • packages/typegen/src/fmodata/downloadMetadata.ts (1 hunks)
  • packages/typegen/src/fmodata/index.ts (1 hunks)
  • packages/typegen/src/fmodata/parseMetadata.ts (1 hunks)
  • packages/typegen/src/getEnvValues.ts (1 hunks)
  • packages/typegen/src/server/app.ts (1 hunks)
  • packages/typegen/src/server/createDataApiClient.ts (1 hunks)
  • packages/typegen/src/typegen.ts (4 hunks)
  • packages/typegen/src/types.ts (1 hunks)
  • packages/typegen/tsconfig.json (1 hunks)
  • packages/typegen/web/package.json (1 hunks)
  • packages/typegen/web/src/App.tsx (1 hunks)
  • packages/typegen/web/src/components/ConfigEditor.tsx (1 hunks)
  • packages/typegen/web/src/components/ConfigList.css (1 hunks)
  • packages/typegen/web/src/components/ConfigList.tsx (1 hunks)
  • packages/typegen/web/src/components/ConfigSummary.tsx (1 hunks)
  • packages/typegen/web/src/components/EnvVarDialog.tsx (1 hunks)
  • packages/typegen/web/src/components/LayoutEditor.tsx (1 hunks)
  • packages/typegen/web/src/components/dropdown-menu/default.tsx (1 hunks)
  • packages/typegen/web/src/components/mode-toggle.tsx (1 hunks)
  • packages/typegen/web/src/components/theme-provider.tsx (1 hunks)
  • packages/typegen/web/src/components/ui/dropdown-menu.tsx (1 hunks)
  • packages/typegen/web/src/components/ui/switch-field.tsx (1 hunks)
  • packages/typegen/web/src/components/ui/tooltip.tsx (1 hunks)
  • packages/typegen/web/src/components/useEnvVarIndicator.ts (1 hunks)
  • packages/typegen/web/src/hooks/useConfig.ts (1 hunks)
  • packages/typegen/web/src/hooks/useTestConnection.ts (1 hunks)
  • packages/typegen/web/src/main.tsx (1 hunks)
  • packages/typegen/web/tsconfig.app.json (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (12)
  • packages/typegen/web/src/components/ConfigSummary.tsx
  • packages/typegen/web/tsconfig.app.json
  • packages/typegen/web/package.json
  • packages/typegen/web/src/components/ConfigList.css
  • packages/typegen/web/src/components/ui/tooltip.tsx
  • packages/typegen/proofkit-typegen.config.jsonc
  • packages/typegen/web/src/main.tsx
  • packages/typegen/web/src/components/useEnvVarIndicator.ts
  • packages/typegen/web/src/hooks/useConfig.ts
  • packages/typegen/web/src/components/LayoutEditor.tsx
  • packages/typegen/web/src/components/ConfigList.tsx
  • packages/fmodata/tests/tsconfig.build.json
🧰 Additional context used
🧬 Code graph analysis (10)
packages/typegen/src/fmodata/downloadMetadata.ts (2)
packages/typegen/src/types.ts (1)
  • typegenConfigSingle (137-142)
packages/typegen/src/getEnvValues.ts (2)
  • getEnvValues (35-63)
  • validateEnvValues (73-125)
packages/typegen/web/src/components/mode-toggle.tsx (3)
packages/typegen/web/src/components/theme-provider.tsx (1)
  • useTheme (66-73)
packages/typegen/web/src/components/ui/dropdown-menu.tsx (4)
  • DropdownMenu (209-209)
  • DropdownMenuTrigger (223-223)
  • DropdownMenuContent (211-211)
  • DropdownMenuItem (213-213)
packages/registry/templates/components/mode-toggle/mode-toggle.tsx (1)
  • ModeToggle (14-39)
packages/typegen/src/typegen.ts (3)
packages/typegen/src/types.ts (1)
  • typegenConfigSingle (137-142)
packages/typegen/web/src/lib/schema.ts (1)
  • typegenConfigSingle (3-3)
packages/typegen/src/getEnvValues.ts (2)
  • getEnvValues (35-63)
  • validateAndLogEnvValues (135-179)
packages/typegen/src/getEnvValues.ts (2)
packages/typegen/src/types.ts (1)
  • typegenConfigSingle (137-142)
packages/fmodata/tests/e2e/setup.ts (3)
  • apiKey (21-21)
  • username (22-22)
  • password (23-23)
packages/typegen/src/server/createDataApiClient.ts (4)
packages/typegen/src/types.ts (2)
  • typegenConfigSingle (137-142)
  • typegenConfig (144-146)
packages/typegen/src/server/contract.ts (1)
  • SingleConfig (6-6)
packages/fmodata/src/client/filemaker-odata.ts (1)
  • FMServerConnection (22-304)
packages/typegen/src/server/app.ts (1)
  • ApiContext (19-22)
packages/typegen/web/src/hooks/useTestConnection.ts (3)
packages/typegen/src/server/contract.ts (1)
  • SingleConfig (6-6)
packages/typegen/web/src/lib/config-utils.ts (1)
  • SingleConfig (4-4)
packages/typegen/web/src/lib/api.ts (1)
  • client (7-7)
packages/typegen/src/server/app.ts (4)
packages/fmdapi/src/client-types.ts (2)
  • LayoutOrFolder (262-262)
  • FileMakerError (289-296)
packages/typegen/src/types.ts (2)
  • typegenConfig (144-146)
  • typegenConfigSingle (137-142)
packages/typegen/src/typegen.ts (1)
  • generateTypedClients (29-58)
packages/typegen/src/server/createDataApiClient.ts (3)
  • createDataApiClient (251-310)
  • createClientFromConfig (195-243)
  • createOdataClientFromConfig (167-188)
packages/typegen/src/types.ts (2)
apps/docs/scripts/bundle-registry-templates.js (1)
  • path (4-4)
packages/typegen/web/src/lib/schema.ts (1)
  • typegenConfigSingle (3-3)
packages/typegen/web/src/App.tsx (5)
packages/typegen/web/src/lib/config-utils.ts (1)
  • SingleConfig (4-4)
packages/typegen/web/src/hooks/useConfig.ts (1)
  • useConfig (5-56)
packages/typegen/web/src/lib/api.ts (1)
  • client (7-7)
packages/typegen/web/src/components/ConfigSummary.tsx (1)
  • ConfigSummary (5-36)
packages/typegen/web/src/components/ConfigEditor.tsx (1)
  • ConfigEditor (40-328)
packages/typegen/web/src/components/EnvVarDialog.tsx (8)
packages/typegen/web/src/hooks/useTestConnection.ts (2)
  • setDialogOpen (11-17)
  • useTestConnection (44-140)
packages/typegen/web/src/components/useEnvVarIndicator.ts (1)
  • useEnvVarIndicator (6-81)
packages/typegen/web/src/lib/envValues.ts (1)
  • useEnvValue (4-20)
packages/typegen/web/src/components/ui/dialog.tsx (4)
  • Dialog (128-128)
  • DialogTrigger (138-138)
  • DialogContent (131-131)
  • DialogTitle (137-137)
packages/typegen/web/src/components/ui/button.tsx (1)
  • Button (423-423)
packages/typegen/web/src/components/ServerEnvField.tsx (1)
  • EnvVarField (25-70)
packages/typegen/web/src/components/EnvVarField.tsx (1)
  • EnvVarField (115-139)
packages/typegen/web/src/components/ui/select.tsx (5)
  • Select (224-224)
  • SelectTrigger (233-233)
  • SelectValue (234-234)
  • SelectContent (225-225)
  • SelectItem (228-228)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Cursor Bugbot
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: build
  • GitHub Check: build
🔇 Additional comments (16)
packages/typegen/web/src/components/dropdown-menu/default.tsx (1)

35-158: LGTM! Comprehensive dropdown demo.

This demo effectively showcases the dropdown menu component capabilities including nested submenus, keyboard shortcuts, badges, and various menu item states. The structure is clear and serves as a good reference for component usage.

packages/typegen/web/src/components/mode-toggle.tsx (1)

12-37: LGTM! Clean theme toggle implementation.

The component correctly integrates with the theme provider and follows the standard pattern for theme toggles with smooth icon transitions. The implementation aligns with the registry template.

packages/typegen/web/src/components/ui/switch-field.tsx (5)

1-7: LGTM: Clean import structure.

The imports are well-organized and appropriate for a client-side React component with UI primitives and utilities.


9-19: LGTM: Well-defined props interface.

The props interface is comprehensive with appropriate types. The size prop uses string literals for type safety, and optional props are clearly marked.


21-34: Excellent ID generation pattern.

The use of useId() with a fallback to a provided ID is a React best practice for generating stable, unique identifiers. The derived descriptionId properly supports the accessibility implementation below.


36-45: LGTM: Well-structured container with conditional styling.

The container appropriately adjusts layout based on description presence and provides good visual feedback with the border color change when checked.


46-56: Switch component props verification complete.

The size prop is properly supported by the Switch component via its CVA variants (sm, md, lg, xl). The accessibility implementation with aria-describedby is correctly implemented.

The className="order-1" on the Switch is ineffective since it's the only child of the flex container—the inline-flex in SwitchWrapper only applies ordering rules when there are multiple flex siblings. This can optionally be removed.

packages/typegen/web/src/components/ConfigEditor.tsx (1)

48-50: LGTM: Hook violation from previous review has been correctly resolved.

The useId hook is now called at the top level of the component, and IDs are derived from the base ID, properly addressing the Rules of Hooks violation flagged in the previous review.

packages/typegen/package.json (1)

31-42: LGTM: Export paths correctly distinguish API contract from app implementation.

The ./api and ./api-app exports now correctly point to separate implementations (contract.js vs app.js), properly addressing the duplicate export concern from the previous review.

packages/typegen/src/typegen.ts (2)

49-57: LGTM: Proper type narrowing and graceful handling of unsupported config types.

The code correctly normalizes config to an array and uses type guards to process only fmdapi configurations while warning about unsupported types. This aligns well with the narrowed function signature at line 61.


101-112: LGTM: Clean environment validation flow with proper error handling.

The refactored validation flow using getEnvValues and validateAndLogEnvValues provides clear separation of concerns and proper early-exit on validation failure.

packages/typegen/src/fmodata/index.ts (1)

1-11: LGTM: Clean barrel export pattern for fmodata module.

The file provides a consolidated public API surface for metadata handling functionality, properly re-exporting functions and types.

packages/typegen/src/fmodata/parseMetadata.ts (1)

60-66: LGTM: Well-configured XML parser for OData metadata.

The parser configuration appropriately handles attributes, auto-parses values, and uses a prefix to avoid naming collisions. The boolean parsing throughout the file correctly handles both string and boolean representations.

packages/typegen/src/getEnvValues.ts (2)

35-63: LGTM!

The environment variable resolution logic correctly handles custom env names with fallback to defaults, supporting both API key and username/password authentication patterns.


73-125: LGTM!

The validation logic correctly identifies missing environment variables and constructs clear error messages. The defensive ?? "" fallback on lines 117 is acceptable even though validation ensures these values exist, as it provides additional type safety.

packages/typegen/src/types.ts (1)

90-142: LGTM!

The z.preprocess wrapper adds a default type: "fmdapi" for backward compatibility, which properly handles existing configs that don't specify a type field. The discriminated union structure is well-designed.

Comment on lines +9 to +10
/* Include ES2022 lib for ErrorOptions and Error.cause support */
"lib": ["DOM", "DOM.Iterable", "ES2021", "ES2022"],
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash

# Check if packages/typegen exists and what files it contains
echo "=== Checking packages/typegen directory ==="
if [ -d "packages/typegen" ]; then
  echo "Directory exists. Listing contents:"
  ls -la packages/typegen/ | head -20
  
  # Check package.json
  echo -e "\n=== packages/typegen/package.json ==="
  if [ -f "packages/typegen/package.json" ]; then
    cat packages/typegen/package.json
  fi
  
  # Check tsconfig.json
  echo -e "\n=== packages/typegen/tsconfig.json ==="
  if [ -f "packages/typegen/tsconfig.json" ]; then
    cat packages/typegen/tsconfig.json
  fi
  
  # Check if src directory exists
  echo -e "\n=== Checking packages/typegen/src ==="
  if [ -d "packages/typegen/src" ]; then
    echo "Source directory exists. Files:"
    find packages/typegen/src -type f -name "*.ts" -o -name "*.tsx" | head -20
  fi
else
  echo "packages/typegen directory not found"
  echo "Available packages:"
  ls -d packages/*/ 2>/dev/null | head -20
fi

Repository: proofgeist/proofkit

Length of output: 5377


🏁 Script executed:

#!/bin/bash

# Search for DOM API usage in src directory
echo "=== Searching for DOM API usage in packages/typegen/src ==="
rg -n --type=ts '(document\.|window\.|HTMLElement|navigator\.|localStorage|sessionStorage|fetch\(|addEventListener|DOM|document\.querySelector|document\.getElementById)' packages/typegen/src/

echo -e "\n=== Checking if web/ has its own tsconfig.json ==="
if [ -f "packages/typegen/web/tsconfig.json" ]; then
  echo "web/tsconfig.json exists:"
  cat packages/typegen/web/tsconfig.json
else
  echo "web/tsconfig.json does not exist"
  ls -la packages/typegen/web/ 2>/dev/null || echo "web/ directory not found"
fi

echo -e "\n=== Checking if vite.config.ts uses DOM or browser APIs ==="
head -50 packages/typegen/vite.config.ts

Repository: proofgeist/proofkit

Length of output: 1467


Remove unnecessary DOM types from server-side TypeScript configuration.

The main packages/typegen/tsconfig.json is used for server-side code and contains no DOM API usage. DOM types should only be included in the web UI's separate tsconfig.json (which they already are). The ES2022 addition for Error.cause support is appropriate, but DOM and DOM.Iterable should be removed:

Change:

"lib": ["DOM", "DOM.Iterable", "ES2021", "ES2022"],

To:

"lib": ["ES2022"],
🤖 Prompt for AI Agents
In packages/typegen/tsconfig.json around lines 9 to 10, the tsconfig
unnecessarily includes DOM and DOM.Iterable which are client-side types; remove
those and simplify the lib entry to only include the needed ES2022 lib (for
Error.cause) by replacing the current lib array with a single "ES2022" entry so
server-side compilation no longer pulls in DOM types.

Comment on lines +37 to +39
const { control, setValue, getValues } = useFormContext<{
config: any[];
}>();
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Replace any[] with proper type.

The form context uses any[] for the config array, which bypasses type safety. Use the proper SingleConfig[] type imported from config-utils (line 4) to ensure type safety throughout the component.

Apply this diff:

-  const { control, setValue, getValues } = useFormContext<{
-    config: any[];
-  }>();
+  const { control, setValue, getValues } = useFormContext<{
+    config: SingleConfig[];
+  }>();
📝 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.

Suggested change
const { control, setValue, getValues } = useFormContext<{
config: any[];
}>();
const { control, setValue, getValues } = useFormContext<{
config: SingleConfig[];
}>();
🤖 Prompt for AI Agents
In packages/typegen/web/src/components/EnvVarDialog.tsx around lines 37 to 39,
the useFormContext generic currently uses `any[]` for `config`; replace it with
the proper `SingleConfig[]` type (imported from `config-utils` on line 4) so the
line becomes useFormContext<{ config: SingleConfig[] }>(), and update any
related local typings if necessary to match `SingleConfig` fields to restore
full type safety.

Comment on lines 100 to 129
useEffect(() => {
let authSelector: "none" | "apiKey" | "username" = "apiKey";

if (envNamesAuth) {
if (typeof envNamesAuth === "object") {
// Check for username first (since it has two fields, it's more specific)
if ("username" in envNamesAuth || "password" in envNamesAuth) {
authSelector = "username";
} else if ("apiKey" in envNamesAuth) {
authSelector = "apiKey";
}
// If it's an empty object {}, don't change the selector or reset values
// This preserves the current state when the server returns {}
}
} else {
// Only initialize if auth is truly undefined/null
// Check current form value to avoid overwriting
const currentAuth = getValues(`config.${index}.envNames.auth` as any);
if (!currentAuth) {
setValue(`config.${index}.envNames.auth` as const, {
apiKey: "",
});
}
}

// Only update selector if it's different to avoid unnecessary re-renders
if (authSelector !== authTypeSelector) {
setAuthTypeSelector(authSelector);
}
}, [envNamesAuth, setValue, getValues, index, authTypeSelector]);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Risk of infinite loop due to state in effect dependencies.

The effect depends on authTypeSelector (line 129) and conditionally updates it via setAuthTypeSelector (line 127). While the guard on line 126 prevents updates when unchanged, this pattern is fragile and can lead to infinite loops if the comparison logic changes or if React's batching behavior changes.

Apply this refactor to remove the circular dependency:

-  }, [envNamesAuth, setValue, getValues, index, authTypeSelector]);
+  }, [envNamesAuth, setValue, getValues, index]);

The effect will still work correctly because it derives authSelector from envNamesAuth and only updates the state when needed. Removing authTypeSelector from dependencies eliminates the circular dependency risk.

📝 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.

Suggested change
useEffect(() => {
let authSelector: "none" | "apiKey" | "username" = "apiKey";
if (envNamesAuth) {
if (typeof envNamesAuth === "object") {
// Check for username first (since it has two fields, it's more specific)
if ("username" in envNamesAuth || "password" in envNamesAuth) {
authSelector = "username";
} else if ("apiKey" in envNamesAuth) {
authSelector = "apiKey";
}
// If it's an empty object {}, don't change the selector or reset values
// This preserves the current state when the server returns {}
}
} else {
// Only initialize if auth is truly undefined/null
// Check current form value to avoid overwriting
const currentAuth = getValues(`config.${index}.envNames.auth` as any);
if (!currentAuth) {
setValue(`config.${index}.envNames.auth` as const, {
apiKey: "",
});
}
}
// Only update selector if it's different to avoid unnecessary re-renders
if (authSelector !== authTypeSelector) {
setAuthTypeSelector(authSelector);
}
}, [envNamesAuth, setValue, getValues, index, authTypeSelector]);
useEffect(() => {
let authSelector: "none" | "apiKey" | "username" = "apiKey";
if (envNamesAuth) {
if (typeof envNamesAuth === "object") {
// Check for username first (since it has two fields, it's more specific)
if ("username" in envNamesAuth || "password" in envNamesAuth) {
authSelector = "username";
} else if ("apiKey" in envNamesAuth) {
authSelector = "apiKey";
}
// If it's an empty object {}, don't change the selector or reset values
// This preserves the current state when the server returns {}
}
} else {
// Only initialize if auth is truly undefined/null
// Check current form value to avoid overwriting
const currentAuth = getValues(`config.${index}.envNames.auth` as any);
if (!currentAuth) {
setValue(`config.${index}.envNames.auth` as const, {
apiKey: "",
});
}
}
// Only update selector if it's different to avoid unnecessary re-renders
if (authSelector !== authTypeSelector) {
setAuthTypeSelector(authSelector);
}
}, [envNamesAuth, setValue, getValues, index]);
🤖 Prompt for AI Agents
In packages/typegen/web/src/components/EnvVarDialog.tsx around lines 100 to 129,
the useEffect currently lists authTypeSelector in its dependency array and
conditionally calls setAuthTypeSelector inside the effect, creating a fragile
circular dependency; remove authTypeSelector from the dependency array so the
effect only depends on envNamesAuth, setValue, getValues, and index, and keep
the existing guard that only calls setAuthTypeSelector when authSelector !==
authTypeSelector to avoid unnecessary updates; ensure setAuthTypeSelector is the
same stable setter (no further changes needed) and run tests to confirm no
regressions.

Comment on lines +29 to +31
const [theme, setTheme] = useState<Theme>(
() => (localStorage.getItem(storageKey) as Theme) || defaultTheme
);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Validate localStorage value before using.

The localStorage value is cast directly to Theme without validation. If the stored value is corrupted or invalid, this could result in unexpected behavior.

Apply this diff to add validation:

  const [theme, setTheme] = useState<Theme>(
-    () => (localStorage.getItem(storageKey) as Theme) || defaultTheme
+    () => {
+      const stored = localStorage.getItem(storageKey) as Theme;
+      if (stored === "dark" || stored === "light" || stored === "system") {
+        return stored;
+      }
+      return defaultTheme;
+    }
  );
📝 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.

Suggested change
const [theme, setTheme] = useState<Theme>(
() => (localStorage.getItem(storageKey) as Theme) || defaultTheme
);
const [theme, setTheme] = useState<Theme>(
() => {
const stored = localStorage.getItem(storageKey) as Theme;
if (stored === "dark" || stored === "light" || stored === "system") {
return stored;
}
return defaultTheme;
}
);
🤖 Prompt for AI Agents
In packages/typegen/web/src/components/theme-provider.tsx around lines 29-31,
the useState initializer casts localStorage.getItem(storageKey) to Theme without
validation; replace this with code that reads the raw string, checks it against
the allowed Theme values (e.g., Object.values(Theme) or a small whitelist), and
returns that value only if valid, otherwise return defaultTheme; also guard
against exceptions (wrap read in try/catch) and avoid direct casting so
corrupted or unexpected values fall back to default.

Comment on lines +53 to +56
setTheme: (theme: Theme) => {
localStorage.setItem(storageKey, theme);
setTheme(theme);
},
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Add error handling for localStorage operations.

The localStorage.setItem call can throw errors in environments where localStorage is unavailable (private browsing mode), full, or restricted. This could crash the theme toggle functionality.

Apply this diff to add error handling:

    setTheme: (theme: Theme) => {
-      localStorage.setItem(storageKey, theme);
+      try {
+        localStorage.setItem(storageKey, theme);
+      } catch (error) {
+        console.warn("Failed to save theme preference:", error);
+      }
      setTheme(theme);
    },
📝 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.

Suggested change
setTheme: (theme: Theme) => {
localStorage.setItem(storageKey, theme);
setTheme(theme);
},
setTheme: (theme: Theme) => {
try {
localStorage.setItem(storageKey, theme);
} catch (error) {
console.warn("Failed to save theme preference:", error);
}
setTheme(theme);
},
🤖 Prompt for AI Agents
In packages/typegen/web/src/components/theme-provider.tsx around lines 53 to 56,
the direct call to localStorage.setItem may throw and crash the theme toggle;
wrap the storage operation in a try/catch (or guard with typeof window !==
'undefined' && window.localStorage) so failures don’t propagate, log or report
the error in the catch, and always call the React setTheme(state updater) so the
UI still updates even if persisting to localStorage fails; keep behavior
minimal—don’t rethrow, optionally fall back to an in-memory flag if persistence
is required.

- Updated typegen configuration to support OData clients, allowing for better integration and client generation.
- Improved the ConfigEditor component with new fields for metadata path and auto-download options.
- Refactored layout and switch components for better usability and added tooltips for clarity.
- Enhanced error handling and logging in the type generation process.
- Updated UI components to improve user experience, including better state management and additional icons.
- Added support for downloading and parsing metadata in the ConfigEditor component, allowing users to manage OData configurations more effectively.
- Updated typegen configuration to ensure metadata paths point to .xml files, improving validation and user feedback.
- Introduced new API endpoints for downloading and parsing metadata, enhancing the overall functionality of the typegen server.
- Improved UI components with additional state management for download actions and better user experience through tooltips and loading indicators.
- Updated dependencies for @dnd-kit and @TanStack libraries to the latest versions for improved performance and features.
eluce2 and others added 10 commits December 19, 2025 08:27
…tion testing

- Introduced a new state to control loading of tables based on configuration.
- Added connection testing logic to display appropriate messages for connection errors.
- Updated useListTables hook to accept an enabled parameter for conditional querying.
- Improved loading and error handling UI to provide better user feedback.
…ction

- Added support for `reduceMetadata` option in the typegen configuration for `fmdapi_test` table, allowing for field-specific overrides.
- Updated the `ConfigEditor` component to include a toggle for reducing metadata annotations.
- Improved `MetadataFieldsDialog` to manage variable name and reduce metadata settings for individual tables.
- Refactored `generateODataTablesSingle` to utilize the new `reduceMetadata` configuration.
- Deleted unused OData schema files: contacts, customer, related, user_customer, users, fmdapi_test, isolated_contacts, and their corresponding index entries.
- Updated typegen configuration to include `alwaysOverrideFieldNames` option, allowing for more flexible field name management during schema generation.
- Improved handling of existing fields in `generateODataTypes` to preserve user customizations and manage field name updates based on metadata.
This commit refactors the logic for preserving user-defined chained calls in generated types. It also updates dependencies in pnpm-lock.yaml and adds a new test file for preserving user customizations.

Co-authored-by: eric.luce <eric.luce@proofgeist.com>
@eluce2 eluce2 changed the base branch from 12-07-orm to graphite-base/78 December 19, 2025 17:51
@eluce2 eluce2 changed the base branch from graphite-base/78 to 10-28-fmdata December 19, 2025 17:52
validator = z.number();
break;
default:
validator = z.unknown();
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Schema only stores explicit validators, breaking expand default select

The refactored code in fmTableOccurrence now only populates outputSchema with entries for fields that have explicit outputValidator set. Previously, default validators were auto-generated for every field. This breaks getDefaultSelectFields() which uses Object.keys(baseTableConfig.schema) to enumerate field names when defaultSelect === "schema". For tables without explicit validators, this returns an empty array, causing expand operations to have empty $select clauses. While the main query path in entity-set.ts uses getTableColumns() (which still works), the expand-builder.ts relies on the now-broken getDefaultSelectFields().

Additional Locations (1)

Fix in Cursor Fix in Web

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants