@grampro/kit 2.7.2 → 2.7.3
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/.gbs/skills/gbs-components/SKILL.md +233 -209
- package/.gbs/skills/gbs-components/references/data-grid.md +9 -0
- package/.gbs/skills/gbs-components/references/forms.md +12 -0
- package/.gbs/skills/gbs-components/references/install.md +14 -3
- package/CHANGELOG.md +963 -861
- package/README.md +11 -3
- package/index.cjs +108 -123
- package/package.json +1 -1
- package/source/components/accordion/passport.json +1 -1
- package/source/components/alert/passport.json +1 -1
- package/source/components/avatar/passport.json +1 -1
- package/source/components/badge/passport.json +1 -1
- package/source/components/breadcrumb/passport.json +1 -1
- package/source/components/button/passport.json +1 -1
- package/source/components/card/passport.json +1 -1
- package/source/components/checkbox/passport.json +2 -2
- package/source/components/checkbox/passport.manual.json +1 -1
- package/source/components/checkbox/react/Checkbox.tsx +14 -0
- package/source/components/combobox/passport.json +3 -2
- package/source/components/combobox/passport.manual.json +1 -1
- package/source/components/combobox/react/MultiSelect.tsx +13 -0
- package/source/components/combobox/react/Select.tsx +13 -0
- package/source/components/combobox/react/surface.ts +70 -0
- package/source/components/data-grid/passport.json +6 -6
- package/source/components/data-grid/react/DataGrid.tsx +353 -351
- package/source/components/date-picker/passport.json +3 -2
- package/source/components/date-picker/passport.manual.json +1 -1
- package/source/components/date-picker/react/DatePicker.tsx +16 -2
- package/source/components/date-picker/react/DateRangePicker.tsx +16 -2
- package/source/components/date-picker/react/surface.ts +68 -0
- package/source/components/dialog/passport.json +1 -1
- package/source/components/file-uploader/passport.json +1 -1
- package/source/components/input/passport.json +2 -2
- package/source/components/input/passport.manual.json +1 -1
- package/source/components/input/react/Input.tsx +21 -0
- package/source/components/menu/passport.json +1 -1
- package/source/components/modal/passport.json +1 -1
- package/source/components/number-input/passport.json +2 -2
- package/source/components/number-input/passport.manual.json +1 -1
- package/source/components/number-input/react/NumberInput.tsx +30 -0
- package/source/components/popover/passport.json +1 -1
- package/source/components/progress/passport.json +1 -1
- package/source/components/radio-group/passport.json +2 -2
- package/source/components/radio-group/passport.manual.json +1 -1
- package/source/components/radio-group/react/RadioGroup.tsx +33 -0
- package/source/components/shared/core/agent/field-executors.ts +173 -137
- package/source/components/shared/core/agent/index.ts +21 -0
- package/source/components/shared/core/agent/surface.ts +437 -0
- package/source/components/shared/core/agent/webmcp.ts +332 -328
- package/source/components/shared/core/agent/write.ts +62 -58
- package/source/components/shared/index.ts +7 -0
- package/source/components/shared/react/AgentSurface.tsx +145 -0
- package/source/components/shared/react/agentSurfaceContext.ts +92 -0
- package/source/components/shared/react/useFieldAgent.ts +62 -88
- package/source/components/shared/version.json +4 -4
- package/source/components/shared/version.ts +6 -6
- package/source/components/skeleton/passport.json +1 -1
- package/source/components/spinner/passport.json +1 -1
- package/source/components/switch/passport.json +2 -2
- package/source/components/switch/passport.manual.json +1 -1
- package/source/components/switch/react/Switch.tsx +16 -0
- package/source/components/tabs/passport.json +1 -1
- package/source/components/textarea/passport.json +2 -2
- package/source/components/textarea/passport.manual.json +1 -1
- package/source/components/textarea/react/Textarea.tsx +16 -0
- package/source/components/toaster/passport.json +1 -1
- package/source/components/tooltip/passport.json +1 -1
- package/tools/doctor/baseline.cjs +5 -4
- package/tools/mcp/server.cjs +13 -1
- package/tools/passport/extract.cjs +20 -65
- package/tools/passport/hash.cjs +18 -1
- package/tools/passport/index.cjs +4 -2
- package/tools/passport/typescript.cjs +220 -0
- package/tools/prompt.cjs +205 -0
- package/tools/ui.cjs +67 -0
package/README.md
CHANGED
|
@@ -14,6 +14,9 @@ Figma design.
|
|
|
14
14
|
npx @grampro/kit add DataGrid
|
|
15
15
|
```
|
|
16
16
|
|
|
17
|
+
Or `npx @grampro/kit add` with no names, to pick from the list: type to search,
|
|
18
|
+
space to select, enter to install.
|
|
19
|
+
|
|
17
20
|
The source lands in `component-lib/`, or `src/component-lib/` in a project
|
|
18
21
|
with a `src/` folder; `--dest` puts it anywhere you like, and later installs
|
|
19
22
|
follow the first. There is no runtime package to depend on, and nothing to
|
|
@@ -218,6 +221,10 @@ version. What is built and tested:
|
|
|
218
221
|
application supplies. No model is bundled, downloaded or named.
|
|
219
222
|
- **WebMCP** — one tool, `operate_grid`, exposing the same validated boundary to
|
|
220
223
|
a browser agent. Needs no model at all.
|
|
224
|
+
- **`<AgentSurface>`** — wrap a page and every field and grid in it describes
|
|
225
|
+
itself to a browser agent, from its own props, and is operated through its own
|
|
226
|
+
validator. One WebMCP tool, `operate_page`, for the whole page; no tool
|
|
227
|
+
definitions to write
|
|
221
228
|
- A model evaluation harness, with the measurements that justify both
|
|
222
229
|
|
|
223
230
|
Every producer — a model, a browser agent, a form — proposes. The validator
|
|
@@ -227,9 +234,10 @@ it is why no AI path gets a second route into the component.
|
|
|
227
234
|
Nine of the 27 are agent-operable. The executor abstraction was the open
|
|
228
235
|
question — a grid is driven through an imperative API, and a field is not — and
|
|
229
236
|
it is settled: Combobox and DatePicker are driven through their handles,
|
|
230
|
-
Input, Textarea,
|
|
231
|
-
|
|
232
|
-
|
|
237
|
+
Input, Textarea, Checkbox and Switch through the element their ref already
|
|
238
|
+
hands back, and NumberInput and RadioGroup, inside an `<AgentSurface>`, through
|
|
239
|
+
their own commit path — a number is not text, and a group is not one radio.
|
|
240
|
+
Neither the contract, the schemas, the validator nor the refusals know which.
|
|
233
241
|
|
|
234
242
|
Still open: the components whose value is in what they contain rather than what
|
|
235
243
|
they hold — Menu, Tabs, Accordion, Modal, FileUploader — and the question of
|
package/index.cjs
CHANGED
|
@@ -24,7 +24,7 @@ const path = require("path");
|
|
|
24
24
|
const crypto = require("crypto");
|
|
25
25
|
const yargs = require("yargs/yargs");
|
|
26
26
|
const { hideBin } = require("yargs/helpers");
|
|
27
|
-
const
|
|
27
|
+
const ui = require("./tools/ui.cjs");
|
|
28
28
|
|
|
29
29
|
// Configuration
|
|
30
30
|
const CONFIG = {
|
|
@@ -391,7 +391,7 @@ const copyCommonFiles = async (destPath) => {
|
|
|
391
391
|
const src = path.join(LEGACY_SOURCE_PATH, ...file.src);
|
|
392
392
|
const dest = path.join(destPath, file.dest);
|
|
393
393
|
await fs.copy(src, dest, { overwrite: true });
|
|
394
|
-
console.log(
|
|
394
|
+
console.log(`${ui.check()} ${file.dest} copied successfully`);
|
|
395
395
|
}
|
|
396
396
|
};
|
|
397
397
|
|
|
@@ -467,7 +467,7 @@ const installShared = async (destPath, { force = false } = {}) => {
|
|
|
467
467
|
|
|
468
468
|
if (order > 0) {
|
|
469
469
|
console.error(
|
|
470
|
-
`\n
|
|
470
|
+
`\n${ui.cross()} This project has shared v${installedVersion}, newer than the v${srcVersion} in this installer.`,
|
|
471
471
|
);
|
|
472
472
|
console.error(
|
|
473
473
|
" Installing it would downgrade the folder and break the components already here.",
|
|
@@ -478,11 +478,17 @@ const installShared = async (destPath, { force = false } = {}) => {
|
|
|
478
478
|
process.exit(1);
|
|
479
479
|
}
|
|
480
480
|
|
|
481
|
-
|
|
481
|
+
// The same version is only "already installed" when it is the same files:
|
|
482
|
+
// a release that changed shared/ without bumping it must still reach the
|
|
483
|
+
// project, or the components it brings import files that are not there.
|
|
484
|
+
const sameFiles = listFiles(src).every(
|
|
485
|
+
(file) => manifest.files[file] === sha256(path.join(src, file)),
|
|
486
|
+
);
|
|
487
|
+
if (order === 0 && sameFiles) {
|
|
482
488
|
console.log(
|
|
483
489
|
edited.length > 0
|
|
484
|
-
?
|
|
485
|
-
:
|
|
490
|
+
? `${ui.check()} shared v${installedVersion} is already installed (keeping your ${edited.length} edited file(s))`
|
|
491
|
+
: `${ui.check()} shared v${installedVersion} is already installed`,
|
|
486
492
|
);
|
|
487
493
|
return;
|
|
488
494
|
}
|
|
@@ -490,7 +496,7 @@ const installShared = async (destPath, { force = false } = {}) => {
|
|
|
490
496
|
|
|
491
497
|
if (edited.length > 0 && !force) {
|
|
492
498
|
console.error(
|
|
493
|
-
`\n
|
|
499
|
+
`\n${ui.cross()} These files in component-lib/${SHARED_DIR} have local changes:`,
|
|
494
500
|
);
|
|
495
501
|
edited.forEach((file) => console.error(` - ${file}`));
|
|
496
502
|
console.error(
|
|
@@ -541,8 +547,8 @@ const installShared = async (destPath, { force = false } = {}) => {
|
|
|
541
547
|
|
|
542
548
|
console.log(
|
|
543
549
|
installedVersion
|
|
544
|
-
?
|
|
545
|
-
:
|
|
550
|
+
? `${ui.check()} shared updated ${installedVersion} → ${srcVersion}`
|
|
551
|
+
: `${ui.check()} shared v${srcVersion} installed`,
|
|
546
552
|
);
|
|
547
553
|
};
|
|
548
554
|
|
|
@@ -656,7 +662,7 @@ const writeAntigravityRule = async (root) => {
|
|
|
656
662
|
|
|
657
663
|
/**
|
|
658
664
|
* Codex: a pointer in AGENTS.md rather than the skill itself, because that file
|
|
659
|
-
* is always in context and the skill is
|
|
665
|
+
* is always in context and the skill is about 10 KB. Everything outside our markers is
|
|
660
666
|
* left exactly as it was.
|
|
661
667
|
*/
|
|
662
668
|
const writeAgentsPointer = async (root) => {
|
|
@@ -727,7 +733,7 @@ const installSkill = async (
|
|
|
727
733
|
const edited = locallyEdited(destRoot, manifest);
|
|
728
734
|
|
|
729
735
|
if (edited.length > 0 && !force) {
|
|
730
|
-
console.error(`${NL}
|
|
736
|
+
console.error(`${NL}${ui.cross()} These skill files have local changes:`);
|
|
731
737
|
edited.forEach((file) => console.error(` - ${file}`));
|
|
732
738
|
console.error(
|
|
733
739
|
NL +
|
|
@@ -793,8 +799,8 @@ const installSkill = async (
|
|
|
793
799
|
|
|
794
800
|
console.log(
|
|
795
801
|
installedVersion
|
|
796
|
-
?
|
|
797
|
-
:
|
|
802
|
+
? `${ui.check()} skill updated ${installedVersion} → ${PACKAGE_VERSION}`
|
|
803
|
+
: `${ui.check()} skill v${PACKAGE_VERSION} installed`,
|
|
798
804
|
);
|
|
799
805
|
console.log(` ${SKILL_DEST_DIR}/ (canonical, ${listFiles(src).length} files)`);
|
|
800
806
|
if (targets.includes("claude")) {
|
|
@@ -902,10 +908,17 @@ const runPassport = async ({ check: checkOnly, dest: requestedDest }) => {
|
|
|
902
908
|
process.exit(1);
|
|
903
909
|
}
|
|
904
910
|
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
911
|
+
/*
|
|
912
|
+
* Each component is stamped with the kit version it was installed from —
|
|
913
|
+
* its install record, else the passport it shipped with — not with this
|
|
914
|
+
* project's own version. Stamping the project's version made every
|
|
915
|
+
* regenerated passport differ from the shipped one, so `passport --check`
|
|
916
|
+
* failed for components nobody had touched.
|
|
917
|
+
*/
|
|
918
|
+
const libraryVersion = (name, dir) =>
|
|
919
|
+
readJson(path.join(dir, ".install-manifest.json"), {})?.version ||
|
|
920
|
+
readJson(path.join(dir, "passport.json"), {})?.source?.libraryVersion ||
|
|
921
|
+
PACKAGE_VERSION;
|
|
909
922
|
|
|
910
923
|
const options = {
|
|
911
924
|
libRoot: dest,
|
|
@@ -913,6 +926,7 @@ const runPassport = async ({ check: checkOnly, dest: requestedDest }) => {
|
|
|
913
926
|
libraryVersion,
|
|
914
927
|
includeLocal: true,
|
|
915
928
|
coverageThreshold: undefined,
|
|
929
|
+
regenerate: `npx ${CLI_NAME} passport`,
|
|
916
930
|
};
|
|
917
931
|
|
|
918
932
|
try {
|
|
@@ -924,7 +938,7 @@ const runPassport = async ({ check: checkOnly, dest: requestedDest }) => {
|
|
|
924
938
|
continue;
|
|
925
939
|
}
|
|
926
940
|
for (const issue of row.problems) {
|
|
927
|
-
console.log(` ${issue.level === "error" ?
|
|
941
|
+
console.log(` ${issue.level === "error" ? ui.cross(process.stdout) : ui.c.yellow("!")} ${issue.component}: ${issue.message}`);
|
|
928
942
|
}
|
|
929
943
|
}
|
|
930
944
|
console.log(`${NL}${report.length} component(s) · ${errors} error(s) · ${warnings} warning(s)`);
|
|
@@ -934,9 +948,9 @@ const runPassport = async ({ check: checkOnly, dest: requestedDest }) => {
|
|
|
934
948
|
|
|
935
949
|
const { results, written } = passport.generate(options);
|
|
936
950
|
const errors = results.flatMap((r) => r.issues.filter((i) => i.level === "error"));
|
|
937
|
-
for (const issue of errors) console.error(`
|
|
951
|
+
for (const issue of errors) console.error(` ${ui.cross()} ${issue.component}: ${issue.message}`);
|
|
938
952
|
console.log(
|
|
939
|
-
`${NL}
|
|
953
|
+
`${NL}${ui.check()} ${results.length} passport(s) regenerated · ${written.length} changed`,
|
|
940
954
|
);
|
|
941
955
|
if (errors.length > 0) process.exit(1);
|
|
942
956
|
} catch (error) {
|
|
@@ -1038,14 +1052,14 @@ const copyComponent = async (component, destPath, current = false, { force = fal
|
|
|
1038
1052
|
if (outcome === "updated") {
|
|
1039
1053
|
console.log(
|
|
1040
1054
|
previous && previous !== PACKAGE_VERSION
|
|
1041
|
-
?
|
|
1042
|
-
:
|
|
1055
|
+
? `${ui.check()} Component ${component} updated ${previous} → ${PACKAGE_VERSION}`
|
|
1056
|
+
: `${ui.check()} Component ${component} replaced with ${PACKAGE_VERSION}${force ? " (--force)" : ""}`,
|
|
1043
1057
|
);
|
|
1044
1058
|
} else if (outcome === "unchanged") {
|
|
1045
|
-
console.log(
|
|
1059
|
+
console.log(`${ui.check()} Component ${component} already up to date (${PACKAGE_VERSION})`);
|
|
1046
1060
|
} else {
|
|
1047
1061
|
console.log(
|
|
1048
|
-
|
|
1062
|
+
`${ui.check()} Component ${component} installed successfully ${
|
|
1049
1063
|
component === "Grid"
|
|
1050
1064
|
? "This Version of Grid will be deprecated soon. Please Install The New Data Grid Component"
|
|
1051
1065
|
: ""
|
|
@@ -1069,8 +1083,8 @@ const reportKept = (component, status) => {
|
|
|
1069
1083
|
files.length ? ` ${label}: ${files.slice(0, 6).join(", ")}${files.length > 6 ? `, +${files.length - 6} more` : ""}` : null;
|
|
1070
1084
|
console.error(
|
|
1071
1085
|
status.state === "unknown"
|
|
1072
|
-
?
|
|
1073
|
-
:
|
|
1086
|
+
? `${ui.cross()} ${component} was not replaced: it was installed before 2.7.2 and differs from ${PACKAGE_VERSION}, so it may hold your changes.`
|
|
1087
|
+
: `${ui.cross()} ${component} was not replaced: it has changes made since it was installed.`,
|
|
1074
1088
|
);
|
|
1075
1089
|
for (const line of [list("changed", status.changed), list("deleted", status.missing)].filter(Boolean)) {
|
|
1076
1090
|
console.error(line);
|
|
@@ -1098,7 +1112,7 @@ const installComponentWithDependencies = async (
|
|
|
1098
1112
|
|
|
1099
1113
|
if (pendingInstalls.length === 0) {
|
|
1100
1114
|
console.log(
|
|
1101
|
-
|
|
1115
|
+
`${ui.check()} ${component} and all its dependencies are already installed.`,
|
|
1102
1116
|
);
|
|
1103
1117
|
return;
|
|
1104
1118
|
}
|
|
@@ -1144,7 +1158,7 @@ const installMultipleComponents = async (
|
|
|
1144
1158
|
|
|
1145
1159
|
if (pendingInstalls.length === 0) {
|
|
1146
1160
|
console.log(
|
|
1147
|
-
|
|
1161
|
+
`${ui.check()} All selected components and their dependencies are already installed.`,
|
|
1148
1162
|
);
|
|
1149
1163
|
return;
|
|
1150
1164
|
}
|
|
@@ -1175,95 +1189,44 @@ const installMultipleComponents = async (
|
|
|
1175
1189
|
console.log(`\nFor documentation visit: ${CONFIG.docs}`);
|
|
1176
1190
|
};
|
|
1177
1191
|
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
const isCurrentIndex = index === currentIndex;
|
|
1200
|
-
const deps = CONFIG.dependencies[component]
|
|
1201
|
-
? ` (requires: ${CONFIG.dependencies[component].join(", ")})`
|
|
1202
|
-
: "";
|
|
1203
|
-
|
|
1204
|
-
const prefix = isCurrentIndex ? ">" : " ";
|
|
1205
|
-
const checkbox = isSelected ? "☑" : "☐";
|
|
1206
|
-
const line = `${prefix} ${checkbox} ${component}${deps}`;
|
|
1207
|
-
|
|
1208
|
-
if (isCurrentIndex) {
|
|
1209
|
-
console.log(`\x1b[36m${line}\x1b[0m`); // Cyan highlight
|
|
1210
|
-
} else {
|
|
1211
|
-
console.log(line);
|
|
1212
|
-
}
|
|
1213
|
-
});
|
|
1214
|
-
|
|
1215
|
-
console.log(`\nSelected: ${selectedComponents.size} components`);
|
|
1216
|
-
console.log("Press ENTER to install selected components, or 'q' to quit");
|
|
1217
|
-
};
|
|
1218
|
-
|
|
1219
|
-
const handleKeyPress = (key) => {
|
|
1220
|
-
switch (key) {
|
|
1221
|
-
case "[A": // Up arrow
|
|
1222
|
-
currentIndex = Math.max(0, currentIndex - 1);
|
|
1223
|
-
renderMenu();
|
|
1224
|
-
break;
|
|
1225
|
-
case "[B": // Down arrow
|
|
1226
|
-
currentIndex = Math.min(
|
|
1227
|
-
getAvailableComponents(current).length - 1,
|
|
1228
|
-
currentIndex + 1,
|
|
1229
|
-
);
|
|
1230
|
-
renderMenu();
|
|
1231
|
-
break;
|
|
1232
|
-
case " ": // Space bar
|
|
1233
|
-
const component = getAvailableComponents(current)[currentIndex];
|
|
1234
|
-
if (selectedComponents.has(component)) {
|
|
1235
|
-
selectedComponents.delete(component);
|
|
1236
|
-
} else {
|
|
1237
|
-
selectedComponents.add(component);
|
|
1238
|
-
}
|
|
1239
|
-
renderMenu();
|
|
1240
|
-
break;
|
|
1241
|
-
case "\r": // Enter
|
|
1242
|
-
if (selectedComponents.size > 0) {
|
|
1243
|
-
rl.close();
|
|
1244
|
-
resolve(Array.from(selectedComponents));
|
|
1245
|
-
}
|
|
1246
|
-
break;
|
|
1247
|
-
case "q":
|
|
1248
|
-
rl.close();
|
|
1249
|
-
resolve([]);
|
|
1250
|
-
break;
|
|
1251
|
-
}
|
|
1252
|
-
};
|
|
1253
|
-
|
|
1254
|
-
// Enable raw mode to capture arrow keys
|
|
1255
|
-
process.stdin.setRawMode(true);
|
|
1256
|
-
process.stdin.resume();
|
|
1257
|
-
process.stdin.setEncoding("utf8");
|
|
1258
|
-
process.stdin.on("data", handleKeyPress);
|
|
1259
|
-
|
|
1260
|
-
renderMenu();
|
|
1192
|
+
/*
|
|
1193
|
+
* Where each component sits in the docs' navigation, shown beside it in the
|
|
1194
|
+
* picker and searchable: typing "overlay" narrows the list to the overlays.
|
|
1195
|
+
*/
|
|
1196
|
+
const GROUPS = {
|
|
1197
|
+
Inputs: ["Button", "Checkbox", "Combobox", "DatePicker", "FileUploader", "Input", "NumberInput", "RadioGroup", "Switch", "Textarea"],
|
|
1198
|
+
Data: ["DataGrid"],
|
|
1199
|
+
Overlays: ["Dialog", "Menu", "Modal", "Popover", "Tooltip"],
|
|
1200
|
+
Feedback: ["Alert", "Progress", "Skeleton", "Spinner", "Toaster"],
|
|
1201
|
+
Display: ["Accordion", "Avatar", "Badge", "Card"],
|
|
1202
|
+
Navigation: ["Breadcrumb", "Tabs"],
|
|
1203
|
+
};
|
|
1204
|
+
const groupOf = (component) =>
|
|
1205
|
+
Object.keys(GROUPS).find((group) => GROUPS[group].includes(component)) || "";
|
|
1206
|
+
|
|
1207
|
+
/** The one-line purpose a component's passport gives, for the picker's hint line. */
|
|
1208
|
+
const purposeOf = (component, current) => {
|
|
1209
|
+
if (!current) return "";
|
|
1210
|
+
const passport = readJson(path.join(SOURCE_PATH, folderFor(component, true), "passport.json"));
|
|
1211
|
+
return (passport && passport.purpose) || "";
|
|
1212
|
+
};
|
|
1261
1213
|
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1214
|
+
const interactiveComponentSelector = async (current = false, destPath) => {
|
|
1215
|
+
const { multiselect } = require("./tools/prompt.cjs");
|
|
1216
|
+
const options = getAvailableComponents(current)
|
|
1217
|
+
.map((component) => ({
|
|
1218
|
+
value: component,
|
|
1219
|
+
tag: current ? groupOf(component) : "",
|
|
1220
|
+
hint: purposeOf(component, current),
|
|
1221
|
+
note: destPath && checkComponentExists(component, destPath, current) ? "installed" : "",
|
|
1222
|
+
}))
|
|
1223
|
+
// The docs' order of groups, alphabetical within each.
|
|
1224
|
+
.sort((a, b) => {
|
|
1225
|
+
const order = Object.keys(GROUPS);
|
|
1226
|
+
return order.indexOf(a.tag) - order.indexOf(b.tag) || a.value.localeCompare(b.value);
|
|
1265
1227
|
});
|
|
1266
|
-
});
|
|
1228
|
+
const picked = await multiselect({ message: "Which components?", options });
|
|
1229
|
+
return picked || [];
|
|
1267
1230
|
};
|
|
1268
1231
|
|
|
1269
1232
|
const parseMultipleComponents = (componentString) => {
|
|
@@ -1420,7 +1383,7 @@ const main = async () => {
|
|
|
1420
1383
|
.example("$0 mcp install --for codex", "Only write the Codex config")
|
|
1421
1384
|
.example("$0 secret set figma", "Store a Figma token in the OS credential store")
|
|
1422
1385
|
.example("$0 design ./design.json", "Split a whole-file Figma export into design/")
|
|
1423
|
-
.example("$0
|
|
1386
|
+
.example("$0 add", "Pick components interactively (same as -i)")
|
|
1424
1387
|
.help().argv;
|
|
1425
1388
|
|
|
1426
1389
|
/*
|
|
@@ -1497,10 +1460,15 @@ const main = async () => {
|
|
|
1497
1460
|
if (parsed.command === "passport") argv.passport = true;
|
|
1498
1461
|
if (parsed.command === "add") {
|
|
1499
1462
|
if (parsed.operands.length === 0) {
|
|
1500
|
-
|
|
1501
|
-
|
|
1463
|
+
// In a terminal, a bare `add` opens the picker; in a script it says what to type.
|
|
1464
|
+
if (process.stdin.isTTY && process.stdout.isTTY) argv.interactive = true;
|
|
1465
|
+
else {
|
|
1466
|
+
console.log(`Which component? Try: ${CLI_NAME} add Button (or: ${CLI_NAME} list)`);
|
|
1467
|
+
return;
|
|
1468
|
+
}
|
|
1469
|
+
} else {
|
|
1470
|
+
argv.add = parsed.operands.join(",");
|
|
1502
1471
|
}
|
|
1503
|
-
argv.add = parsed.operands.join(",");
|
|
1504
1472
|
}
|
|
1505
1473
|
} else if (argv.add || argv.list || argv.skill || argv.passport) {
|
|
1506
1474
|
console.log(`Tip: the shorter form is \`${CLI_NAME} add <Component>\`.`);
|
|
@@ -1532,21 +1500,38 @@ const main = async () => {
|
|
|
1532
1500
|
|
|
1533
1501
|
// Interactive mode
|
|
1534
1502
|
if (argv.interactive) {
|
|
1535
|
-
|
|
1536
|
-
const
|
|
1503
|
+
const destPath = resolveDestPath(argv.dest);
|
|
1504
|
+
const where = path.relative(process.cwd(), destPath) || ".";
|
|
1505
|
+
ui.intro(`GramproKit ${PACKAGE_VERSION}`);
|
|
1506
|
+
let selectedComponents;
|
|
1507
|
+
try {
|
|
1508
|
+
selectedComponents = await interactiveComponentSelector(current, destPath);
|
|
1509
|
+
} catch (error) {
|
|
1510
|
+
ui.outro(ui.c.red(error.message));
|
|
1511
|
+
process.exitCode = 1;
|
|
1512
|
+
return;
|
|
1513
|
+
}
|
|
1537
1514
|
|
|
1538
1515
|
if (selectedComponents.length === 0) {
|
|
1539
|
-
|
|
1516
|
+
ui.outro(ui.c.dim("Nothing installed."));
|
|
1540
1517
|
return;
|
|
1541
1518
|
}
|
|
1519
|
+
console.log(ui.c.gray(ui.s.bar));
|
|
1520
|
+
console.log(`${ui.c.gray(ui.s.bar)} ${ui.c.dim(`Installing into ${where}/`)}`);
|
|
1521
|
+
console.log("");
|
|
1542
1522
|
|
|
1543
|
-
const destPath = resolveDestPath(argv.dest);
|
|
1544
1523
|
await fs.ensureDir(destPath);
|
|
1545
1524
|
|
|
1546
1525
|
await copySupportFiles(destPath, current, { force: argv.force });
|
|
1547
1526
|
|
|
1548
1527
|
// Install selected components
|
|
1549
1528
|
await installMultipleComponents(selectedComponents, destPath, current, { force: argv.force });
|
|
1529
|
+
console.log("");
|
|
1530
|
+
ui.outro(
|
|
1531
|
+
process.exitCode
|
|
1532
|
+
? ui.c.yellow("Done, with the warnings above.")
|
|
1533
|
+
: `${ui.c.green("Done.")} Import them from ${ui.c.cyan(`${where}/`)}`,
|
|
1534
|
+
);
|
|
1550
1535
|
return;
|
|
1551
1536
|
}
|
|
1552
1537
|
|
package/package.json
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
]
|
|
11
11
|
},
|
|
12
12
|
"source": {
|
|
13
|
-
"libraryVersion": "2.7.
|
|
13
|
+
"libraryVersion": "2.7.3",
|
|
14
14
|
"files": [
|
|
15
15
|
"core/agent.ts",
|
|
16
16
|
"core/group.ts",
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"react/locale.ts",
|
|
24
24
|
"react/props.ts"
|
|
25
25
|
],
|
|
26
|
-
"sourceHash": "sha256:
|
|
26
|
+
"sourceHash": "sha256:3bb3c0d76ef4b8d42f6b3bc0a3cdd203a90a1662175ec62ae40eba49ce7ad423"
|
|
27
27
|
},
|
|
28
28
|
"purpose": "A single native checkbox, and CheckboxGroup for several that share a name, with mixed state, a select-all and group-level validation.",
|
|
29
29
|
"props": [
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"intentDomains": [
|
|
6
6
|
"ActionIntent"
|
|
7
7
|
],
|
|
8
|
-
"$operationsNote": "Mirrors shared/core/agent/field.ts, which is what the runtime dispatches on. source/components/shared/core/agent/__tests__/field-passport.test.ts fails if the two drift. No `apiMethod`: a field is written through its ref — the element for most of these, a handle for Combobox and DatePicker — and that is a normal case, not a gap.",
|
|
8
|
+
"$operationsNote": "Mirrors shared/core/agent/field.ts, which is what the runtime dispatches on. source/components/shared/core/agent/__tests__/field-passport.test.ts fails if the two drift. No `apiMethod`: a field is written through its ref — the element for most of these, a handle for Combobox and DatePicker — and that is a normal case, not a gap. Inside an <AgentSurface> the component registers itself with these operations; NumberInput and RadioGroup are then written through their own commit path rather than the element.",
|
|
9
9
|
"operations": {
|
|
10
10
|
"setValue": {
|
|
11
11
|
"summary": "Write a value into the field, as though a person had entered it.",
|
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
} from "react";
|
|
15
15
|
import { useControllableState } from "../../shared/react/useControllableState";
|
|
16
16
|
import { describeField } from "../../shared/core/field";
|
|
17
|
+
import { labelText, useAgentSurfaceField } from "../../shared/react/agentSurfaceContext";
|
|
17
18
|
import type { CheckboxSize, CheckedState } from "../core/types";
|
|
18
19
|
import { CheckboxGroupContext } from "./context";
|
|
19
20
|
import { cx, type CheckboxSlot } from "./props";
|
|
@@ -107,6 +108,19 @@ export function Checkbox(props: CheckboxProps) {
|
|
|
107
108
|
onCheckedChange?.(next);
|
|
108
109
|
};
|
|
109
110
|
|
|
111
|
+
// Inside an <AgentSurface>, offered to an agent as a yes/no it can set.
|
|
112
|
+
useAgentSurfaceField(() => ({
|
|
113
|
+
contract: {
|
|
114
|
+
component: "Checkbox",
|
|
115
|
+
kind: "boolean",
|
|
116
|
+
label: labelText(label, inputProps["aria-label"], name, value),
|
|
117
|
+
disabled: disabled || group?.disabled,
|
|
118
|
+
required,
|
|
119
|
+
operations: ["setValue", "clear", "focus"],
|
|
120
|
+
},
|
|
121
|
+
target: () => inputRef.current,
|
|
122
|
+
}));
|
|
123
|
+
|
|
110
124
|
const invalid = Boolean(error) || (group?.invalid ?? false);
|
|
111
125
|
const describedBy = describeField(id, { extra: describedByProp, description, error });
|
|
112
126
|
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
]
|
|
10
10
|
},
|
|
11
11
|
"source": {
|
|
12
|
-
"libraryVersion": "2.7.
|
|
12
|
+
"libraryVersion": "2.7.3",
|
|
13
13
|
"files": [
|
|
14
14
|
"core/agent.ts",
|
|
15
15
|
"core/filter.ts",
|
|
@@ -24,9 +24,10 @@
|
|
|
24
24
|
"react/icons.tsx",
|
|
25
25
|
"react/locale.ts",
|
|
26
26
|
"react/props.ts",
|
|
27
|
+
"react/surface.ts",
|
|
27
28
|
"react/useCombobox.ts"
|
|
28
29
|
],
|
|
29
|
-
"sourceHash": "sha256:
|
|
30
|
+
"sourceHash": "sha256:c8001ea7c65beeed1b50d9f35cb0fcc447d01cea88128dda3d8b3584bd542496"
|
|
30
31
|
},
|
|
31
32
|
"purpose": "Select and MultiSelect: a searchable combobox for one or many values, with options in the browser's top layer, optional virtualisation and create-on-type.",
|
|
32
33
|
"props": [
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
"Recommendation",
|
|
8
8
|
"Classification"
|
|
9
9
|
],
|
|
10
|
-
"$operationsNote": "Mirrors shared/core/agent/field.ts, which is what the runtime dispatches on. source/components/shared/core/agent/__tests__/field-passport.test.ts fails if the two drift. No `apiMethod`: a field is written through its ref — the element for most of these, a handle for Combobox and DatePicker — and that is a normal case, not a gap.",
|
|
10
|
+
"$operationsNote": "Mirrors shared/core/agent/field.ts, which is what the runtime dispatches on. source/components/shared/core/agent/__tests__/field-passport.test.ts fails if the two drift. No `apiMethod`: a field is written through its ref — the element for most of these, a handle for Combobox and DatePicker — and that is a normal case, not a gap. Inside an <AgentSurface> the component registers itself with these operations; NumberInput and RadioGroup are then written through their own commit path rather than the element.",
|
|
11
11
|
"operations": {
|
|
12
12
|
"setValue": {
|
|
13
13
|
"summary": "Write a value into the field, as though a person had entered it.",
|
|
@@ -8,6 +8,7 @@ import { Listbox } from "./Listbox";
|
|
|
8
8
|
import { defaultComboboxText } from "./locale";
|
|
9
9
|
import { ComboboxPopover } from "./Popover";
|
|
10
10
|
import { cx, type ComboboxHandle, type ComboboxSharedProps } from "./props";
|
|
11
|
+
import { useComboboxSurface } from "./surface";
|
|
11
12
|
import { useCombobox } from "./useCombobox";
|
|
12
13
|
|
|
13
14
|
export interface MultiSelectProps<V extends OptionValue = string> extends ComboboxSharedProps<V> {
|
|
@@ -89,6 +90,18 @@ export function MultiSelect<V extends OptionValue = string>(props: MultiSelectPr
|
|
|
89
90
|
const visibleTags = hidden > 0 ? selectedOptions.slice(0, maxVisibleTags) : selectedOptions;
|
|
90
91
|
|
|
91
92
|
useImperativeHandle(ref, () => combobox.handle, [combobox.handle]);
|
|
93
|
+
useComboboxSurface({
|
|
94
|
+
component: "MultiSelect",
|
|
95
|
+
label,
|
|
96
|
+
ariaLabel: props["aria-label"],
|
|
97
|
+
name,
|
|
98
|
+
required,
|
|
99
|
+
disabled,
|
|
100
|
+
options,
|
|
101
|
+
mode: props.mode,
|
|
102
|
+
max: max,
|
|
103
|
+
handle: combobox.handle,
|
|
104
|
+
});
|
|
92
105
|
|
|
93
106
|
const selectableVisible = combobox.entries.filter(
|
|
94
107
|
(entry) => !entry.option.disabled && !combobox.isSelected(entry.option.value),
|
|
@@ -8,6 +8,7 @@ import { ChevronIcon, XIcon } from "./icons";
|
|
|
8
8
|
import { Listbox } from "./Listbox";
|
|
9
9
|
import { ComboboxPopover } from "./Popover";
|
|
10
10
|
import { cx, type ComboboxHandle, type ComboboxSharedProps } from "./props";
|
|
11
|
+
import { useComboboxSurface } from "./surface";
|
|
11
12
|
import { useCombobox } from "./useCombobox";
|
|
12
13
|
|
|
13
14
|
export interface SelectProps<V extends OptionValue = string> extends ComboboxSharedProps<V> {
|
|
@@ -83,6 +84,18 @@ export function Select<V extends OptionValue = string>(props: SelectProps<V>) {
|
|
|
83
84
|
const selected = combobox.selectedOptions[0];
|
|
84
85
|
|
|
85
86
|
useImperativeHandle(ref, () => combobox.handle, [combobox.handle]);
|
|
87
|
+
useComboboxSurface({
|
|
88
|
+
component: "Select",
|
|
89
|
+
label,
|
|
90
|
+
ariaLabel: props["aria-label"],
|
|
91
|
+
name,
|
|
92
|
+
required,
|
|
93
|
+
disabled,
|
|
94
|
+
options,
|
|
95
|
+
mode: props.mode,
|
|
96
|
+
max: undefined,
|
|
97
|
+
handle: combobox.handle,
|
|
98
|
+
});
|
|
86
99
|
|
|
87
100
|
return (
|
|
88
101
|
<div
|