diff --git a/packages/admin-next/src/components/resource-form.test.tsx b/packages/admin-next/src/components/resource-form.test.tsx
new file mode 100644
index 0000000..ee3b466
--- /dev/null
+++ b/packages/admin-next/src/components/resource-form.test.tsx
@@ -0,0 +1,107 @@
+import { cleanup, render, screen, waitFor } from "@testing-library/react";
+import { afterEach, describe, expect, it, vi } from "vitest";
+import type { AdminActions } from "../server/actions.js";
+import type { FormSchema } from "../types.js";
+import { ResourceForm } from "./resource-form.js";
+
+afterEach(cleanup);
+
+describe("ResourceForm", () => {
+ it("prefills edit fields declared inside conditional dependencies", async () => {
+ const schema: FormSchema = {
+ uiType: "form",
+ type: "edit",
+ schema: {
+ type: "object",
+ additionalProperties: false,
+ properties: {
+ models_mode: {
+ type: "string",
+ enum: ["all", "only"],
+ },
+ voices_mode: {
+ type: "string",
+ enum: ["all", "only"],
+ },
+ },
+ dependencies: {
+ models_mode: {
+ oneOf: [
+ {
+ properties: {
+ models_mode: { enum: ["all"] },
+ },
+ },
+ {
+ properties: {
+ models_mode: { enum: ["only"] },
+ models_allow: {
+ type: "array",
+ items: { type: "string" },
+ },
+ },
+ },
+ ],
+ },
+ voices_mode: {
+ oneOf: [
+ {
+ properties: {
+ voices_mode: { enum: ["all"] },
+ },
+ },
+ {
+ properties: {
+ voices_mode: { enum: ["only"] },
+ voices_allow: {
+ type: "array",
+ items: { type: "string" },
+ },
+ },
+ },
+ ],
+ },
+ },
+ },
+ uiSchema: {},
+ supportedActions: [],
+ };
+ const actions: AdminActions = {
+ listResources: vi.fn(),
+ getSchema: vi.fn(),
+ fetchUrl: vi.fn(),
+ submitAction: vi.fn(),
+ fetchAction: vi.fn().mockResolvedValue({
+ ok: true,
+ data: {
+ data: {
+ id: "free",
+ models_mode: "only",
+ models_allow: ["gpt-4o-mini"],
+ voices_mode: "only",
+ voices_allow: ["en-US-E2EAvaNeural"],
+ },
+ },
+ }),
+ };
+
+ render(
+ ,
+ );
+
+ expect(await screen.findByDisplayValue("gpt-4o-mini")).toBeTruthy();
+ expect(await screen.findByDisplayValue("en-US-E2EAvaNeural")).toBeTruthy();
+ await waitFor(() => {
+ expect(actions.fetchAction).toHaveBeenCalledWith("permissions", "edit", {
+ dynamicPath: "free",
+ });
+ });
+ });
+});
diff --git a/packages/admin-next/src/components/resource-form.tsx b/packages/admin-next/src/components/resource-form.tsx
index a2a68e5..da5d2e5 100644
--- a/packages/admin-next/src/components/resource-form.tsx
+++ b/packages/admin-next/src/components/resource-form.tsx
@@ -22,15 +22,58 @@ import {
} from "./array-templates.js";
import { Button } from "./ui.js";
-/** Keep only the keys declared in the form schema's `properties`. */
+type SchemaNode = {
+ type?: string | string[];
+ properties?: Record;
+ items?: SchemaNode;
+ dependencies?: Record;
+ oneOf?: SchemaNode[];
+ anyOf?: SchemaNode[];
+ allOf?: SchemaNode[];
+ if?: SchemaNode;
+ then?: SchemaNode;
+ else?: SchemaNode;
+};
+
+/**
+ * Collect every property key a schema can hold, including keys declared in
+ * conditional draft-07 branches. RJSF resolves these branches at render time,
+ * so edit-prefill filtering must account for them before handing over data.
+ */
+function collectSchemaKeys(
+ schema: SchemaNode,
+ keys = new Set(),
+ seen = new Set(),
+): Set {
+ if (seen.has(schema)) return keys;
+ seen.add(schema);
+
+ for (const key of Object.keys(schema.properties ?? {})) keys.add(key);
+
+ for (const branch of [schema.if, schema.then, schema.else]) {
+ if (branch) collectSchemaKeys(branch, keys, seen);
+ }
+ for (const branches of [schema.oneOf, schema.anyOf, schema.allOf]) {
+ for (const branch of branches ?? []) {
+ collectSchemaKeys(branch, keys, seen);
+ }
+ }
+ for (const dependency of Object.values(schema.dependencies ?? {})) {
+ if (!Array.isArray(dependency)) {
+ collectSchemaKeys(dependency, keys, seen);
+ }
+ }
+
+ return keys;
+}
+
+/** Keep only the keys the form schema can hold (declared or conditional). */
function pickSchemaKeys(
data: Record,
schema: FormSchema,
): Record {
- const properties = (schema.schema as { properties?: Record })
- .properties;
- if (!properties) return data;
- const allowed = new Set(Object.keys(properties));
+ const allowed = collectSchemaKeys(schema.schema as SchemaNode);
+ if (allowed.size === 0) return data;
const out: Record = {};
for (const [key, value] of Object.entries(data)) {
if (allowed.has(key)) out[key] = value;
@@ -38,12 +81,6 @@ function pickSchemaKeys(
return out;
}
-type SchemaNode = {
- type?: string | string[];
- properties?: Record;
- items?: SchemaNode;
-};
-
function normalizeFormDataForSchema(
data: Record,
schema: FormSchema,