@gmickel/gno 1.31.0 → 1.32.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/README.md +5 -4
- package/assets/skill/SKILL.md +23 -0
- package/browser-extension/artifacts/{gno-browser-clipper-v1.31.0.zip → gno-browser-clipper-v1.32.0.zip} +0 -0
- package/browser-extension/artifacts/gno-browser-clipper-v1.32.0.zip.sha256 +1 -0
- package/browser-extension/dist/manifest.json +1 -1
- package/package.json +1 -1
- package/spec/cli.md +19 -0
- package/spec/db/schema.sql +55 -0
- package/spec/mcp.md +114 -11
- package/spec/output-schemas/file-refactor-apply-result.schema.json +305 -0
- package/spec/output-schemas/file-refactor-preview.schema.json +393 -0
- package/src/core/document-capabilities.ts +13 -0
- package/src/core/file-ops.ts +129 -1
- package/src/core/file-refactor-adapter.ts +329 -0
- package/src/core/file-refactor-apply-edits.ts +61 -0
- package/src/core/file-refactor-apply-fs.ts +512 -0
- package/src/core/file-refactor-apply-safety.ts +340 -0
- package/src/core/file-refactor-apply-validate.ts +401 -0
- package/src/core/file-refactor-contract.ts +486 -0
- package/src/core/file-refactor-destination.ts +123 -0
- package/src/core/file-refactor-from-snapshot.ts +148 -0
- package/src/core/file-refactor-journal-port.ts +150 -0
- package/src/core/file-refactor-journal.ts +347 -0
- package/src/core/file-refactor-paths.ts +60 -0
- package/src/core/file-refactor-plan-classify.ts +208 -0
- package/src/core/file-refactor-plan-validate.ts +169 -0
- package/src/core/file-refactor-planner-types.ts +62 -0
- package/src/core/file-refactor-planner.ts +423 -0
- package/src/core/file-refactor-resolve.ts +280 -0
- package/src/core/file-refactor-service.ts +468 -0
- package/src/core/file-refactors.ts +84 -56
- package/src/core/link-destination-parse.ts +275 -0
- package/src/core/link-inventory-markdown.ts +454 -0
- package/src/core/link-inventory-opaque.ts +244 -0
- package/src/core/link-inventory-types.ts +47 -0
- package/src/core/link-inventory.ts +182 -0
- package/src/core/link-relevance.ts +150 -0
- package/src/mcp/tools/index.ts +18 -17
- package/src/mcp/tools/workspace-write.ts +215 -97
- package/src/sdk/client.ts +167 -115
- package/src/sdk/index.ts +7 -0
- package/src/sdk/types.ts +32 -2
- package/src/serve/file-refactor-http.ts +239 -0
- package/src/serve/public/components/RefactorImpactPreview.tsx +227 -0
- package/src/serve/public/globals.built.css +1 -1
- package/src/serve/public/pages/DocView.tsx +176 -41
- package/src/serve/routes/api.ts +191 -104
- package/src/store/migrations/026-file-refactor-recovery-journal.ts +72 -0
- package/src/store/migrations/index.ts +2 -0
- package/src/store/sqlite/adapter.ts +452 -0
- package/src/store/sqlite/file-refactor-journal-store.ts +275 -0
- package/src/store/types.ts +84 -0
- package/browser-extension/artifacts/gno-browser-clipper-v1.31.0.zip.sha256 +0 -1
|
@@ -31,6 +31,10 @@ import {
|
|
|
31
31
|
|
|
32
32
|
import type { PdfFallbackReason } from "../lib/pdf";
|
|
33
33
|
|
|
34
|
+
import {
|
|
35
|
+
FILE_REFACTOR_APPLY_CONFIRMATION,
|
|
36
|
+
type FileRefactorPreviewPlan,
|
|
37
|
+
} from "../../../core/file-refactor-contract";
|
|
34
38
|
import { extractSections } from "../../../core/sections";
|
|
35
39
|
import {
|
|
36
40
|
CodeBlock,
|
|
@@ -47,6 +51,7 @@ import {
|
|
|
47
51
|
OutgoingLinksPanel,
|
|
48
52
|
type OutgoingLink,
|
|
49
53
|
} from "../components/OutgoingLinksPanel";
|
|
54
|
+
import { RefactorImpactPreview } from "../components/RefactorImpactPreview";
|
|
50
55
|
import { RelatedNotesSidebar } from "../components/RelatedNotesSidebar";
|
|
51
56
|
import { TagInput } from "../components/TagInput";
|
|
52
57
|
import { Badge } from "../components/ui/badge";
|
|
@@ -199,6 +204,9 @@ interface RenameDocResponse {
|
|
|
199
204
|
uri: string;
|
|
200
205
|
path: string;
|
|
201
206
|
relPath: string;
|
|
207
|
+
planDigest?: string;
|
|
208
|
+
status?: string;
|
|
209
|
+
warning?: string;
|
|
202
210
|
refactorWarnings?: {
|
|
203
211
|
warnings: string[];
|
|
204
212
|
};
|
|
@@ -209,6 +217,9 @@ interface MoveDocResponse {
|
|
|
209
217
|
uri: string;
|
|
210
218
|
path: string;
|
|
211
219
|
relPath: string;
|
|
220
|
+
planDigest?: string;
|
|
221
|
+
status?: string;
|
|
222
|
+
warning?: string;
|
|
212
223
|
refactorWarnings?: {
|
|
213
224
|
warnings: string[];
|
|
214
225
|
};
|
|
@@ -359,13 +370,23 @@ export default function DocView({ navigate }: PageProps) {
|
|
|
359
370
|
const [renaming, setRenaming] = useState(false);
|
|
360
371
|
const [renameError, setRenameError] = useState<string | null>(null);
|
|
361
372
|
const [renameValue, setRenameValue] = useState("");
|
|
362
|
-
const [
|
|
373
|
+
const [renamePlan, setRenamePlan] = useState<FileRefactorPreviewPlan | null>(
|
|
374
|
+
null
|
|
375
|
+
);
|
|
376
|
+
const [renamePlanLoading, setRenamePlanLoading] = useState(false);
|
|
377
|
+
const [renameConfirmed, setRenameConfirmed] = useState(false);
|
|
378
|
+
const [renameOutcome, setRenameOutcome] = useState<string | null>(null);
|
|
363
379
|
const [moveDialogOpen, setMoveDialogOpen] = useState(false);
|
|
364
380
|
const [moving, setMoving] = useState(false);
|
|
365
381
|
const [moveError, setMoveError] = useState<string | null>(null);
|
|
366
382
|
const [moveFolderPath, setMoveFolderPath] = useState("");
|
|
367
383
|
const [moveName, setMoveName] = useState("");
|
|
368
|
-
const [
|
|
384
|
+
const [movePlan, setMovePlan] = useState<FileRefactorPreviewPlan | null>(
|
|
385
|
+
null
|
|
386
|
+
);
|
|
387
|
+
const [movePlanLoading, setMovePlanLoading] = useState(false);
|
|
388
|
+
const [moveConfirmed, setMoveConfirmed] = useState(false);
|
|
389
|
+
const [moveOutcome, setMoveOutcome] = useState<string | null>(null);
|
|
369
390
|
const [duplicateDialogOpen, setDuplicateDialogOpen] = useState(false);
|
|
370
391
|
const [duplicating, setDuplicating] = useState(false);
|
|
371
392
|
const [duplicateError, setDuplicateError] = useState<string | null>(null);
|
|
@@ -875,7 +896,9 @@ export default function DocView({ navigate }: PageProps) {
|
|
|
875
896
|
const filename = doc.relPath.split("/").pop() ?? doc.relPath;
|
|
876
897
|
setRenameValue(filename);
|
|
877
898
|
setRenameError(null);
|
|
878
|
-
|
|
899
|
+
setRenamePlan(null);
|
|
900
|
+
setRenameConfirmed(false);
|
|
901
|
+
setRenameOutcome(null);
|
|
879
902
|
setRenameDialogOpen(true);
|
|
880
903
|
}, [doc]);
|
|
881
904
|
|
|
@@ -883,7 +906,11 @@ export default function DocView({ navigate }: PageProps) {
|
|
|
883
906
|
if (!renameDialogOpen || !doc || !renameValue.trim()) {
|
|
884
907
|
return;
|
|
885
908
|
}
|
|
886
|
-
|
|
909
|
+
let cancelled = false;
|
|
910
|
+
setRenamePlanLoading(true);
|
|
911
|
+
setRenamePlan(null);
|
|
912
|
+
setRenameConfirmed(false);
|
|
913
|
+
void apiFetch<FileRefactorPreviewPlan>(
|
|
887
914
|
`/api/docs/${encodeURIComponent(doc.docid)}/refactor-plan`,
|
|
888
915
|
{
|
|
889
916
|
method: "POST",
|
|
@@ -893,22 +920,42 @@ export default function DocView({ navigate }: PageProps) {
|
|
|
893
920
|
uri: doc.uri,
|
|
894
921
|
}),
|
|
895
922
|
}
|
|
896
|
-
).then(({ data }) => {
|
|
897
|
-
|
|
923
|
+
).then(({ data, error: err }) => {
|
|
924
|
+
if (cancelled) {
|
|
925
|
+
return;
|
|
926
|
+
}
|
|
927
|
+
setRenamePlanLoading(false);
|
|
928
|
+
if (err) {
|
|
929
|
+
setRenameError(err);
|
|
930
|
+
setRenamePlan(null);
|
|
931
|
+
return;
|
|
932
|
+
}
|
|
933
|
+
setRenameError(null);
|
|
934
|
+
setRenamePlan(data);
|
|
898
935
|
});
|
|
936
|
+
return () => {
|
|
937
|
+
cancelled = true;
|
|
938
|
+
};
|
|
899
939
|
}, [doc, renameDialogOpen, renameValue]);
|
|
900
940
|
|
|
901
941
|
const handleRename = useCallback(async () => {
|
|
902
|
-
if (!doc) {
|
|
942
|
+
if (!doc || !renamePlan?.canApply || !renameConfirmed) {
|
|
903
943
|
return;
|
|
904
944
|
}
|
|
905
945
|
setRenaming(true);
|
|
906
946
|
setRenameError(null);
|
|
947
|
+
setRenameOutcome(null);
|
|
907
948
|
const { data, error: err } = await apiFetch<RenameDocResponse>(
|
|
908
949
|
`/api/docs/${encodeURIComponent(doc.docid)}/rename`,
|
|
909
950
|
{
|
|
910
951
|
method: "POST",
|
|
911
|
-
body: JSON.stringify({
|
|
952
|
+
body: JSON.stringify({
|
|
953
|
+
name: renameValue,
|
|
954
|
+
uri: doc.uri,
|
|
955
|
+
planDigest: renamePlan.planDigest,
|
|
956
|
+
confirmation: FILE_REFACTOR_APPLY_CONFIRMATION,
|
|
957
|
+
schemaVersion: renamePlan.schemaVersion,
|
|
958
|
+
}),
|
|
912
959
|
}
|
|
913
960
|
);
|
|
914
961
|
setRenaming(false);
|
|
@@ -918,11 +965,19 @@ export default function DocView({ navigate }: PageProps) {
|
|
|
918
965
|
return;
|
|
919
966
|
}
|
|
920
967
|
|
|
968
|
+
if (data?.status === "applied_with_sync_pending" || data?.warning) {
|
|
969
|
+
setRenameOutcome(
|
|
970
|
+
data.warning ??
|
|
971
|
+
"Files renamed, but index sync is still pending. Run Update All to finish."
|
|
972
|
+
);
|
|
973
|
+
return;
|
|
974
|
+
}
|
|
975
|
+
|
|
921
976
|
setRenameDialogOpen(false);
|
|
922
977
|
if (data?.uri) {
|
|
923
978
|
navigate(`/doc?uri=${encodeURIComponent(data.uri)}`);
|
|
924
979
|
}
|
|
925
|
-
}, [doc, navigate, renameValue]);
|
|
980
|
+
}, [doc, navigate, renameConfirmed, renamePlan, renameValue]);
|
|
926
981
|
|
|
927
982
|
const handleStartMove = useCallback(() => {
|
|
928
983
|
if (!doc) {
|
|
@@ -931,7 +986,9 @@ export default function DocView({ navigate }: PageProps) {
|
|
|
931
986
|
setMoveFolderPath(getParentPath(doc.relPath));
|
|
932
987
|
setMoveName(doc.relPath.split("/").pop() ?? doc.relPath);
|
|
933
988
|
setMoveError(null);
|
|
934
|
-
|
|
989
|
+
setMovePlan(null);
|
|
990
|
+
setMoveConfirmed(false);
|
|
991
|
+
setMoveOutcome(null);
|
|
935
992
|
setMoveDialogOpen(true);
|
|
936
993
|
}, [doc]);
|
|
937
994
|
|
|
@@ -939,7 +996,11 @@ export default function DocView({ navigate }: PageProps) {
|
|
|
939
996
|
if (!moveDialogOpen || !doc || !moveFolderPath.trim()) {
|
|
940
997
|
return;
|
|
941
998
|
}
|
|
942
|
-
|
|
999
|
+
let cancelled = false;
|
|
1000
|
+
setMovePlanLoading(true);
|
|
1001
|
+
setMovePlan(null);
|
|
1002
|
+
setMoveConfirmed(false);
|
|
1003
|
+
void apiFetch<FileRefactorPreviewPlan>(
|
|
943
1004
|
`/api/docs/${encodeURIComponent(doc.docid)}/refactor-plan`,
|
|
944
1005
|
{
|
|
945
1006
|
method: "POST",
|
|
@@ -950,17 +1011,31 @@ export default function DocView({ navigate }: PageProps) {
|
|
|
950
1011
|
uri: doc.uri,
|
|
951
1012
|
}),
|
|
952
1013
|
}
|
|
953
|
-
).then(({ data }) => {
|
|
954
|
-
|
|
1014
|
+
).then(({ data, error: err }) => {
|
|
1015
|
+
if (cancelled) {
|
|
1016
|
+
return;
|
|
1017
|
+
}
|
|
1018
|
+
setMovePlanLoading(false);
|
|
1019
|
+
if (err) {
|
|
1020
|
+
setMoveError(err);
|
|
1021
|
+
setMovePlan(null);
|
|
1022
|
+
return;
|
|
1023
|
+
}
|
|
1024
|
+
setMoveError(null);
|
|
1025
|
+
setMovePlan(data);
|
|
955
1026
|
});
|
|
1027
|
+
return () => {
|
|
1028
|
+
cancelled = true;
|
|
1029
|
+
};
|
|
956
1030
|
}, [doc, moveDialogOpen, moveFolderPath, moveName]);
|
|
957
1031
|
|
|
958
1032
|
const handleMove = useCallback(async () => {
|
|
959
|
-
if (!doc) {
|
|
1033
|
+
if (!doc || !movePlan?.canApply || !moveConfirmed) {
|
|
960
1034
|
return;
|
|
961
1035
|
}
|
|
962
1036
|
setMoving(true);
|
|
963
1037
|
setMoveError(null);
|
|
1038
|
+
setMoveOutcome(null);
|
|
964
1039
|
const { data, error: err } = await apiFetch<MoveDocResponse>(
|
|
965
1040
|
`/api/docs/${encodeURIComponent(doc.docid)}/move`,
|
|
966
1041
|
{
|
|
@@ -969,6 +1044,9 @@ export default function DocView({ navigate }: PageProps) {
|
|
|
969
1044
|
folderPath: moveFolderPath,
|
|
970
1045
|
name: moveName,
|
|
971
1046
|
uri: doc.uri,
|
|
1047
|
+
planDigest: movePlan.planDigest,
|
|
1048
|
+
confirmation: FILE_REFACTOR_APPLY_CONFIRMATION,
|
|
1049
|
+
schemaVersion: movePlan.schemaVersion,
|
|
972
1050
|
}),
|
|
973
1051
|
}
|
|
974
1052
|
);
|
|
@@ -979,11 +1057,19 @@ export default function DocView({ navigate }: PageProps) {
|
|
|
979
1057
|
return;
|
|
980
1058
|
}
|
|
981
1059
|
|
|
1060
|
+
if (data?.status === "applied_with_sync_pending" || data?.warning) {
|
|
1061
|
+
setMoveOutcome(
|
|
1062
|
+
data.warning ??
|
|
1063
|
+
"Files moved, but index sync is still pending. Run Update All to finish."
|
|
1064
|
+
);
|
|
1065
|
+
return;
|
|
1066
|
+
}
|
|
1067
|
+
|
|
982
1068
|
setMoveDialogOpen(false);
|
|
983
1069
|
if (data?.uri) {
|
|
984
1070
|
navigate(`/doc?uri=${encodeURIComponent(data.uri)}`);
|
|
985
1071
|
}
|
|
986
|
-
}, [doc, moveFolderPath, moveName, navigate]);
|
|
1072
|
+
}, [doc, moveConfirmed, moveFolderPath, moveName, movePlan, navigate]);
|
|
987
1073
|
|
|
988
1074
|
const handleStartDuplicate = useCallback(() => {
|
|
989
1075
|
if (!doc) {
|
|
@@ -2207,39 +2293,61 @@ export default function DocView({ navigate }: PageProps) {
|
|
|
2207
2293
|
</Dialog>
|
|
2208
2294
|
|
|
2209
2295
|
<Dialog onOpenChange={setRenameDialogOpen} open={renameDialogOpen}>
|
|
2210
|
-
<DialogContent>
|
|
2296
|
+
<DialogContent className="sm:max-w-lg">
|
|
2211
2297
|
<DialogHeader>
|
|
2212
2298
|
<DialogTitle>Rename document</DialogTitle>
|
|
2213
2299
|
<DialogDescription>
|
|
2214
|
-
Rename the file on disk inside its current folder.
|
|
2215
|
-
|
|
2300
|
+
Rename the file on disk inside its current folder. Reference
|
|
2301
|
+
rewrites follow the exact plan digest below.
|
|
2216
2302
|
</DialogDescription>
|
|
2217
2303
|
</DialogHeader>
|
|
2218
2304
|
<input
|
|
2305
|
+
aria-label="New file name"
|
|
2219
2306
|
className="w-full rounded-md border border-input bg-transparent px-3 py-2 text-sm"
|
|
2220
2307
|
onChange={(event) => setRenameValue(event.target.value)}
|
|
2221
2308
|
value={renameValue}
|
|
2222
2309
|
/>
|
|
2310
|
+
<RefactorImpactPreview
|
|
2311
|
+
confirmed={renameConfirmed}
|
|
2312
|
+
loading={renamePlanLoading}
|
|
2313
|
+
onConfirmedChange={setRenameConfirmed}
|
|
2314
|
+
outcomeMessage={renameOutcome}
|
|
2315
|
+
outcomeTone="warning"
|
|
2316
|
+
plan={renamePlan}
|
|
2317
|
+
/>
|
|
2223
2318
|
{renameError && (
|
|
2224
|
-
<div
|
|
2319
|
+
<div
|
|
2320
|
+
className="rounded-lg bg-destructive/10 p-3 text-destructive text-sm"
|
|
2321
|
+
role="alert"
|
|
2322
|
+
>
|
|
2225
2323
|
{renameError}
|
|
2226
2324
|
</div>
|
|
2227
2325
|
)}
|
|
2228
|
-
{renameWarnings.length > 0 && (
|
|
2229
|
-
<div className="rounded-lg border border-amber-500/30 bg-amber-500/10 p-3 text-amber-500 text-sm">
|
|
2230
|
-
{renameWarnings.map((warning) => (
|
|
2231
|
-
<div key={warning}>{warning}</div>
|
|
2232
|
-
))}
|
|
2233
|
-
</div>
|
|
2234
|
-
)}
|
|
2235
2326
|
<DialogFooter className="gap-2 sm:gap-0">
|
|
2236
2327
|
<Button
|
|
2237
|
-
onClick={() =>
|
|
2328
|
+
onClick={() => {
|
|
2329
|
+
if (renameOutcome && renamePlan) {
|
|
2330
|
+
setRenameDialogOpen(false);
|
|
2331
|
+
navigate(
|
|
2332
|
+
`/doc?uri=${encodeURIComponent(renamePlan.target.uri)}`
|
|
2333
|
+
);
|
|
2334
|
+
return;
|
|
2335
|
+
}
|
|
2336
|
+
setRenameDialogOpen(false);
|
|
2337
|
+
}}
|
|
2238
2338
|
variant="outline"
|
|
2239
2339
|
>
|
|
2240
|
-
Cancel
|
|
2340
|
+
{renameOutcome ? "Open note" : "Cancel"}
|
|
2241
2341
|
</Button>
|
|
2242
|
-
<Button
|
|
2342
|
+
<Button
|
|
2343
|
+
disabled={
|
|
2344
|
+
renaming ||
|
|
2345
|
+
!renamePlan?.canApply ||
|
|
2346
|
+
!renameConfirmed ||
|
|
2347
|
+
Boolean(renameOutcome)
|
|
2348
|
+
}
|
|
2349
|
+
onClick={() => void handleRename()}
|
|
2350
|
+
>
|
|
2243
2351
|
{renaming && (
|
|
2244
2352
|
<Loader2Icon className="mr-1.5 size-4 animate-spin" />
|
|
2245
2353
|
)}
|
|
@@ -2250,42 +2358,69 @@ export default function DocView({ navigate }: PageProps) {
|
|
|
2250
2358
|
</Dialog>
|
|
2251
2359
|
|
|
2252
2360
|
<Dialog onOpenChange={setMoveDialogOpen} open={moveDialogOpen}>
|
|
2253
|
-
<DialogContent>
|
|
2361
|
+
<DialogContent className="sm:max-w-lg">
|
|
2254
2362
|
<DialogHeader>
|
|
2255
2363
|
<DialogTitle>Move document</DialogTitle>
|
|
2256
2364
|
<DialogDescription>
|
|
2257
2365
|
Move the current note to another folder inside this collection.
|
|
2366
|
+
Reference rewrites follow the exact plan digest below.
|
|
2258
2367
|
</DialogDescription>
|
|
2259
2368
|
</DialogHeader>
|
|
2260
2369
|
<div className="space-y-3">
|
|
2261
2370
|
<Input
|
|
2371
|
+
aria-label="Destination folder path"
|
|
2262
2372
|
onChange={(event) => setMoveFolderPath(event.target.value)}
|
|
2263
2373
|
placeholder="projects/research"
|
|
2264
2374
|
value={moveFolderPath}
|
|
2265
2375
|
/>
|
|
2266
2376
|
<Input
|
|
2377
|
+
aria-label="File name"
|
|
2267
2378
|
onChange={(event) => setMoveName(event.target.value)}
|
|
2268
2379
|
placeholder="note.md"
|
|
2269
2380
|
value={moveName}
|
|
2270
2381
|
/>
|
|
2382
|
+
<RefactorImpactPreview
|
|
2383
|
+
confirmed={moveConfirmed}
|
|
2384
|
+
loading={movePlanLoading}
|
|
2385
|
+
onConfirmedChange={setMoveConfirmed}
|
|
2386
|
+
outcomeMessage={moveOutcome}
|
|
2387
|
+
outcomeTone="warning"
|
|
2388
|
+
plan={movePlan}
|
|
2389
|
+
/>
|
|
2271
2390
|
{moveError && (
|
|
2272
|
-
<div
|
|
2391
|
+
<div
|
|
2392
|
+
className="rounded-lg bg-destructive/10 p-3 text-destructive text-sm"
|
|
2393
|
+
role="alert"
|
|
2394
|
+
>
|
|
2273
2395
|
{moveError}
|
|
2274
2396
|
</div>
|
|
2275
2397
|
)}
|
|
2276
|
-
{moveWarnings.length > 0 && (
|
|
2277
|
-
<div className="rounded-lg border border-amber-500/30 bg-amber-500/10 p-3 text-amber-500 text-sm">
|
|
2278
|
-
{moveWarnings.map((warning) => (
|
|
2279
|
-
<div key={warning}>{warning}</div>
|
|
2280
|
-
))}
|
|
2281
|
-
</div>
|
|
2282
|
-
)}
|
|
2283
2398
|
</div>
|
|
2284
2399
|
<DialogFooter className="gap-2 sm:gap-0">
|
|
2285
|
-
<Button
|
|
2286
|
-
|
|
2400
|
+
<Button
|
|
2401
|
+
onClick={() => {
|
|
2402
|
+
if (moveOutcome && movePlan) {
|
|
2403
|
+
setMoveDialogOpen(false);
|
|
2404
|
+
navigate(
|
|
2405
|
+
`/doc?uri=${encodeURIComponent(movePlan.target.uri)}`
|
|
2406
|
+
);
|
|
2407
|
+
return;
|
|
2408
|
+
}
|
|
2409
|
+
setMoveDialogOpen(false);
|
|
2410
|
+
}}
|
|
2411
|
+
variant="outline"
|
|
2412
|
+
>
|
|
2413
|
+
{moveOutcome ? "Open note" : "Cancel"}
|
|
2287
2414
|
</Button>
|
|
2288
|
-
<Button
|
|
2415
|
+
<Button
|
|
2416
|
+
disabled={
|
|
2417
|
+
moving ||
|
|
2418
|
+
!movePlan?.canApply ||
|
|
2419
|
+
!moveConfirmed ||
|
|
2420
|
+
Boolean(moveOutcome)
|
|
2421
|
+
}
|
|
2422
|
+
onClick={() => void handleMove()}
|
|
2423
|
+
>
|
|
2289
2424
|
{moving && <Loader2Icon className="mr-1.5 size-4 animate-spin" />}
|
|
2290
2425
|
Move
|
|
2291
2426
|
</Button>
|