@acmekit/acmekit 2.13.89 → 2.13.91

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"develop.d.ts","sourceRoot":"","sources":["../../../src/commands/plugin/develop.ts"],"names":[],"mappings":"AAcA,wBAA8B,aAAa,CAAC,EAC1C,SAAS,GACV,EAAE;IACD,SAAS,EAAE,MAAM,CAAA;CAClB,iBA0HA"}
1
+ {"version":3,"file":"develop.d.ts","sourceRoot":"","sources":["../../../src/commands/plugin/develop.ts"],"names":[],"mappings":"AA+DA,wBAA8B,aAAa,CAAC,EAC1C,SAAS,GACV,EAAE;IACD,SAAS,EAAE,MAAM,CAAA;CAClB,iBA8FA"}
@@ -1,7 +1,19 @@
1
1
  "use strict";
2
+ var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
3
+ if (kind === "m") throw new TypeError("Private method is not writable");
4
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
5
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
6
+ return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
7
+ };
8
+ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
9
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
10
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
11
+ return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
12
+ };
2
13
  var __importDefault = (this && this.__importDefault) || function (mod) {
3
14
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
15
  };
16
+ var _DebouncedTask_instances, _DebouncedTask_timer, _DebouncedTask_running, _DebouncedTask_queued, _DebouncedTask_fn, _DebouncedTask_delay, _DebouncedTask_run;
5
17
  Object.defineProperty(exports, "__esModule", { value: true });
6
18
  exports.default = developPlugin;
7
19
  const build_tools_1 = require("@acmekit/framework/build-tools");
@@ -13,6 +25,53 @@ const ADMIN_REBUILD_DEBOUNCE_MS = 500;
13
25
  const ROUTE_TYPES_DEBOUNCE_MS = 300;
14
26
  const QUERY_TYPES_DEBOUNCE_MS = 300;
15
27
  const YALC_PUSH_DEBOUNCE_MS = 300;
