@lmzhen/dsh-evolution-ui 0.34.0 → 0.35.0
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/lib/client.js
CHANGED
|
@@ -841,12 +841,12 @@ window.__ModuleLoader__.load({
|
|
|
841
841
|
projectionFailed: "这一块读设置时出错,已记入日志;其它参数不受影响",
|
|
842
842
|
readonly: "本次会话不允许写入设置(临时运行模式)",
|
|
843
843
|
apply: "保存",
|
|
844
|
-
reset: "
|
|
844
|
+
reset: "复位",
|
|
845
845
|
empty: "这里没有可改项",
|
|
846
846
|
changed: "{n} 项已覆盖",
|
|
847
847
|
discard: "放弃修改",
|
|
848
848
|
saving: "保存中…",
|
|
849
|
-
resetting: "
|
|
849
|
+
resetting: "复位中…",
|
|
850
850
|
writeTargetHost: "保存去向:宿主配置(对所有会话生效)",
|
|
851
851
|
invalidNumber: "需要一个数字",
|
|
852
852
|
invalidSelect: "不在允许的取值里",
|
|
@@ -949,7 +949,7 @@ window.__ModuleLoader__.load({
|
|
|
949
949
|
projectionFailed: "reading this row failed and the error is in the log; the other parameters are unaffected",
|
|
950
950
|
readonly: "settings cannot be written in this session (temporary mode)",
|
|
951
951
|
apply: "Save",
|
|
952
|
-
reset: "Reset
|
|
952
|
+
reset: "Reset",
|
|
953
953
|
empty: "nothing to change here",
|
|
954
954
|
changed: "{n} overridden",
|
|
955
955
|
discard: "Discard",
|
|
@@ -1270,10 +1270,12 @@ window.__ModuleLoader__.load({
|
|
|
1270
1270
|
type: "button",
|
|
1271
1271
|
className: "evolution-param-button",
|
|
1272
1272
|
disabled: props.disabled || props.clearing,
|
|
1273
|
+
"data-role": "reset",
|
|
1274
|
+
title: props.resetTo === null ? void 0 : t("resetTo", { value: props.resetTo }),
|
|
1273
1275
|
onClick: () => {
|
|
1274
1276
|
props.onClear(field.id);
|
|
1275
1277
|
}
|
|
1276
|
-
}, props.clearing ? t("resetting") : t("reset"))
|
|
1278
|
+
}, props.clearing ? t("resetting") : t("reset"))) : null);
|
|
1277
1279
|
}
|
|
1278
1280
|
/**
|
|
1279
1281
|
* Render one row's card body from a supplied snapshot.
|
|
@@ -1780,6 +1782,28 @@ window.__ModuleLoader__.load({
|
|
|
1780
1782
|
};
|
|
1781
1783
|
}
|
|
1782
1784
|
//#endregion
|
|
1785
|
+
//#region ../evolution-client-kit/src/format.ts
|
|
1786
|
+
/**
|
|
1787
|
+
* Format an ISO string, epoch milliseconds, or null as local wall time.
|
|
1788
|
+
* Invalid values return null; callers decide how to say "unknown".
|
|
1789
|
+
* @param value - ISO string, epoch ms, or null.
|
|
1790
|
+
* @param options - seconds on/off; defaults to on.
|
|
1791
|
+
* @returns the formatted local time, or null when the value is absent/invalid.
|
|
1792
|
+
*/
|
|
1793
|
+
function formatLocalDateTime(value, options = {}) {
|
|
1794
|
+
if (value === null || value === void 0) return null;
|
|
1795
|
+
const ms = typeof value === "number" ? value : Date.parse(value);
|
|
1796
|
+
if (!Number.isFinite(ms)) return null;
|
|
1797
|
+
const date = new Date(ms);
|
|
1798
|
+
if (Number.isNaN(date.getTime())) return null;
|
|
1799
|
+
const pad = (part) => String(part).padStart(2, "0");
|
|
1800
|
+
const head = String(date.getFullYear()) + "-" + pad(date.getMonth() + 1) + "-" + pad(date.getDate()) + " " + pad(date.getHours()) + ":" + pad(date.getMinutes());
|
|
1801
|
+
return options.seconds === false ? head : head + ":" + pad(date.getSeconds());
|
|
1802
|
+
}
|
|
1803
|
+
//#endregion
|
|
1804
|
+
//#region ../evolution-client-kit/src/notice.ts
|
|
1805
|
+
/** One inline state notice: semantic tone plus text, no surface-specific styling. */
|
|
1806
|
+
//#endregion
|
|
1783
1807
|
//#region ../evolution-client-kit/src/action.ts
|
|
1784
1808
|
/**
|
|
1785
1809
|
* Command-only actions. Approval decisions are a separate host API and must never
|
|
@@ -2903,6 +2927,7 @@ window.__ModuleLoader__.load({
|
|
|
2903
2927
|
const zh = {
|
|
2904
2928
|
"entry.label": "技能历史",
|
|
2905
2929
|
title: "技能历史",
|
|
2930
|
+
bodyTitle: "正文版本",
|
|
2906
2931
|
"workbench.history": "技能历史",
|
|
2907
2932
|
"workbench.pending": "待批",
|
|
2908
2933
|
"workbench.merge": "合并",
|
|
@@ -2932,6 +2957,7 @@ window.__ModuleLoader__.load({
|
|
|
2932
2957
|
"failure.server": "读取失败,可以重试。",
|
|
2933
2958
|
"fallback.hint": "可以改用命令:{command}",
|
|
2934
2959
|
"undo.confirm": "确认回退(会写入一版,可再回退)",
|
|
2960
|
+
"undo.confirmUnit": "确认回退整个跨技能写入单元(会写入一版,可再回退)",
|
|
2935
2961
|
"undo.no": "取消",
|
|
2936
2962
|
current: "当前",
|
|
2937
2963
|
"versions.count": "{n} 个版本",
|
|
@@ -2972,6 +2998,7 @@ window.__ModuleLoader__.load({
|
|
|
2972
2998
|
"action.archive": "归档",
|
|
2973
2999
|
"action.archive.note": "这一版存的是被归档的正文",
|
|
2974
3000
|
"action.consolidate": "合并",
|
|
3001
|
+
"action.merge": "合并",
|
|
2975
3002
|
"action.restructure": "结构调整",
|
|
2976
3003
|
"action.extract": "跨技能提取",
|
|
2977
3004
|
"action.support-write": "写入附带文件",
|
|
@@ -2995,6 +3022,7 @@ window.__ModuleLoader__.load({
|
|
|
2995
3022
|
const en = {
|
|
2996
3023
|
"entry.label": "Skill history",
|
|
2997
3024
|
title: "Skill history",
|
|
3025
|
+
bodyTitle: "Body versions",
|
|
2998
3026
|
"workbench.history": "Skill history",
|
|
2999
3027
|
"workbench.pending": "Pending",
|
|
3000
3028
|
"workbench.merge": "Merge",
|
|
@@ -3024,6 +3052,7 @@ window.__ModuleLoader__.load({
|
|
|
3024
3052
|
"failure.server": "The read failed; you can retry.",
|
|
3025
3053
|
"fallback.hint": "Command fallback: {command}",
|
|
3026
3054
|
"undo.confirm": "Confirm restore (writes a new version; can be undone)",
|
|
3055
|
+
"undo.confirmUnit": "Confirm restoring the whole cross-skill unit (writes a new version; can be undone)",
|
|
3027
3056
|
"undo.no": "Cancel",
|
|
3028
3057
|
current: "Current",
|
|
3029
3058
|
"versions.count": "{n} versions",
|
|
@@ -3064,6 +3093,7 @@ window.__ModuleLoader__.load({
|
|
|
3064
3093
|
"action.archive": "archived",
|
|
3065
3094
|
"action.archive.note": "this version holds the archived body",
|
|
3066
3095
|
"action.consolidate": "merged",
|
|
3096
|
+
"action.merge": "merged",
|
|
3067
3097
|
"action.restructure": "restructured",
|
|
3068
3098
|
"action.extract": "extracted across skills",
|
|
3069
3099
|
"action.support-write": "support file written",
|
|
@@ -3130,6 +3160,7 @@ window.__ModuleLoader__.load({
|
|
|
3130
3160
|
".evo-hist-skill[aria-current=\"true\"]{background:var(--dsw-alias-interactive-bg-active);color:var(--dsw-alias-label-primary);font-weight:500}",
|
|
3131
3161
|
".evo-hist-cell-line{display:flex;align-items:baseline;gap:var(--evo-space-6);min-width:0}",
|
|
3132
3162
|
".evo-hist-cell-meta{margin-top:var(--evo-space-2)}",
|
|
3163
|
+
".evo-hist-skill-description{display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;margin-top:var(--evo-space-2);color:var(--dsw-alias-label-tertiary)}",
|
|
3133
3164
|
".evo-hist-cell{min-width:0;font-variant-numeric:tabular-nums}",
|
|
3134
3165
|
".evo-hist-slot-lead{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}",
|
|
3135
3166
|
".evo-hist-slot-meta{flex:none;white-space:nowrap}",
|
|
@@ -3173,7 +3204,7 @@ window.__ModuleLoader__.load({
|
|
|
3173
3204
|
".evo-doc-tag{position:absolute;left:var(--evo-space-8);top:var(--evo-space-8);color:var(--dsw-alias-label-tertiary)}",
|
|
3174
3205
|
".evo-doc-line-add{color:var(--evo-tone-success)}",
|
|
3175
3206
|
".evo-doc-line-del{color:var(--evo-tone-danger)}",
|
|
3176
|
-
"@media (max-width:
|
|
3207
|
+
"@media (max-width:900px){.evo-hist-root{flex-direction:column}.evo-hist-aside{flex:0 0 auto;width:100%;max-height:42%;border-right:0;border-bottom:var(--evo-hairline-width) solid var(--dsw-alias-border-l2)}.evo-hist-row{max-width:none}}"
|
|
3177
3208
|
].join("\n");
|
|
3178
3209
|
//#endregion
|
|
3179
3210
|
//#region src/panels/history/client/atoms.ts
|
|
@@ -3281,11 +3312,7 @@ window.__ModuleLoader__.load({
|
|
|
3281
3312
|
*/
|
|
3282
3313
|
/** The local time an ISO timestamp names, as the operator's own clock shows it. */
|
|
3283
3314
|
function localTime(at) {
|
|
3284
|
-
|
|
3285
|
-
if (Number.isNaN(parsed)) return at;
|
|
3286
|
-
const date = new Date(parsed);
|
|
3287
|
-
const pad = (value) => String(value).padStart(2, "0");
|
|
3288
|
-
return String(date.getFullYear()) + "-" + pad(date.getMonth() + 1) + "-" + pad(date.getDate()) + " " + pad(date.getHours()) + ":" + pad(date.getMinutes());
|
|
3315
|
+
return formatLocalDateTime(at, { seconds: false }) ?? at;
|
|
3289
3316
|
}
|
|
3290
3317
|
/** The text one cell shows, resolved through the seat. */
|
|
3291
3318
|
function cellText(copy, cell) {
|
|
@@ -3334,7 +3361,11 @@ window.__ModuleLoader__.load({
|
|
|
3334
3361
|
className: "evo-hist-skill",
|
|
3335
3362
|
"aria-current": selected ? "true" : void 0,
|
|
3336
3363
|
onClick: onOpen
|
|
3337
|
-
}, cellLine(row.cells.title, copy, "evo-hist-cell-line"),
|
|
3364
|
+
}, cellLine(row.cells.title, copy, "evo-hist-cell-line"), (() => {
|
|
3365
|
+
const description = row.cells.meta.filter((cell) => cell.key === "description");
|
|
3366
|
+
const facts = row.cells.meta.filter((cell) => cell.key !== "description");
|
|
3367
|
+
return [description.length === 0 ? null : cellLine(description, copy, "evo-hist-cell-line evo-hist-skill-description"), facts.length === 0 ? null : cellLine(facts, copy, "evo-hist-cell-line evo-hist-cell-meta")];
|
|
3368
|
+
})());
|
|
3338
3369
|
}
|
|
3339
3370
|
/**
|
|
3340
3371
|
* One version row: what happened, when, how big — then the controls, then whatever is expanded.
|
|
@@ -3362,7 +3393,7 @@ window.__ModuleLoader__.load({
|
|
|
3362
3393
|
key: "undo",
|
|
3363
3394
|
tone: confirming ? "primary" : "quiet",
|
|
3364
3395
|
onClick: onRestore,
|
|
3365
|
-
children: confirming ? copy.t("undo.confirm") : copy.t("undo")
|
|
3396
|
+
children: confirming ? copy.t(row.unitId === void 0 ? "undo.confirm" : "undo.confirmUnit") : copy.t("undo")
|
|
3366
3397
|
}));
|
|
3367
3398
|
}
|
|
3368
3399
|
if (row.undoable === true || row.charsDelta !== void 0) actions.push(button({
|
|
@@ -3422,8 +3453,8 @@ window.__ModuleLoader__.load({
|
|
|
3422
3453
|
});
|
|
3423
3454
|
}
|
|
3424
3455
|
/** The pane's heading and its one control. */
|
|
3425
|
-
function panelHead(copy, onRefresh) {
|
|
3426
|
-
return (0, react.createElement)("div", { className: "evo-hist-head" }, (0, react.createElement)("h2", { className: "evo-hist-title" }, copy.t("title")), button({
|
|
3456
|
+
function panelHead(copy, onRefresh, title) {
|
|
3457
|
+
return (0, react.createElement)("div", { className: "evo-hist-head" }, (0, react.createElement)("h2", { className: "evo-hist-title" }, title ?? copy.t("title")), button({
|
|
3427
3458
|
key: "refresh",
|
|
3428
3459
|
title: copy.t("refresh"),
|
|
3429
3460
|
onClick: onRefresh,
|
|
@@ -3949,7 +3980,7 @@ window.__ModuleLoader__.load({
|
|
|
3949
3980
|
};
|
|
3950
3981
|
return (0, react.createElement)("div", { className: "evo-hist-root" }, (0, react.createElement)("aside", { className: "evo-hist-aside" }, searchField(face, query, setQuery), (0, react.createElement)("div", { className: "evo-hist-list" }, skills.length === 0 ? emptyState(face.t(skillsLoaded ? "empty.skills" : "loading")) : visible.length === 0 ? emptyState(face.t("search.none")) : visible.map((skill) => skillLine(face, skill, skill.name === selected, () => {
|
|
3951
3982
|
open(skill.name);
|
|
3952
|
-
})))), (0, react.createElement)("div", { className: "evo-hist-main" }, panelHead(face, refresh), notice(noteText, noteError), failure === void 0 ? null : (0, react.createElement)("div", { className: "evo-hist-failure" }, failureBlock(face.t, failure)), selected === void 0 ? [emptyState(face.t(skills.length === 0 ? skillsLoaded ? "empty.skills" : "loading" : "empty.pick"), "empty"), (0, react.createElement)("p", {
|
|
3983
|
+
})))), (0, react.createElement)("div", { className: "evo-hist-main" }, panelHead(face, refresh, selected ?? face.t("bodyTitle")), notice(noteText, noteError), failure === void 0 ? null : (0, react.createElement)("div", { className: "evo-hist-failure" }, failureBlock(face.t, failure)), selected === void 0 ? [emptyState(face.t(skills.length === 0 ? skillsLoaded ? "empty.skills" : "loading" : "empty.pick"), "empty"), (0, react.createElement)("p", {
|
|
3953
3984
|
key: "hint",
|
|
3954
3985
|
className: "evo-hist-hint"
|
|
3955
3986
|
}, face.t("hint"))] : loaded ? null : emptyState(versionsFailed === void 0 ? face.t("loading") : `${face.t("empty.versions.failed")} — ${versionsFailed}`), loaded ? (0, react.createElement)("div", null, group(face.t("group.content"), payload.content, void 0), group(face.t("group.support"), payload.support, face.t("group.support.note")), payload.content.length === 0 && payload.support.length === 0 ? (0, react.createElement)("p", { className: "evo-hist-hint" }, face.t("empty.versions")) : null) : null));
|
|
@@ -4029,19 +4060,26 @@ window.__ModuleLoader__.load({
|
|
|
4029
4060
|
const translate = (key, values) => t(key, values);
|
|
4030
4061
|
const [state, setState] = (0, react.useState)({ phase: "loading" });
|
|
4031
4062
|
const [refreshing, setRefreshing] = (0, react.useState)(false);
|
|
4063
|
+
const [refreshFailure, setRefreshFailure] = (0, react.useState)(void 0);
|
|
4032
4064
|
const [open, setOpen] = (0, react.useState)(false);
|
|
4033
4065
|
const ticket = (0, react.useRef)(0);
|
|
4034
4066
|
const read = (keepView) => {
|
|
4035
4067
|
const current = ticket.current + 1;
|
|
4036
4068
|
ticket.current = current;
|
|
4037
|
-
if (keepView)
|
|
4038
|
-
|
|
4069
|
+
if (keepView) {
|
|
4070
|
+
setRefreshing(true);
|
|
4071
|
+
setRefreshFailure(void 0);
|
|
4072
|
+
} else setState({ phase: "loading" });
|
|
4039
4073
|
api.view().then((answer) => {
|
|
4040
4074
|
if (ticket.current !== current) return;
|
|
4041
|
-
|
|
4042
|
-
|
|
4043
|
-
|
|
4044
|
-
|
|
4075
|
+
if (answer.ok) {
|
|
4076
|
+
setState({
|
|
4077
|
+
phase: "ready",
|
|
4078
|
+
view: answer.data
|
|
4079
|
+
});
|
|
4080
|
+
setRefreshFailure(void 0);
|
|
4081
|
+
} else if (keepView) setRefreshFailure(answer.failure);
|
|
4082
|
+
else setState({
|
|
4045
4083
|
phase: "failed",
|
|
4046
4084
|
failure: answer.failure
|
|
4047
4085
|
});
|
|
@@ -4056,6 +4094,7 @@ window.__ModuleLoader__.load({
|
|
|
4056
4094
|
read(true);
|
|
4057
4095
|
};
|
|
4058
4096
|
const summary = state.phase === "ready" ? summaryOf(state.view.runtime) : void 0;
|
|
4097
|
+
const focusRow = summary === void 0 || summary.status === "ok" || state.phase !== "ready" ? void 0 : state.view.runtime.find((row) => row.status === summary.status);
|
|
4059
4098
|
const header = (0, react.createElement)("button", {
|
|
4060
4099
|
type: "button",
|
|
4061
4100
|
className: "evolution-param-head",
|
|
@@ -4066,7 +4105,7 @@ window.__ModuleLoader__.load({
|
|
|
4066
4105
|
}, (0, react.createElement)("span", { className: "evolution-param-card-title" }, t("diagTitle")), summary === void 0 ? null : (0, react.createElement)("span", {
|
|
4067
4106
|
className: "evo-diag-badge",
|
|
4068
4107
|
"data-status": summary.status
|
|
4069
|
-
}, t(SUMMARY_KEYS[summary.status], summary.count === 0 ? void 0 : { n: summary.count })), (0, react.createElement)("span", { className: "evolution-param-chevron" }, open ? "▲" : "▼"));
|
|
4108
|
+
}, t(SUMMARY_KEYS[summary.status], summary.count === 0 ? void 0 : { n: summary.count })), focusRow === void 0 ? null : (0, react.createElement)("span", { "data-role": "diag-focus" }, focusRow.label), (0, react.createElement)("span", { className: "evolution-param-chevron" }, open ? "▲" : "▼"));
|
|
4070
4109
|
const runtime = (view) => view.runtime.length === 0 ? null : (0, react.createElement)("div", {}, (0, react.createElement)("h3", { className: "evolution-param-group" }, t("diagRuntimeTitle")), (0, react.createElement)("div", { className: "evo-diag-list" }, ...view.runtime.map((row) => (0, react.createElement)("div", {
|
|
4071
4110
|
key: "runtime-" + row.key,
|
|
4072
4111
|
className: "evo-diag-item",
|
|
@@ -4081,7 +4120,11 @@ window.__ModuleLoader__.load({
|
|
|
4081
4120
|
fallback: props.fallback
|
|
4082
4121
|
});
|
|
4083
4122
|
const view = state.view;
|
|
4084
|
-
return (0, react.createElement)("div", {}, (0, react.createElement)("p", { className: "evolution-param-note" }, t("diagSubtitle")),
|
|
4123
|
+
return (0, react.createElement)("div", {}, (0, react.createElement)("p", { className: "evolution-param-note" }, t("diagSubtitle")), refreshFailure === void 0 ? null : FailureBlock({
|
|
4124
|
+
failure: refreshFailure,
|
|
4125
|
+
t: translate,
|
|
4126
|
+
fallback: props.fallback
|
|
4127
|
+
}), (0, react.createElement)("div", { className: "evolution-param-actions" }, (0, react.createElement)("button", {
|
|
4085
4128
|
type: "button",
|
|
4086
4129
|
className: "evolution-param-button",
|
|
4087
4130
|
onClick: refresh,
|
|
@@ -72,5 +72,5 @@ export declare function emptyState(text: string, key?: string): ReactNode;
|
|
|
72
72
|
/** One notice about what just happened. */
|
|
73
73
|
export declare function notice(text: string | undefined, error: boolean): ReactNode;
|
|
74
74
|
/** The pane's heading and its one control. */
|
|
75
|
-
export declare function panelHead(copy: Copy, onRefresh: () => void): ReactNode;
|
|
75
|
+
export declare function panelHead(copy: Copy, onRefresh: () => void, title?: string): ReactNode;
|
|
76
76
|
//# sourceMappingURL=chrome.d.ts.map
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* description, so the settings surface can state both without a hand-written copy.
|
|
6
6
|
*/
|
|
7
7
|
/** The family version this surface belongs to. */
|
|
8
|
-
export declare const FAMILY_VERSION = "0.
|
|
8
|
+
export declare const FAMILY_VERSION = "0.35.0";
|
|
9
9
|
/** What this surface is for, in one line. */
|
|
10
10
|
export declare const PACKAGE_PURPOSE = "Web UI umbrella for the evolution family: settings and skill-history panels";
|
|
11
11
|
//# sourceMappingURL=generated-about.d.ts.map
|
|
@@ -23,12 +23,12 @@ export declare const zh: {
|
|
|
23
23
|
readonly projectionFailed: "这一块读设置时出错,已记入日志;其它参数不受影响";
|
|
24
24
|
readonly readonly: "本次会话不允许写入设置(临时运行模式)";
|
|
25
25
|
readonly apply: "保存";
|
|
26
|
-
readonly reset: "
|
|
26
|
+
readonly reset: "复位";
|
|
27
27
|
readonly empty: "这里没有可改项";
|
|
28
28
|
readonly changed: "{n} 项已覆盖";
|
|
29
29
|
readonly discard: "放弃修改";
|
|
30
30
|
readonly saving: "保存中…";
|
|
31
|
-
readonly resetting: "
|
|
31
|
+
readonly resetting: "复位中…";
|
|
32
32
|
readonly writeTargetHost: "保存去向:宿主配置(对所有会话生效)";
|
|
33
33
|
readonly invalidNumber: "需要一个数字";
|
|
34
34
|
readonly invalidSelect: "不在允许的取值里";
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lmzhen/dsh-evolution-ui",
|
|
3
3
|
"description": "Web UI umbrella for the evolution family: settings and skill-history panels (community build)",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.35.0",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|
|
@@ -45,9 +45,9 @@
|
|
|
45
45
|
"react": "^18.2.0"
|
|
46
46
|
},
|
|
47
47
|
"dependencies": {
|
|
48
|
-
"@lmzhen/dsh-evolution-client-kit": "^0.
|
|
49
|
-
"@lmzhen/dsh-evolution-core": "^0.
|
|
50
|
-
"@lmzhen/dsh-evolution-curator": "^0.
|
|
48
|
+
"@lmzhen/dsh-evolution-client-kit": "^0.35.0",
|
|
49
|
+
"@lmzhen/dsh-evolution-core": "^0.35.0",
|
|
50
|
+
"@lmzhen/dsh-evolution-curator": "^0.35.0"
|
|
51
51
|
},
|
|
52
52
|
"devDependencies": {
|
|
53
53
|
"@types/react": "~18.3.1",
|