@gtkx/mcp 1.6.0 → 2.0.0-beta.10
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.
- package/README.md +47 -44
- package/dist/app-router.d.ts +6 -2
- package/dist/app-router.d.ts.map +1 -1
- package/dist/app-router.js +56 -19
- package/dist/app-router.js.map +1 -1
- package/dist/internal.d.ts +2 -1
- package/dist/internal.d.ts.map +1 -1
- package/dist/internal.js +2 -1
- package/dist/internal.js.map +1 -1
- package/dist/protocol/schemas.d.ts +1 -2
- package/dist/protocol/schemas.d.ts.map +1 -1
- package/dist/protocol/schemas.js +1 -7
- package/dist/protocol/schemas.js.map +1 -1
- package/dist/reference.d.ts.map +1 -1
- package/dist/reference.js +25 -46
- package/dist/reference.js.map +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +92 -44
- package/dist/server.js.map +1 -1
- package/dist/socket-path.d.ts +11 -0
- package/dist/socket-path.d.ts.map +1 -0
- package/dist/socket-path.js +74 -0
- package/dist/socket-path.js.map +1 -0
- package/dist/socket-server.d.ts +3 -1
- package/dist/socket-server.d.ts.map +1 -1
- package/dist/socket-server.js +48 -32
- package/dist/socket-server.js.map +1 -1
- package/package.json +15 -9
- package/src/app-router.ts +77 -22
- package/src/internal.ts +4 -1
- package/src/protocol/schemas.ts +0 -9
- package/src/reference.ts +27 -53
- package/src/server.ts +114 -46
- package/src/socket-path.ts +103 -0
- package/src/socket-server.ts +61 -37
package/src/app-router.ts
CHANGED
|
@@ -16,6 +16,7 @@ import { type AppInfo, RegisterParamsSchema } from "./protocol/schemas.js";
|
|
|
16
16
|
|
|
17
17
|
type AppRegisteredEvent = CustomEvent<AppInfo>;
|
|
18
18
|
type AppUnregisteredEvent = CustomEvent<string>;
|
|
19
|
+
type PendingAppWait = { reject: (error: Error) => void };
|
|
19
20
|
|
|
20
21
|
type RegisteredApp = {
|
|
21
22
|
info: AppInfo;
|
|
@@ -24,6 +25,14 @@ type RegisteredApp = {
|
|
|
24
25
|
|
|
25
26
|
const DEFAULT_REQUEST_TIMEOUT_MS = 30_000;
|
|
26
27
|
|
|
28
|
+
const routerStoppedError = (): Error => new Error("GTKX MCP server stopped while waiting for an application");
|
|
29
|
+
|
|
30
|
+
const appWaitTimeoutError = (timeout: number): Error =>
|
|
31
|
+
new Error(
|
|
32
|
+
`Timeout waiting for app registration after ${String(timeout)}ms. ` +
|
|
33
|
+
"Make sure your GTKX app is running with 'gtkx dev'.",
|
|
34
|
+
);
|
|
35
|
+
|
|
27
36
|
function appRegisteredEvent(info: AppInfo): AppRegisteredEvent {
|
|
28
37
|
return new CustomEvent("appRegistered", { detail: info });
|
|
29
38
|
}
|
|
@@ -43,6 +52,10 @@ class AppRouter extends EventTarget {
|
|
|
43
52
|
|
|
44
53
|
private connections: AppConnections;
|
|
45
54
|
|
|
55
|
+
private pendingAppWaits: Set<PendingAppWait> = new Set();
|
|
56
|
+
|
|
57
|
+
private isDisposed = false;
|
|
58
|
+
|
|
46
59
|
constructor(connections: AppConnections, options: { requestTimeout?: number } = {}) {
|
|
47
60
|
super();
|
|
48
61
|
this.connections = connections;
|
|
@@ -69,6 +82,10 @@ class AppRouter extends EventTarget {
|
|
|
69
82
|
}
|
|
70
83
|
|
|
71
84
|
private handleRequest(connection: ProtocolConnection, request: JSONRPCRequest): Promise<Result> {
|
|
85
|
+
if (this.isDisposed) {
|
|
86
|
+
return Promise.reject(routerStoppedError());
|
|
87
|
+
}
|
|
88
|
+
|
|
72
89
|
if (request.method === "app.register") {
|
|
73
90
|
return Promise.resolve(this.handleRegister(connection, request));
|
|
74
91
|
}
|
|
@@ -134,6 +151,40 @@ class AppRouter extends EventTarget {
|
|
|
134
151
|
this.dispatchEvent(appUnregisteredEvent(applicationId));
|
|
135
152
|
}
|
|
136
153
|
|
|
154
|
+
private async waitForRegistration(applicationId: string | undefined, timeout: number): Promise<AppInfo> {
|
|
155
|
+
const controller = new AbortController();
|
|
156
|
+
const { promise, reject, resolve } = Promise.withResolvers<AppInfo>();
|
|
157
|
+
const rejectWait = (error: Error): void => {
|
|
158
|
+
controller.abort();
|
|
159
|
+
reject(error);
|
|
160
|
+
};
|
|
161
|
+
const pendingWait: PendingAppWait = { reject: rejectWait };
|
|
162
|
+
const timeoutId = setTimeout(() => {
|
|
163
|
+
rejectWait(appWaitTimeoutError(timeout));
|
|
164
|
+
}, timeout);
|
|
165
|
+
|
|
166
|
+
this.addEventListener(
|
|
167
|
+
"appRegistered",
|
|
168
|
+
(event) => {
|
|
169
|
+
const info = (event as AppRegisteredEvent).detail;
|
|
170
|
+
|
|
171
|
+
if (applicationId === undefined || info.applicationId === applicationId) {
|
|
172
|
+
resolve(info);
|
|
173
|
+
}
|
|
174
|
+
},
|
|
175
|
+
{ signal: controller.signal },
|
|
176
|
+
);
|
|
177
|
+
this.pendingAppWaits.add(pendingWait);
|
|
178
|
+
|
|
179
|
+
try {
|
|
180
|
+
return await promise;
|
|
181
|
+
} finally {
|
|
182
|
+
clearTimeout(timeoutId);
|
|
183
|
+
controller.abort();
|
|
184
|
+
this.pendingAppWaits.delete(pendingWait);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
137
188
|
getApps(): AppInfo[] {
|
|
138
189
|
return this.apps.values().map((app) => app.info).toArray();
|
|
139
190
|
}
|
|
@@ -152,36 +203,40 @@ class AppRouter extends EventTarget {
|
|
|
152
203
|
return this.getDefaultApp()?.info.projectRoot;
|
|
153
204
|
}
|
|
154
205
|
|
|
155
|
-
|
|
156
|
-
|
|
206
|
+
dispose(): void {
|
|
207
|
+
if (this.isDisposed) {
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
this.isDisposed = true;
|
|
212
|
+
const error = routerStoppedError();
|
|
157
213
|
|
|
158
|
-
|
|
159
|
-
|
|
214
|
+
for (const pendingWait of this.pendingAppWaits) {
|
|
215
|
+
pendingWait.reject(error);
|
|
160
216
|
}
|
|
217
|
+
}
|
|
161
218
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
219
|
+
waitForApp(applicationId?: string, timeout: number = AppRouter.defaultWaitTimeout): Promise<AppInfo> {
|
|
220
|
+
if (this.isDisposed) {
|
|
221
|
+
return Promise.reject(routerStoppedError());
|
|
222
|
+
}
|
|
165
223
|
|
|
166
|
-
|
|
167
|
-
new Error(
|
|
168
|
-
`Timeout waiting for app registration after ${String(timeout)}ms. ` +
|
|
169
|
-
"Make sure your GTKX app is running with 'gtkx dev'.",
|
|
170
|
-
),
|
|
171
|
-
);
|
|
172
|
-
}, timeout);
|
|
224
|
+
const app = applicationId === undefined ? this.getDefaultApp() : this.apps.get(applicationId);
|
|
173
225
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
resolve((event as AppRegisteredEvent).detail);
|
|
178
|
-
};
|
|
226
|
+
if (app) {
|
|
227
|
+
return Promise.resolve(app.info);
|
|
228
|
+
}
|
|
179
229
|
|
|
180
|
-
|
|
181
|
-
});
|
|
230
|
+
return this.waitForRegistration(applicationId, timeout);
|
|
182
231
|
}
|
|
183
232
|
|
|
184
|
-
async sendToApp<T>(
|
|
233
|
+
async sendToApp<T>(
|
|
234
|
+
applicationId: string | undefined,
|
|
235
|
+
method: string,
|
|
236
|
+
params?: RequestParams,
|
|
237
|
+
waitTimeout?: number,
|
|
238
|
+
): Promise<T> {
|
|
239
|
+
await this.waitForApp(applicationId, waitTimeout);
|
|
185
240
|
const app = this.resolveTargetApp(applicationId);
|
|
186
241
|
|
|
187
242
|
try {
|
package/src/internal.ts
CHANGED
|
@@ -6,7 +6,6 @@ export {
|
|
|
6
6
|
widgetNotFoundError,
|
|
7
7
|
} from "./protocol/errors.js";
|
|
8
8
|
export {
|
|
9
|
-
DEFAULT_SOCKET_PATH,
|
|
10
9
|
DEFAULT_SUBTREE_DEPTH,
|
|
11
10
|
MAX_SUBTREE_WIDGETS,
|
|
12
11
|
type ParamsSchema,
|
|
@@ -16,5 +15,9 @@ export {
|
|
|
16
15
|
type ServerRequestParams,
|
|
17
16
|
ServerRequestParamsSchemas,
|
|
18
17
|
} from "./protocol/schemas.js";
|
|
18
|
+
export {
|
|
19
|
+
MCP_SOCKET_PATH_ENV,
|
|
20
|
+
resolveMcpSocketPath,
|
|
21
|
+
} from "./socket-path.js";
|
|
19
22
|
export { ProtocolConnection } from "./transport.js";
|
|
20
23
|
export type { JSONRPCRequest, Result } from "@modelcontextprotocol/sdk/types.js";
|
package/src/protocol/schemas.ts
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import { tmpdir } from "node:os";
|
|
2
|
-
import { join } from "node:path";
|
|
3
1
|
import { z } from "zod";
|
|
4
2
|
|
|
5
3
|
type SerializedWidget = {
|
|
@@ -130,12 +128,6 @@ const ServerRequestParamsSchemas: {
|
|
|
130
128
|
"widget.screenshot": screenshotParams,
|
|
131
129
|
};
|
|
132
130
|
|
|
133
|
-
const DEFAULT_SOCKET_PATH: string = join(getRuntimeDir(), "gtkx-mcp.sock");
|
|
134
|
-
|
|
135
|
-
function getRuntimeDir(): string {
|
|
136
|
-
return process.env.XDG_RUNTIME_DIR ?? tmpdir();
|
|
137
|
-
}
|
|
138
|
-
|
|
139
131
|
export {
|
|
140
132
|
DEFAULT_SUBTREE_DEPTH,
|
|
141
133
|
MAX_SUBTREE_WIDGETS,
|
|
@@ -148,7 +140,6 @@ export {
|
|
|
148
140
|
fireEventParams,
|
|
149
141
|
screenshotParams,
|
|
150
142
|
ServerRequestParamsSchemas,
|
|
151
|
-
DEFAULT_SOCKET_PATH,
|
|
152
143
|
type SerializedWidget,
|
|
153
144
|
type SerializedProperty,
|
|
154
145
|
type AppInfo,
|
package/src/reference.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { type ApiReference, type ApiSymbol, loadApiReference, resolveGirPath, resolveLibraries } from "@gtkx/codegen";
|
|
2
2
|
import { loadConfig } from "@gtkx/config";
|
|
3
|
-
import { resolveFuture } from "@gtkx/config/internal";
|
|
4
3
|
import { type McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
5
4
|
import { type CallToolResult, ErrorCode, McpError, type ReadResourceResult } from "@modelcontextprotocol/sdk/types.js";
|
|
6
5
|
import { existsSync, statSync } from "node:fs";
|
|
@@ -59,7 +58,7 @@ type ResourceServer = Pick<McpServer, "registerResource">;
|
|
|
59
58
|
|
|
60
59
|
const FRESHNESS_INTERVAL_MS = 2000;
|
|
61
60
|
const FAILURE_RETRY_MS = 5000;
|
|
62
|
-
const CONFIG_EXTENSIONS = ["ts", "mts", "
|
|
61
|
+
const CONFIG_EXTENSIONS = ["ts", "mts", "js", "mjs", "json"];
|
|
63
62
|
|
|
64
63
|
const PROJECT_SOURCE_LABELS: Record<ProjectSource, string> = {
|
|
65
64
|
argument: "requested with `projectRoot`",
|
|
@@ -80,8 +79,8 @@ const SYMBOL_KIND = z.enum([
|
|
|
80
79
|
]);
|
|
81
80
|
|
|
82
81
|
const SYMBOL_DESCRIPTION =
|
|
83
|
-
"Qualified symbol name (`
|
|
84
|
-
"or bare symbol name when unambiguous (`
|
|
82
|
+
"Qualified symbol name (`Adw.Toast`, `Gtk.Orientation`, `GLib.idleAdd`), JSX element name (`AdwToast`), " +
|
|
83
|
+
"or bare symbol name when unambiguous (`Toast`).";
|
|
85
84
|
|
|
86
85
|
const PROJECT_ROOT_DESCRIPTION =
|
|
87
86
|
"Directory of the GTKX project whose bindings to document, absolute or relative to the working directory. " +
|
|
@@ -97,13 +96,13 @@ const listApiShape = {
|
|
|
97
96
|
namespace: z
|
|
98
97
|
.string()
|
|
99
98
|
.optional()
|
|
100
|
-
.describe("Namespace to list (e.g. `
|
|
99
|
+
.describe("Namespace to list (e.g. `Adw`, `Gtk`, `Gio`). Omit for an overview of all namespaces."),
|
|
101
100
|
};
|
|
102
101
|
|
|
103
102
|
const searchApiShape = {
|
|
104
103
|
...projectRootShape,
|
|
105
104
|
query: z.string().describe("Case-insensitive substring of a symbol name, e.g. `headerbar` or `orientation`."),
|
|
106
|
-
namespace: z.string().optional().describe("Restrict matches to one namespace (e.g. `
|
|
105
|
+
namespace: z.string().optional().describe("Restrict matches to one namespace (e.g. `Adw`)."),
|
|
107
106
|
kind: SYMBOL_KIND.optional().describe("Restrict matches to one symbol kind."),
|
|
108
107
|
limit: z.number().int().min(1).optional().describe("Maximum number of results (default: 20)."),
|
|
109
108
|
};
|
|
@@ -193,17 +192,11 @@ const loadReference = async (requestedRoot: string): Promise<LoadedReference> =>
|
|
|
193
192
|
);
|
|
194
193
|
}
|
|
195
194
|
|
|
196
|
-
const
|
|
197
|
-
const libraries = resolveLibraries(config.libraries, girPath, future.isAdwaitaDefault);
|
|
195
|
+
const libraries = resolveLibraries(config.libraries);
|
|
198
196
|
|
|
199
197
|
const reference = loadApiReference({
|
|
200
198
|
libraries,
|
|
201
199
|
girPath,
|
|
202
|
-
isByteArrayTyped: future.isByteArrayTyped,
|
|
203
|
-
isValueUnwrapped: future.isValueUnwrapped,
|
|
204
|
-
isFinishTrimmed: future.isFinishTrimmed,
|
|
205
|
-
isInoutInPlace: future.isInoutInPlace,
|
|
206
|
-
isTreeShaken: future.isTreeShaken,
|
|
207
200
|
});
|
|
208
201
|
|
|
209
202
|
const watched = [watchFile(resolve(root, configFile)), ...reference.girFiles.map((file) => watchFile(file))];
|
|
@@ -211,17 +204,13 @@ const loadReference = async (requestedRoot: string): Promise<LoadedReference> =>
|
|
|
211
204
|
return { reference, root, watched };
|
|
212
205
|
};
|
|
213
206
|
|
|
214
|
-
const markFailed = async (entry: CacheEntry): Promise<void> => {
|
|
215
|
-
try {
|
|
216
|
-
await entry.pending;
|
|
217
|
-
} catch {
|
|
218
|
-
entry.failedAt = Date.now();
|
|
219
|
-
}
|
|
220
|
-
};
|
|
221
|
-
|
|
222
207
|
const startLoad = (cache: ReferenceCache, root: string): CacheEntry => {
|
|
223
208
|
const entry: CacheEntry = { pending: loadReference(root), verifiedAt: Date.now(), failedAt: undefined };
|
|
224
|
-
|
|
209
|
+
|
|
210
|
+
void entry.pending.catch(() => {
|
|
211
|
+
entry.failedAt = Date.now();
|
|
212
|
+
});
|
|
213
|
+
|
|
225
214
|
cache.set(root, entry);
|
|
226
215
|
|
|
227
216
|
return entry;
|
|
@@ -259,11 +248,9 @@ const currentReference = async (cache: ReferenceCache, root: string): Promise<Lo
|
|
|
259
248
|
return revalidate(cache, root, entry).pending;
|
|
260
249
|
};
|
|
261
250
|
|
|
262
|
-
const defaultWorkingDirectory = (): string => process.cwd();
|
|
263
|
-
|
|
264
251
|
const createReferenceProvider = (options: ReferenceProviderOptions): ReferenceProvider => {
|
|
265
252
|
const cache: ReferenceCache = new Map();
|
|
266
|
-
const getWorkingDirectory = options.getWorkingDirectory ??
|
|
253
|
+
const getWorkingDirectory = options.getWorkingDirectory ?? (() => process.cwd());
|
|
267
254
|
|
|
268
255
|
const resolve = (projectRoot?: string): ResolvedProject =>
|
|
269
256
|
resolveProject(getWorkingDirectory(), options.getAppRoot, projectRoot);
|
|
@@ -308,23 +295,12 @@ const scopedResult = async (
|
|
|
308
295
|
const formatCandidates = (candidates: ApiSymbol[]): string =>
|
|
309
296
|
candidates.map((candidate) => `- ${candidate.namespace}.${candidate.name} (${candidate.kind})`).join("\n");
|
|
310
297
|
|
|
311
|
-
const buildSearchOptions = (args: ToolArgs<typeof searchApiShape>): SearchOptions => {
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
if (args.kind !== undefined) {
|
|
319
|
-
options.kinds = [args.kind];
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
if (args.limit !== undefined) {
|
|
323
|
-
options.limit = args.limit;
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
return options;
|
|
327
|
-
};
|
|
298
|
+
const buildSearchOptions = (args: ToolArgs<typeof searchApiShape>): SearchOptions => ({
|
|
299
|
+
query: args.query,
|
|
300
|
+
...(args.namespace !== undefined && { namespace: args.namespace }),
|
|
301
|
+
...(args.kind !== undefined && { kinds: [args.kind] }),
|
|
302
|
+
...(args.limit !== undefined && { limit: args.limit }),
|
|
303
|
+
});
|
|
328
304
|
|
|
329
305
|
const listApiResult = (reference: ReferenceApi, namespace: string | undefined): CallToolResult => {
|
|
330
306
|
if (namespace === undefined) {
|
|
@@ -378,8 +354,9 @@ const listApiTool = (provider: ReferenceProvider): Tool =>
|
|
|
378
354
|
title: "List API reference",
|
|
379
355
|
kind: "readOnly",
|
|
380
356
|
description:
|
|
381
|
-
"List the project's generated
|
|
382
|
-
"
|
|
357
|
+
"List the project's generated bindings API (`@gtkx/gi` and `@gtkx/jsx`), including Adwaita, " +
|
|
358
|
+
"GTK4, and its declared GIR libraries. Without a namespace, returns every namespace with symbol counts; " +
|
|
359
|
+
"with a namespace, lists all of its symbols grouped by kind.",
|
|
383
360
|
inputSchema: listApiShape,
|
|
384
361
|
handler: ({ namespace, projectRoot }) =>
|
|
385
362
|
scopedResult(provider, projectRoot, (reference) => listApiResult(reference, namespace)),
|
|
@@ -391,7 +368,7 @@ const searchApiTool = (provider: ReferenceProvider): Tool =>
|
|
|
391
368
|
title: "Search API reference",
|
|
392
369
|
kind: "readOnly",
|
|
393
370
|
description:
|
|
394
|
-
"Search the project's generated
|
|
371
|
+
"Search the project's generated bindings API by symbol name. Returns matching symbols with " +
|
|
395
372
|
"their namespace, kind, and a one-line summary; fetch full pages with `gtkx_get_api_docs`.",
|
|
396
373
|
inputSchema: searchApiShape,
|
|
397
374
|
handler: (args) => scopedResult(provider, args.projectRoot, (reference) => searchApiResult(reference, args)),
|
|
@@ -403,7 +380,7 @@ const getApiDocsTool = (provider: ReferenceProvider): Tool =>
|
|
|
403
380
|
title: "Get API docs",
|
|
404
381
|
kind: "readOnly",
|
|
405
382
|
description:
|
|
406
|
-
"Get the full reference page for one symbol of the project's generated
|
|
383
|
+
"Get the full reference page for one symbol of the project's generated bindings: JSX elements " +
|
|
407
384
|
"(props, signals, methods) or `@gtkx/gi` classes, interfaces, records, enums, callbacks, aliases, " +
|
|
408
385
|
"functions, and constants.",
|
|
409
386
|
inputSchema: apiDocsShape,
|
|
@@ -431,9 +408,6 @@ const withLoadFallback = async <T>(load: () => Promise<T>, fallback: T): Promise
|
|
|
431
408
|
}
|
|
432
409
|
};
|
|
433
410
|
|
|
434
|
-
const namesStartingWith = (names: string[], value: string): string[] =>
|
|
435
|
-
names.filter((name) => name.toLowerCase().startsWith(value.toLowerCase()));
|
|
436
|
-
|
|
437
411
|
const completeNames = (
|
|
438
412
|
provider: ReferenceProvider,
|
|
439
413
|
value: string,
|
|
@@ -442,7 +416,7 @@ const completeNames = (
|
|
|
442
416
|
withLoadFallback(async () => {
|
|
443
417
|
const { reference } = await provider.get();
|
|
444
418
|
|
|
445
|
-
return
|
|
419
|
+
return collect(reference).filter((name) => name.toLowerCase().startsWith(value.toLowerCase()));
|
|
446
420
|
}, []);
|
|
447
421
|
|
|
448
422
|
const namespaceCompleter =
|
|
@@ -488,7 +462,7 @@ const registerIndexResource = (server: ResourceServer, provider: ReferenceProvid
|
|
|
488
462
|
"gtkx://reference/index",
|
|
489
463
|
{
|
|
490
464
|
title: "GTKX API reference index",
|
|
491
|
-
description: "Namespaces of the project's generated
|
|
465
|
+
description: "Namespaces of the project's generated bindings, with symbol and JSX element counts.",
|
|
492
466
|
mimeType: "text/markdown",
|
|
493
467
|
},
|
|
494
468
|
async (uri) => {
|
|
@@ -524,7 +498,7 @@ const registerNamespaceResource = (server: ResourceServer, provider: ReferencePr
|
|
|
524
498
|
}),
|
|
525
499
|
{
|
|
526
500
|
title: "GTKX namespace reference",
|
|
527
|
-
description: "All symbols of one namespace of the project's generated
|
|
501
|
+
description: "All symbols of one namespace of the project's generated bindings, grouped by kind.",
|
|
528
502
|
mimeType: "text/markdown",
|
|
529
503
|
},
|
|
530
504
|
async (uri, variables) => {
|
|
@@ -555,7 +529,7 @@ const registerSymbolResource = (server: ResourceServer, provider: ReferenceProvi
|
|
|
555
529
|
{
|
|
556
530
|
title: "GTKX symbol reference",
|
|
557
531
|
description:
|
|
558
|
-
"Reference page for one symbol of the project's generated
|
|
532
|
+
"Reference page for one symbol of the project's generated bindings: a JSX element or a " +
|
|
559
533
|
"class, interface, record, enum, callback, alias, function, or constant.",
|
|
560
534
|
mimeType: "text/markdown",
|
|
561
535
|
},
|