28
+ /**
29
+ * Debounces calls and serializes execution. If `schedule()` is called while
30
+ * the task is already running, a single follow-up execution is queued and
31
+ * runs after the current one completes — no concurrent executions.
32
+ */
33
+ class DebouncedTask {
34
+ constructor(fn, delay) {
35
+ _DebouncedTask_instances.add(this);
36
+ _DebouncedTask_timer.set(this, null);
37
+ _DebouncedTask_running.set(this, false);
38
+ _DebouncedTask_queued.set(this, false);
39
+ _DebouncedTask_fn.set(this, void 0);
40
+ _DebouncedTask_delay.set(this, void 0);
41
+ __classPrivateFieldSet(this, _DebouncedTask_fn, fn, "f");
42
+ __classPrivateFieldSet(this, _DebouncedTask_delay, delay, "f");
43
+ }
44
+ schedule() {
45
+ if (__classPrivateFieldGet(this, _DebouncedTask_running, "f")) {
46
+ __classPrivateFieldSet(this, _DebouncedTask_queued, true, "f");
47
+ return;
48
+ }
49
+ if (__classPrivateFieldGet(this, _DebouncedTask_timer, "f"))
50
+ clearTimeout(__classPrivateFieldGet(this, _DebouncedTask_timer, "f"));
51
+ __classPrivateFieldSet(this, _DebouncedTask_timer, setTimeout(() => __classPrivateFieldGet(this, _DebouncedTask_instances, "m", _DebouncedTask_run).call(this), __classPrivateFieldGet(this, _DebouncedTask_delay, "f")), "f");
52
+ }
53
+ dispose() {
54
+ if (__classPrivateFieldGet(this, _DebouncedTask_timer, "f")) {
55
+ clearTimeout(__classPrivateFieldGet(this, _DebouncedTask_timer, "f"));
56
+ __classPrivateFieldSet(this, _DebouncedTask_timer, null, "f");
57
+ }
58
+ __classPrivateFieldSet(this, _DebouncedTask_queued, false, "f");
59
+ }
60
+ }
61
+ _DebouncedTask_timer = new WeakMap(), _DebouncedTask_running = new WeakMap(), _DebouncedTask_queued = new WeakMap(), _DebouncedTask_fn = new WeakMap(), _DebouncedTask_delay = new WeakMap(), _DebouncedTask_instances = new WeakSet(), _DebouncedTask_run = async function _DebouncedTask_run() {
62
+ __classPrivateFieldSet(this, _DebouncedTask_timer, null, "f");
63
+ __classPrivateFieldSet(this, _DebouncedTask_running, true, "f");
64
+ try {
65
+ await __classPrivateFieldGet(this, _DebouncedTask_fn, "f").call(this);
66
+ }
67
+ finally {
68
+ __classPrivateFieldSet(this, _DebouncedTask_running, false, "f");
69
+ if (__classPrivateFieldGet(this, _DebouncedTask_queued, "f")) {
70
+ __classPrivateFieldSet(this, _DebouncedTask_queued, false, "f");
71
+ this.schedule();
72
+ }
73
+ }
74
+ };
16
75
  async function developPlugin({ directory, }) {
17
76
  const compiler = new build_tools_1.Compiler(directory, logger_1.logger);
18
77
  const parsedConfig = await compiler.loadTSConfigFile();
@@ -26,80 +85,49 @@ async function developPlugin({ directory, }) {
26
85
  if (!adminBuilt) {
27
86
  logger_1.logger.warn("[plugin:develop] Admin extensions build failed, continuing backend-only");
28
87
  }
29
- // Debounced yalc push triggered after backend transpile or admin rebuild.
30
- // Publishes to the store then pushes to all tracked installations.
31
- let yalcPushTimeout = null;
32
- const scheduleYalcPush = () => {
33
- if (yalcPushTimeout)
34
- clearTimeout(yalcPushTimeout);
35
- yalcPushTimeout = setTimeout(async () => {
36
- yalcPushTimeout = null;
37
- try {
38
- const synced = await (0, require_yalc_1.yalcPublishAndPush)(directory);
39
- if (synced > 0) {
40
- logger_1.logger.info(`[plugin:develop] Synced to ${synced} app${synced > 1 ? "s" : ""}`);
41
- }
88
+ const yalcPush = new DebouncedTask(async () => {
89
+ try {
90
+ const synced = await (0, require_yalc_1.yalcPublishAndPush)(directory);
91
+ if (synced > 0) {
92
+ logger_1.logger.info(`[plugin:develop] Synced to ${synced} app${synced > 1 ? "s" : ""}`);
42
93
  }
43
- catch (err) {
44
- logger_1.logger.warn(`[plugin:develop] Sync failed: ${err.message}`);
45
- }
46
- }, YALC_PUSH_DEBOUNCE_MS);
47
- };
94
+ }
95
+ catch (err) {
96
+ logger_1.logger.warn(`[plugin:develop] Sync failed: ${err.message}`);
97
+ }
98
+ }, YALC_PUSH_DEBOUNCE_MS);
48
99
  // Sync to host apps immediately after the initial build so the
49
100
  // developer doesn't have to save a file to trigger the first push.
50
- scheduleYalcPush();
51
- // Debounced route-type regeneration triggered by API file changes
52
- let routeTypesTimeout = null;
53
- const scheduleRouteTypesRegen = () => {
54
- if (routeTypesTimeout)
55
- clearTimeout(routeTypesTimeout);
56
- routeTypesTimeout = setTimeout(async () => {
57
- routeTypesTimeout = null;
58
- await compiler.generatePluginRouteTypes();
59
- logger_1.logger.info("[plugin:develop] Route types updated");
60
- }, ROUTE_TYPES_DEBOUNCE_MS);
61
- };
62
- // Debounced query-type regeneration triggered by module file changes
63
- let queryTypesTimeout = null;
64
- const scheduleQueryTypesRegen = () => {
65
- if (queryTypesTimeout)
66
- clearTimeout(queryTypesTimeout);
67
- queryTypesTimeout = setTimeout(async () => {
68
- queryTypesTimeout = null;
69
- await compiler.generatePluginQueryTypes({ bustCache: true });
70
- logger_1.logger.info("[plugin:develop] Query types updated");
71
- }, QUERY_TYPES_DEBOUNCE_MS);
72
- };
101
+ yalcPush.schedule();
102
+ const routeTypesRegen = new DebouncedTask(async () => {
103
+ await compiler.generatePluginRouteTypes();
104
+ logger_1.logger.info("[plugin:develop] Route types updated");
105
+ }, ROUTE_TYPES_DEBOUNCE_MS);
106
+ const queryTypesRegen = new DebouncedTask(async () => {
107
+ await compiler.generatePluginQueryTypes({ bustCache: true });
108
+ logger_1.logger.info("[plugin:develop] Query types updated");
109
+ }, QUERY_TYPES_DEBOUNCE_MS);
73
110
  // Watch for backend changes (transpiles src/ → .acmekit/server/src/)
74
111
  const transpiler = new build_tools_1.LocalPluginTranspiler({
75
112
  pluginRoot: directory,
76
113
  logger: logger_1.logger,
77
114
  onFileChange: (transpiledFilePath) => {
78
- // Only regenerate when an API route file changed
79
115
  if (transpiledFilePath.includes(`${path_1.sep}api${path_1.sep}`)) {
80
- scheduleRouteTypesRegen();
116
+ routeTypesRegen.schedule();
81
117
  }
82
- // Regenerate query types when a module file changed
83
118
  if (transpiledFilePath.includes(`${path_1.sep}modules${path_1.sep}`)) {
84
- scheduleQueryTypesRegen();
119
+ queryTypesRegen.schedule();
85
120
  }
86
- scheduleYalcPush();
121
+ yalcPush.schedule();
87
122
  },
88
123
  });
89
124
  await transpiler.writeOptionsFile();
90
125
  await transpiler.watch();
91
126
  // Watch admin extensions for rebuild
92
- let adminRebuildTimeout = null;
93
- const scheduleAdminRebuild = () => {
94
- if (adminRebuildTimeout) {
95
- clearTimeout(adminRebuildTimeout);
96
- }
97
- adminRebuildTimeout = setTimeout(async () => {
98
- adminRebuildTimeout = null;
99
- await compiler.buildPluginAdminExtensions(bundler);
100
- scheduleYalcPush();
101
- }, ADMIN_REBUILD_DEBOUNCE_MS);
102
- };
127
+ const adminRebuild = new DebouncedTask(async () => {
128
+ await compiler.buildPluginAdminExtensions(bundler);
129
+ yalcPush.schedule();
130
+ }, ADMIN_REBUILD_DEBOUNCE_MS);
103
131
  const adminWatcher = chokidar_1.default
104
132
  .watch("src/admin", {
105
133
  cwd: directory,
@@ -113,19 +141,15 @@ async function developPlugin({ directory, }) {
113
141
  "**/__admin-extensions__.js",
114
142
  ],
115
143
  })
116
- .on("add", scheduleAdminRebuild)
117
- .on("change", scheduleAdminRebuild)
118
- .on("unlink", scheduleAdminRebuild);
144
+ .on("add", () => adminRebuild.schedule())
145
+ .on("change", () => adminRebuild.schedule())
146
+ .on("unlink", () => adminRebuild.schedule());
119
147
  // Graceful shutdown
120
148
  process.on("SIGINT", async () => {
121
- if (yalcPushTimeout)
122
- clearTimeout(yalcPushTimeout);
123
- if (routeTypesTimeout)
124
- clearTimeout(routeTypesTimeout);
125
- if (queryTypesTimeout)
126
- clearTimeout(queryTypesTimeout);
127
- if (adminRebuildTimeout)
128
- clearTimeout(adminRebuildTimeout);
149
+ yalcPush.dispose();
150
+ routeTypesRegen.dispose();
151
+ queryTypesRegen.dispose();
152
+ adminRebuild.dispose();
129
153
  await Promise.all([transpiler.close(), adminWatcher.close()]);
130
154
  process.exit(0);
131
155
  });
@@ -1 +1 @@
1
- {"version":3,"file":"develop.js","sourceRoot":"","sources":["../../../src/commands/plugin/develop.ts"],"names":[],"mappings":";;;;;AAcA,gCA8HC;AA5ID,gEAGuC;AACvC,sDAAkD;AAClD,+BAA0B;AAC1B,wDAA+B;AAC/B,iDAAmD;AAEnD,MAAM,yBAAyB,GAAG,GAAG,CAAA;AACrC,MAAM,uBAAuB,GAAG,GAAG,CAAA;AACnC,MAAM,uBAAuB,GAAG,GAAG,CAAA;AACnC,MAAM,qBAAqB,GAAG,GAAG,CAAA;AAElB,KAAK,UAAU,aAAa,CAAC,EAC1C,SAAS,GAGV;IACC,MAAM,QAAQ,GAAG,IAAI,sBAAQ,CAAC,SAAS,EAAE,eAAM,CAAC,CAAA;IAChD,MAAM,YAAY,GAAG,MAAM,QAAQ,CAAC,gBAAgB,EAAE,CAAA;IACtD,IAAI,CAAC,YAAY,EAAE,CAAC;QAClB,OAAM;IACR,CAAC;IAED,wBAAwB;IACxB,MAAM,QAAQ,CAAC,kBAAkB,CAAC,YAAY,CAAC,CAAA;IAE/C,MAAM,OAAO,GAAG,MAAM,MAAM,CAAC,wBAAwB,CAAC,CAAA;IACtD,MAAM,UAAU,GAAG,MAAM,QAAQ,CAAC,0BAA0B,CAAC,OAAO,CAAC,CAAA;IACrE,IAAI,CAAC,UAAU,EAAE,CAAC;QAChB,eAAM,CAAC,IAAI,CACT,yEAAyE,CAC1E,CAAA;IACH,CAAC;IAED,0EAA0E;IAC1E,mEAAmE;IACnE,IAAI,eAAe,GAAyC,IAAI,CAAA;IAChE,MAAM,gBAAgB,GAAG,GAAG,EAAE;QAC5B,IAAI,eAAe;YAAE,YAAY,CAAC,eAAe,CAAC,CAAA;QAClD,eAAe,GAAG,UAAU,CAAC,KAAK,IAAI,EAAE;YACtC,eAAe,GAAG,IAAI,CAAA;YACtB,IAAI,CAAC;gBACH,MAAM,MAAM,GAAG,MAAM,IAAA,iCAAkB,EAAC,SAAS,CAAC,CAAA;gBAClD,IAAI,MAAM,GAAG,CAAC,EAAE,CAAC;oBACf,eAAM,CAAC,IAAI,CACT,8BAA8B,MAAM,OAAO,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CACnE,CAAA;gBACH,CAAC;YACH,CAAC;YAAC,OAAO,GAAQ,EAAE,CAAC;gBAClB,eAAM,CAAC,IAAI,CAAC,iCAAiC,GAAG,CAAC,OAAO,EAAE,CAAC,CAAA;YAC7D,CAAC;QACH,CAAC,EAAE,qBAAqB,CAAC,CAAA;IAC3B,CAAC,CAAA;IAED,+DAA+D;IAC/D,mEAAmE;IACnE,gBAAgB,EAAE,CAAA;IAElB,kEAAkE;IAClE,IAAI,iBAAiB,GAAyC,IAAI,CAAA;IAClE,MAAM,uBAAuB,GAAG,GAAG,EAAE;QACnC,IAAI,iBAAiB;YAAE,YAAY,CAAC,iBAAiB,CAAC,CAAA;QACtD,iBAAiB,GAAG,UAAU,CAAC,KAAK,IAAI,EAAE;YACxC,iBAAiB,GAAG,IAAI,CAAA;YACxB,MAAM,QAAQ,CAAC,wBAAwB,EAAE,CAAA;YACzC,eAAM,CAAC,IAAI,CAAC,sCAAsC,CAAC,CAAA;QACrD,CAAC,EAAE,uBAAuB,CAAC,CAAA;IAC7B,CAAC,CAAA;IAED,qEAAqE;IACrE,IAAI,iBAAiB,GAAyC,IAAI,CAAA;IAClE,MAAM,uBAAuB,GAAG,GAAG,EAAE;QACnC,IAAI,iBAAiB;YAAE,YAAY,CAAC,iBAAiB,CAAC,CAAA;QACtD,iBAAiB,GAAG,UAAU,CAAC,KAAK,IAAI,EAAE;YACxC,iBAAiB,GAAG,IAAI,CAAA;YACxB,MAAM,QAAQ,CAAC,wBAAwB,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAA;YAC5D,eAAM,CAAC,IAAI,CAAC,sCAAsC,CAAC,CAAA;QACrD,CAAC,EAAE,uBAAuB,CAAC,CAAA;IAC7B,CAAC,CAAA;IAED,qEAAqE;IACrE,MAAM,UAAU,GAAG,IAAI,mCAAqB,CAAC;QAC3C,UAAU,EAAE,SAAS;QACrB,MAAM,EAAN,eAAM;QACN,YAAY,EAAE,CAAC,kBAAkB,EAAE,EAAE;YACnC,iDAAiD;YACjD,IAAI,kBAAkB,CAAC,QAAQ,CAAC,GAAG,UAAG,MAAM,UAAG,EAAE,CAAC,EAAE,CAAC;gBACnD,uBAAuB,EAAE,CAAA;YAC3B,CAAC;YACD,oDAAoD;YACpD,IAAI,kBAAkB,CAAC,QAAQ,CAAC,GAAG,UAAG,UAAU,UAAG,EAAE,CAAC,EAAE,CAAC;gBACvD,uBAAuB,EAAE,CAAA;YAC3B,CAAC;YACD,gBAAgB,EAAE,CAAA;QACpB,CAAC;KACF,CAAC,CAAA;IACF,MAAM,UAAU,CAAC,gBAAgB,EAAE,CAAA;IACnC,MAAM,UAAU,CAAC,KAAK,EAAE,CAAA;IAExB,qCAAqC;IACrC,IAAI,mBAAmB,GAAyC,IAAI,CAAA;IACpE,MAAM,oBAAoB,GAAG,GAAG,EAAE;QAChC,IAAI,mBAAmB,EAAE,CAAC;YACxB,YAAY,CAAC,mBAAmB,CAAC,CAAA;QACnC,CAAC;QACD,mBAAmB,GAAG,UAAU,CAAC,KAAK,IAAI,EAAE;YAC1C,mBAAmB,GAAG,IAAI,CAAA;YAC1B,MAAM,QAAQ,CAAC,0BAA0B,CAAC,OAAO,CAAC,CAAA;YAClD,gBAAgB,EAAE,CAAA;QACpB,CAAC,EAAE,yBAAyB,CAAC,CAAA;IAC/B,CAAC,CAAA;IAED,MAAM,YAAY,GAAG,kBAAQ;SAC1B,KAAK,CAAC,WAAW,EAAE;QAClB,GAAG,EAAE,SAAS;QACd,aAAa,EAAE,IAAI;QACnB,OAAO,EAAE;YACP,cAAc;YACd,MAAM;YACN,UAAU;YACV,gBAAgB;YAChB,mCAAmC;YACnC,4BAA4B;SAC7B;KACF,CAAC;SACD,EAAE,CAAC,KAAK,EAAE,oBAAoB,CAAC;SAC/B,EAAE,CAAC,QAAQ,EAAE,oBAAoB,CAAC;SAClC,EAAE,CAAC,QAAQ,EAAE,oBAAoB,CAAC,CAAA;IAErC,oBAAoB;IACpB,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,KAAK,IAAI,EAAE;QAC9B,IAAI,eAAe;YAAE,YAAY,CAAC,eAAe,CAAC,CAAA;QAClD,IAAI,iBAAiB;YAAE,YAAY,CAAC,iBAAiB,CAAC,CAAA;QACtD,IAAI,iBAAiB;YAAE,YAAY,CAAC,iBAAiB,CAAC,CAAA;QACtD,IAAI,mBAAmB;YAAE,YAAY,CAAC,mBAAmB,CAAC,CAAA;QAC1D,MAAM,OAAO,CAAC,GAAG,CAAC,CAAC,UAAU,CAAC,KAAK,EAAE,EAAE,YAAY,CAAC,KAAK,EAAE,CAAC,CAAC,CAAA;QAC7D,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;IACjB,CAAC,CAAC,CAAA;AACJ,CAAC"}
1
+ {"version":3,"file":"develop.js","sourceRoot":"","sources":["../../../src/commands/plugin/develop.ts"],"names":[],"mappings":"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"}
@@ -420,6 +420,8 @@ File: `src/admin/routes/custom/[id]/edit/page.tsx`
420
420
 
421
421
  **Critical structure**: Use `RouteDrawer.Form` wrapping `KeyboundForm` — this handles dirty form blocking on close and Cmd/Ctrl+Enter submit. The `KeyboundForm` wraps BOTH `RouteDrawer.Body` AND `RouteDrawer.Footer` so `type="submit"` works. Use `useRouteModal()` for `handleSuccess` to close the modal after success.
422
422
 
423
+ **IMPORTANT**: `useRouteModal()` must be called in a **child component** rendered inside `<RouteDrawer>`, not in the same component that renders it. The `RouteModalProvider` context is created inside `RouteDrawer`, so calling the hook in the parent component crashes with "useRouteModal must be used within a RouteModalProvider".
424
+
423
425
  ```tsx
424
426
  import { Button, RouteDrawer, Form, Heading, Input, Textarea, toast, KeyboundForm, useRouteModal } from "@acmekit/ui"
425
427
  import { useForm } from "react-hook-form"
@@ -434,39 +436,54 @@ const EditSchema = zod.object({
434
436
  description: zod.string().optional(),
435
437
  })
436
438
 
439
+ // Page component — renders RouteDrawer, delegates form to child
437
440
  const EditPostPage = () => {
438
441
  const { id } = useParams()
439
- const queryClient = useQueryClient()
440
- const { handleSuccess } = useRouteModal()
441
442
 
442
443
  const { data, isLoading, isError, error } = useQuery({
443
444
  queryKey: ["posts", id],
444
445
  queryFn: () => sdk.admin.fetch("/posts/:id", { method: "GET", params: { id: id! } }),
445
446
  })
446
447
 
447
- // Throw query errors to error boundary
448
448
  if (isError) {
449
449
  throw error
450
450
  }
451
451
 
452
+ return (
453
+ <RouteDrawer>
454
+ <RouteDrawer.Header>
455
+ <Heading>Edit Post</Heading>
456
+ </RouteDrawer.Header>
457
+ {!isLoading && data?.post && (
458
+ <EditPostForm post={data.post} />
459
+ )}
460
+ </RouteDrawer>
461
+ )
462
+ }
463
+
464
+ // Form component — useRouteModal() is valid here (inside RouteDrawer)
465
+ const EditPostForm = ({ post }: { post: { id: string; title: string; description?: string } }) => {
466
+ const queryClient = useQueryClient()
467
+ const { handleSuccess } = useRouteModal()
468
+
452
469
  const form = useForm<zod.infer<typeof EditSchema>>({
453
470
  defaultValues: {
454
- title: data?.post?.title ?? "",
455
- description: data?.post?.description ?? "",
471
+ title: post.title ?? "",
472
+ description: post.description ?? "",
456
473
  },
457
474
  resolver: zodResolver(EditSchema),
458
475
  })
459
476
 
460
477
  const { mutateAsync, isPending } = useMutation({
461
478
  mutationFn: (payload: zod.infer<typeof EditSchema>) =>
462
- sdk.admin.fetch("/posts/:id", { method: "POST", params: { id: id! }, body: payload }),
479
+ sdk.admin.fetch("/posts/:id", { method: "POST", params: { id: post.id }, body: payload }),
463
480
  })
464
481
 
465
482
  const handleSubmit = form.handleSubmit(async (values) => {
466
483
  await mutateAsync(values, {
467
484
  onSuccess: () => {
468
485
  queryClient.invalidateQueries({ queryKey: ["posts"] })
469
- queryClient.invalidateQueries({ queryKey: ["posts", id] })
486
+ queryClient.invalidateQueries({ queryKey: ["posts", post.id] })
470
487
  toast.success("Post updated")
471
488
  handleSuccess()
472
489
  },
@@ -475,57 +492,50 @@ const EditPostPage = () => {
475
492
  })
476
493
 
477
494
  return (
478
- <RouteDrawer>
479
- <RouteDrawer.Header>
480
- <Heading>Edit Post</Heading>
481
- </RouteDrawer.Header>
482
- {!isLoading && data?.post && (
483
- <RouteDrawer.Form form={form}>
484
- <KeyboundForm onSubmit={handleSubmit} className="flex flex-1 flex-col">
485
- <RouteDrawer.Body>
486
- <div className="flex flex-col gap-y-4">
487
- <Form.Field
488
- control={form.control}
489
- name="title"
490
- render={({ field }) => (
491
- <Form.Item>
492
- <Form.Label>Title</Form.Label>
493
- <Form.Control>
494
- <Input autoComplete="off" {...field} />
495
- </Form.Control>
496
- <Form.ErrorMessage />
497
- </Form.Item>
498
- )}
499
- />
500
- <Form.Field
501
- control={form.control}
502
- name="description"
503
- render={({ field }) => (
504
- <Form.Item>
505
- <Form.Label optional>Description</Form.Label>
506
- <Form.Control>
507
- <Textarea {...field} />
508
- </Form.Control>
509
- <Form.ErrorMessage />
510
- </Form.Item>
511
- )}
512
- />
513
- </div>
514
- </RouteDrawer.Body>
515
- <RouteDrawer.Footer>
516
- <div className="flex items-center justify-end gap-x-2">
517
- <RouteDrawer.Close asChild>
518
- <Button size="small" variant="secondary">Cancel</Button>
519
- </RouteDrawer.Close>
520
- <Button size="small" type="submit" isLoading={isPending}>
521
- Save
522
- </Button>
523
- </div>
524
- </RouteDrawer.Footer>
525
- </KeyboundForm>
526
- </RouteDrawer.Form>
527
- )}
528
- </RouteDrawer>
495
+ <RouteDrawer.Form form={form}>
496
+ <KeyboundForm onSubmit={handleSubmit} className="flex flex-1 flex-col">
497
+ <RouteDrawer.Body>
498
+ <div className="flex flex-col gap-y-4">
499
+ <Form.Field
500
+ control={form.control}
501
+ name="title"
502
+ render={({ field }) => (
503
+ <Form.Item>
504
+ <Form.Label>Title</Form.Label>
505
+ <Form.Control>
506
+ <Input autoComplete="off" {...field} />
507
+ </Form.Control>
508
+ <Form.ErrorMessage />
509
+ </Form.Item>
510
+ )}
511
+ />
512
+ <Form.Field
513
+ control={form.control}
514
+ name="description"
515
+ render={({ field }) => (
516
+ <Form.Item>
517
+ <Form.Label optional>Description</Form.Label>
518
+ <Form.Control>
519
+ <Textarea {...field} />
520
+ </Form.Control>
521
+ <Form.ErrorMessage />
522
+ </Form.Item>
523
+ )}
524
+ />
525
+ </div>
526
+ </RouteDrawer.Body>
527
+ <RouteDrawer.Footer>
528
+ <div className="flex items-center justify-end gap-x-2">
529
+ <RouteDrawer.Close asChild>
530
+ <Button size="small" variant="secondary">Cancel</Button>
531
+ </RouteDrawer.Close>
532
+ <Button size="small" type="submit" isLoading={isPending}>
533
+ Save
534
+ </Button>
535
+ </div>
536
+ </RouteDrawer.Footer>
537
+ </KeyboundForm>
538
+ </RouteDrawer.Form>
529
539
  )
530
540
  }
531
541
 
@@ -553,6 +563,8 @@ File: `src/admin/routes/custom/create/page.tsx`
553
563
 
554
564
  **Critical structure**: Use `RouteFocusModal.Form` wrapping `KeyboundForm` — this handles dirty form blocking on close and Cmd/Ctrl+Enter submit. The `KeyboundForm` wraps Header + Body + Footer. Use `useRouteModal()` for `handleSuccess` to close the modal after success.
555
565
 
566
+ **IMPORTANT**: `useRouteModal()` must be called in a **child component** rendered inside `<RouteFocusModal>`, not in the same component that renders it. The `RouteModalProvider` context is created inside `RouteFocusModal`, so calling the hook in the parent component crashes with "useRouteModal must be used within a RouteModalProvider".
567
+
556
568
  ```tsx
557
569
  import { Button, RouteFocusModal, Form, Heading, Input, Text, Textarea, toast, KeyboundForm, useRouteModal } from "@acmekit/ui"
558
570
  import { useForm } from "react-hook-form"
@@ -566,7 +578,17 @@ const CreateSchema = zod.object({
566
578
  description: zod.string().optional(),
567
579
  })
568
580
 
581
+ // Page component — renders RouteFocusModal, delegates form to child
569
582
  const CreatePostPage = () => {
583
+ return (
584
+ <RouteFocusModal>
585
+ <CreatePostForm />
586
+ </RouteFocusModal>
587
+ )
588
+ }
589
+
590
+ // Form component — useRouteModal() is valid here (inside RouteFocusModal)
591
+ const CreatePostForm = () => {
570
592
  const queryClient = useQueryClient()
571
593
  const { handleSuccess } = useRouteModal()
572
594
 
@@ -592,61 +614,59 @@ const CreatePostPage = () => {
592
614
  })
593
615
 
594
616
  return (
595
- <RouteFocusModal>
596
- <RouteFocusModal.Form form={form}>
597
- <KeyboundForm onSubmit={handleSubmit} className="flex flex-1 flex-col overflow-hidden">
598
- <RouteFocusModal.Header />
599
- <RouteFocusModal.Body className="flex flex-1 flex-col items-center overflow-y-auto py-16">
600
- <div className="flex w-full max-w-[720px] flex-col gap-y-8">
601
- <div>
602
- <Heading>Create Post</Heading>
603
- <Text size="small" className="text-ui-fg-subtle">
604
- Add a new blog post.
605
- </Text>
606
- </div>
607
- <div className="grid grid-cols-1 gap-4 md:grid-cols-2">
608
- <Form.Field
609
- control={form.control}
610
- name="title"
611
- render={({ field }) => (
612
- <Form.Item>
613
- <Form.Label>Title</Form.Label>
614
- <Form.Control>
615
- <Input autoComplete="off" {...field} />
616
- </Form.Control>
617
- <Form.ErrorMessage />
618
- </Form.Item>
619
- )}
620
- />
621
- <Form.Field
622
- control={form.control}
623
- name="description"
624
- render={({ field }) => (
625
- <Form.Item>
626
- <Form.Label optional>Description</Form.Label>
627
- <Form.Control>
628
- <Textarea {...field} />
629
- </Form.Control>
630
- <Form.ErrorMessage />
631
- </Form.Item>
632
- )}
633
- />
634
- </div>
617
+ <RouteFocusModal.Form form={form}>
618
+ <KeyboundForm onSubmit={handleSubmit} className="flex flex-1 flex-col overflow-hidden">
619
+ <RouteFocusModal.Header />
620
+ <RouteFocusModal.Body className="flex flex-1 flex-col items-center overflow-y-auto py-16">
621
+ <div className="flex w-full max-w-[720px] flex-col gap-y-8">
622
+ <div>
623
+ <Heading>Create Post</Heading>
624
+ <Text size="small" className="text-ui-fg-subtle">
625
+ Add a new blog post.
626
+ </Text>
635
627
  </div>
636
- </RouteFocusModal.Body>
637
- <RouteFocusModal.Footer>
638
- <div className="flex items-center justify-end gap-x-2">
639
- <RouteFocusModal.Close asChild>
640
- <Button size="small" variant="secondary">Cancel</Button>
641
- </RouteFocusModal.Close>
642
- <Button size="small" type="submit" isLoading={isPending}>
643
- Create
644
- </Button>
628
+ <div className="grid grid-cols-1 gap-4 md:grid-cols-2">
629
+ <Form.Field
630
+ control={form.control}
631
+ name="title"
632
+ render={({ field }) => (
633
+ <Form.Item>
634
+ <Form.Label>Title</Form.Label>
635
+ <Form.Control>
636
+ <Input autoComplete="off" {...field} />
637
+ </Form.Control>
638
+ <Form.ErrorMessage />
639
+ </Form.Item>
640
+ )}
641
+ />
642
+ <Form.Field
643
+ control={form.control}
644
+ name="description"
645
+ render={({ field }) => (
646
+ <Form.Item>
647
+ <Form.Label optional>Description</Form.Label>
648
+ <Form.Control>
649
+ <Textarea {...field} />
650
+ </Form.Control>
651
+ <Form.ErrorMessage />
652
+ </Form.Item>
653
+ )}
654
+ />
645
655
  </div>
646
- </RouteFocusModal.Footer>
647
- </KeyboundForm>
648
- </RouteFocusModal.Form>
649
- </RouteFocusModal>
656
+ </div>
657
+ </RouteFocusModal.Body>
658
+ <RouteFocusModal.Footer>
659
+ <div className="flex items-center justify-end gap-x-2">
660
+ <RouteFocusModal.Close asChild>
661
+ <Button size="small" variant="secondary">Cancel</Button>
662
+ </RouteFocusModal.Close>
663
+ <Button size="small" type="submit" isLoading={isPending}>
664
+ Create
665
+ </Button>
666
+ </div>
667
+ </RouteFocusModal.Footer>
668
+ </KeyboundForm>
669
+ </RouteFocusModal.Form>
650
670
  )
651
671
  }
652
672
 
@@ -313,6 +313,7 @@ const confirmed = await prompt({
313
313
  **Imports & dependencies:**
314
314
  - Importing `zod` from `"zod"` in admin code — use `import * as zod from "@acmekit/deps/zod"` to share the same zod instance as acmekit
315
315
  - Adding `react-hook-form` or `@hookform/resolvers` to `devDependencies` — they are provided by acmekit. In plugins: list them in `peerDependencies` (+ `devDependencies` mirror). In apps: they are available transitively, no extra dependency needed
316
+ - Installing `@hookform/resolvers@5.x` — this version requires Zod v4 (`zod/v4/core`), but AcmeKit ships Zod v3. Causes `Could not resolve "zod/v4/core"` at build time. Never install your own version — use what AcmeKit provides
316
317
 
317
318
  **Form context:**
318
319
  - Using `Form.Field`, `Form.Label`, `SwitchBox`, or any `Form.*` sub-component without a form provider — they call `useFormContext()` and crash with "Cannot destructure property 'getFieldState' of 'useFormContext(...)' as it is null". `RouteDrawer.Form` / `RouteFocusModal.Form` provide this automatically; standalone pages must wrap with `<Form {...form}>` from `@acmekit/ui`
@@ -326,6 +327,7 @@ const confirmed = await prompt({
326
327
  - Using `onClick={handleSubmit}` on submit button — use `type="submit"` inside a `KeyboundForm` tag
327
328
  - Missing `className="flex flex-1 flex-col"` on the `KeyboundForm` tag (breaks layout)
328
329
  - Missing `overflow-hidden` on RouteFocusModal's `KeyboundForm` tag
330
+ - Calling `useRouteModal()` in the same component that renders `RouteFocusModal`/`RouteDrawer` — the hook must be in a **child component** rendered inside the modal/drawer (the provider mounts inside it)
329
331
  - Using `navigate("..")` to close overlays instead of `handleSuccess()` from `useRouteModal()`
330
332
  - Putting footer buttons directly in Footer — wrap in `<div className="flex items-center justify-end gap-x-2">`
331
333
  - Using grid layout in Drawer forms — drawers are narrow, use single column `flex flex-col gap-y-4`