@agent-native/core 0.164.11 → 0.164.15
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/corpus/README.md +1 -1
- package/corpus/templates/calendar/.agents/skills/event-management/SKILL.md +84 -0
- package/corpus/templates/calendar/AGENTS.md +5 -0
- package/corpus/templates/calendar/actions/delete-event.ts +1 -1
- package/corpus/templates/calendar/actions/delete-events.ts +508 -0
- package/corpus/templates/calendar/actions/event-action-helpers.ts +1 -1
- package/corpus/templates/calendar/actions/list-events.ts +53 -0
- package/corpus/templates/calendar/changelog/2026-08-18-the-agent-can-now-remove-many-meetings-at-once-such-as-every.md +6 -0
- package/corpus/templates/calendar/server/lib/event-weekday.ts +112 -0
- package/corpus/templates/content/actions/_database-source-utils.ts +25 -2
- package/corpus/templates/content/actions/_database-utils.ts +2 -0
- package/corpus/templates/content/actions/_local-folder-source.ts +186 -2
- package/corpus/templates/content/actions/connect-local-folder-source.ts +23 -8
- package/corpus/templates/content/actions/disconnect-local-folder-source.ts +3 -3
- package/corpus/templates/content/actions/resolve-local-folder-conflict.ts +2 -2
- package/corpus/templates/content/actions/sync-local-folder-source.ts +130 -12
- package/corpus/templates/content/app/components/LocalFolderLiveSync.tsx +77 -0
- package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +354 -8
- package/corpus/templates/content/app/components/editor/DocumentToolbar.tsx +23 -1
- package/corpus/templates/content/app/components/editor/VisualEditor.tsx +78 -1
- package/corpus/templates/content/app/components/editor/database/sidebar.tsx +36 -0
- package/corpus/templates/content/app/components/editor/local-source-write-state.ts +52 -0
- package/corpus/templates/content/app/components/sidebar/DocumentSidebar.tsx +244 -6
- package/corpus/templates/content/app/components/sidebar/local-source-hierarchy.ts +189 -0
- package/corpus/templates/content/app/hooks/use-create-page.ts +26 -4
- package/corpus/templates/content/app/hooks/use-documents.ts +83 -0
- package/corpus/templates/content/app/i18n-data.ts +79 -0
- package/corpus/templates/content/app/lib/desktop-content-files.ts +57 -1
- package/corpus/templates/content/app/lib/local-content-source-files.ts +210 -34
- package/corpus/templates/content/app/lib/local-folder-live-sync.ts +191 -0
- package/corpus/templates/content/app/root.tsx +2 -1
- package/corpus/templates/content/app/routes/_app.local-files.tsx +103 -38
- package/corpus/templates/content/docs/product/capabilities/content.source.file-folder.md +2 -2
- package/corpus/templates/content/docs/product/features/content.feature.bring-your-local-work.md +3 -3
- package/corpus/templates/content/docs/product/roadmap.md +2 -2
- package/corpus/templates/content/docs/solutions/2026-08-18-content-navigation-coherence-shape.md +482 -0
- package/corpus/templates/content/shared/api.ts +25 -0
- package/corpus/templates/factory/AGENTS.md +2 -1
- package/corpus/templates/factory/actions/get-factory-graph-version.ts +69 -0
- package/corpus/templates/factory/actions/list-factory-graph-versions.ts +82 -0
- package/corpus/templates/factory/actions/restore-factory-graph-version.ts +163 -0
- package/corpus/templates/factory/actions/save-factory-graph.ts +93 -60
- package/corpus/templates/factory/app/components/factory/FactoryHistoryView.tsx +709 -0
- package/corpus/templates/factory/app/components/factory/FactoryInspector.tsx +62 -2
- package/corpus/templates/factory/app/components/ui/alert-dialog.tsx +1 -0
- package/corpus/templates/factory/app/i18n/en-US.ts +43 -0
- package/corpus/templates/factory/app/routes/factory.tsx +115 -15
- package/corpus/templates/factory/server/plugins/agent-chat.ts +3 -1
- package/dist/app-config/auth.d.ts +4 -0
- package/dist/app-config/auth.js +7 -0
- package/dist/app-config/schema.d.ts +3 -0
- package/dist/app-config/schema.js +2 -0
- package/dist/cli/code-agent-executor.js +6 -1
- package/dist/cli/code-agent-mcp-config.d.ts +2 -2
- package/dist/cli/code-agent-mcp-config.js +34 -9
- package/dist/client/org/OrgSwitcher.js +8 -4
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/routes.d.ts +1 -1
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/mcp-client/config.d.ts +11 -0
- package/dist/mcp-client/config.js +33 -26
- package/dist/notifications/routes.d.ts +3 -3
- package/dist/observability/routes.d.ts +3 -3
- package/dist/progress/routes.d.ts +1 -1
- package/dist/resources/handlers.d.ts +2 -2
- package/dist/secrets/routes.d.ts +9 -9
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/auth.js +15 -1
- package/dist/server/realtime-token.d.ts +1 -1
- package/docs/content/actions-access-control.mdx +136 -2
- package/docs/content/actions-agent-tools.mdx +101 -0
- package/docs/content/actions-defining.mdx +2 -2
- package/docs/content/audit-log.mdx +1 -1
- package/docs/content/authentication.mdx +1 -1
- package/docs/content/cloudflare.mdx +43 -0
- package/docs/content/creating-templates.mdx +1 -1
- package/docs/content/deploy-an-app.mdx +109 -0
- package/docs/content/deployment-environment-variables.mdx +101 -0
- package/docs/content/deployment.mdx +125 -792
- package/docs/content/docs-components.mdx +2 -2
- package/docs/content/environment-variables.mdx +41 -41
- package/docs/content/file-uploads.mdx +74 -40
- package/docs/content/harness-agents.mdx +2 -2
- package/docs/content/internationalization.mdx +61 -65
- package/docs/content/key-concepts.mdx +3 -3
- package/docs/content/multi-app-workspace.mdx +2 -2
- package/docs/content/netlify.mdx +47 -0
- package/docs/content/node-docker.mdx +138 -0
- package/docs/content/organizations-teams-permissions.mdx +5 -5
- package/docs/content/other-platforms.mdx +27 -0
- package/docs/content/ssr-caching.mdx +45 -0
- package/docs/content/template-assets-developers.mdx +2 -2
- package/docs/content/template-chat-developers.mdx +2 -2
- package/docs/content/template-content-developers.mdx +2 -2
- package/docs/content/{local-file-mode.mdx → template-content-local-files.mdx} +2 -1
- package/docs/content/template-content-sync.mdx +2 -2
- package/docs/content/toolkit-agent-ux.mdx +1 -1
- package/docs/content/toolkit-resources.mdx +2 -2
- package/docs/content/updating-ui-in-production.mdx +35 -0
- package/docs/content/vercel.mdx +38 -0
- package/docs/content/what-is-agent-native.mdx +1 -1
- package/docs/content/workspace-deployment.mdx +350 -0
- package/package.json +1 -1
- package/docs/content/agents.mdx +0 -9
- package/docs/content/database.mdx +0 -317
- package/docs/content/human-approval.mdx +0 -156
|
@@ -771,6 +771,20 @@ interface VisualEditorProps {
|
|
|
771
771
|
* whose type has no NFM analog (via the shared registry-block side-map).
|
|
772
772
|
*/
|
|
773
773
|
notionPageId?: string | null;
|
|
774
|
+
onHistoryControllerChange?: (
|
|
775
|
+
controller: VisualEditorHistoryController | null,
|
|
776
|
+
) => void;
|
|
777
|
+
onHistoryStateChange?: (state: VisualEditorHistoryState) => void;
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
export interface VisualEditorHistoryState {
|
|
781
|
+
canUndo: boolean;
|
|
782
|
+
canRedo: boolean;
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
export interface VisualEditorHistoryController {
|
|
786
|
+
undo: () => boolean;
|
|
787
|
+
redo: () => boolean;
|
|
774
788
|
}
|
|
775
789
|
|
|
776
790
|
export type { NotionPageLink };
|
|
@@ -968,6 +982,27 @@ function isActiveSlashCommandDraft(editor: CoreEditor): boolean {
|
|
|
968
982
|
return /^\s*\/[a-zA-Z0-9]*$/.test(textBefore);
|
|
969
983
|
}
|
|
970
984
|
|
|
985
|
+
export function runPersistableHistoryCommand(
|
|
986
|
+
editor: CoreEditor,
|
|
987
|
+
command: "undo" | "redo",
|
|
988
|
+
): boolean {
|
|
989
|
+
let applied = false;
|
|
990
|
+
for (let index = 0; index < 20; index += 1) {
|
|
991
|
+
const changed = editor
|
|
992
|
+
.chain()
|
|
993
|
+
.focus()
|
|
994
|
+
.command(({ tr }) => {
|
|
995
|
+
tr.setMeta(LOCAL_FILE_USER_EDIT_META, true);
|
|
996
|
+
return true;
|
|
997
|
+
})
|
|
998
|
+
[command]()
|
|
999
|
+
.run();
|
|
1000
|
+
applied ||= changed;
|
|
1001
|
+
if (!changed || !isActiveSlashCommandDraft(editor)) break;
|
|
1002
|
+
}
|
|
1003
|
+
return applied;
|
|
1004
|
+
}
|
|
1005
|
+
|
|
971
1006
|
interface VisualEditorExtensionOptions {
|
|
972
1007
|
documentId?: string;
|
|
973
1008
|
ydoc?: YDoc | null;
|
|
@@ -1957,6 +1992,8 @@ export function VisualEditor({
|
|
|
1957
1992
|
notionPageLinks = [],
|
|
1958
1993
|
onOpenNotionPageLink,
|
|
1959
1994
|
notionPageId,
|
|
1995
|
+
onHistoryControllerChange,
|
|
1996
|
+
onHistoryStateChange,
|
|
1960
1997
|
}: VisualEditorProps) {
|
|
1961
1998
|
const t = useT();
|
|
1962
1999
|
const [isDraggingMedia, setIsDraggingMedia] = useState(false);
|
|
@@ -1967,6 +2004,8 @@ export function VisualEditor({
|
|
|
1967
2004
|
onChangeRef.current = onChange;
|
|
1968
2005
|
const onSaveContentRef = useRef(onSaveContent);
|
|
1969
2006
|
onSaveContentRef.current = onSaveContent;
|
|
2007
|
+
const onHistoryStateChangeRef = useRef(onHistoryStateChange);
|
|
2008
|
+
onHistoryStateChangeRef.current = onHistoryStateChange;
|
|
1970
2009
|
const notionPageLinksRef = useRef(notionPageLinks);
|
|
1971
2010
|
notionPageLinksRef.current = notionPageLinks;
|
|
1972
2011
|
const onMediaSourceCommittedRef = useRef<
|
|
@@ -2150,6 +2189,7 @@ export function VisualEditor({
|
|
|
2150
2189
|
}
|
|
2151
2190
|
};
|
|
2152
2191
|
|
|
2192
|
+
const historyEditorRef = useRef<CoreEditor | null>(null);
|
|
2153
2193
|
const editor = useEditor({
|
|
2154
2194
|
extensions,
|
|
2155
2195
|
// With Collaboration (ydoc) active, content is owned by the Y.XmlFragment —
|
|
@@ -2240,8 +2280,22 @@ export function VisualEditor({
|
|
|
2240
2280
|
if (view.editable) markUserEditIntent();
|
|
2241
2281
|
return false;
|
|
2242
2282
|
},
|
|
2243
|
-
keydown(view) {
|
|
2283
|
+
keydown(view, event) {
|
|
2244
2284
|
if (view.editable) markUserEditIntent();
|
|
2285
|
+
if (
|
|
2286
|
+
view.editable &&
|
|
2287
|
+
(event.metaKey || event.ctrlKey) &&
|
|
2288
|
+
!event.altKey &&
|
|
2289
|
+
event.key.toLowerCase() === "z" &&
|
|
2290
|
+
historyEditorRef.current
|
|
2291
|
+
) {
|
|
2292
|
+
event.preventDefault();
|
|
2293
|
+
runPersistableHistoryCommand(
|
|
2294
|
+
historyEditorRef.current,
|
|
2295
|
+
event.shiftKey ? "redo" : "undo",
|
|
2296
|
+
);
|
|
2297
|
+
return true;
|
|
2298
|
+
}
|
|
2245
2299
|
return false;
|
|
2246
2300
|
},
|
|
2247
2301
|
cut(view) {
|
|
@@ -2276,6 +2330,12 @@ export function VisualEditor({
|
|
|
2276
2330
|
},
|
|
2277
2331
|
},
|
|
2278
2332
|
editable,
|
|
2333
|
+
onTransaction: ({ editor }) => {
|
|
2334
|
+
onHistoryStateChangeRef.current?.({
|
|
2335
|
+
canUndo: editor.can().undo(),
|
|
2336
|
+
canRedo: editor.can().redo(),
|
|
2337
|
+
});
|
|
2338
|
+
},
|
|
2279
2339
|
onUpdate: ({ editor, transaction }) => {
|
|
2280
2340
|
const guards = guardsRef.current;
|
|
2281
2341
|
// `shouldIgnoreUpdate` covers: not editable, mid-programmatic setContent,
|
|
@@ -2326,6 +2386,23 @@ export function VisualEditor({
|
|
|
2326
2386
|
});
|
|
2327
2387
|
},
|
|
2328
2388
|
});
|
|
2389
|
+
historyEditorRef.current = editor;
|
|
2390
|
+
|
|
2391
|
+
useEffect(() => {
|
|
2392
|
+
if (!editor) {
|
|
2393
|
+
onHistoryControllerChange?.(null);
|
|
2394
|
+
return;
|
|
2395
|
+
}
|
|
2396
|
+
onHistoryControllerChange?.({
|
|
2397
|
+
undo: () => runPersistableHistoryCommand(editor, "undo"),
|
|
2398
|
+
redo: () => runPersistableHistoryCommand(editor, "redo"),
|
|
2399
|
+
});
|
|
2400
|
+
onHistoryStateChange?.({
|
|
2401
|
+
canUndo: editor.can().undo(),
|
|
2402
|
+
canRedo: editor.can().redo(),
|
|
2403
|
+
});
|
|
2404
|
+
return () => onHistoryControllerChange?.(null);
|
|
2405
|
+
}, [editor, onHistoryControllerChange, onHistoryStateChange]);
|
|
2329
2406
|
|
|
2330
2407
|
const handleImageFileInputChange = useCallback(
|
|
2331
2408
|
async (event: Event) => {
|
|
@@ -14,6 +14,8 @@ import {
|
|
|
14
14
|
IconDatabase,
|
|
15
15
|
IconDots,
|
|
16
16
|
IconFileText,
|
|
17
|
+
IconFolder,
|
|
18
|
+
IconFolderOpen,
|
|
17
19
|
IconPlus,
|
|
18
20
|
IconStar,
|
|
19
21
|
IconTrash,
|
|
@@ -266,6 +268,7 @@ export function ContentFilesSidebarView({
|
|
|
266
268
|
: undefined;
|
|
267
269
|
const manualReorderEnabled =
|
|
268
270
|
Boolean(manualReorder) &&
|
|
271
|
+
!items.some((item) => item.document.source?.kind === "folder") &&
|
|
269
272
|
(sidebarOrder?.mode ?? "custom") === "custom" &&
|
|
270
273
|
activeView.sorts.length === 0 &&
|
|
271
274
|
activeView.filters.length === 0 &&
|
|
@@ -746,6 +749,39 @@ function DatabaseSidebarRow({
|
|
|
746
749
|
|
|
747
750
|
const title = item.document.title || untitledLabel;
|
|
748
751
|
|
|
752
|
+
if (item.document.source?.kind === "folder") {
|
|
753
|
+
return (
|
|
754
|
+
<div className="group relative min-w-0">
|
|
755
|
+
<button
|
|
756
|
+
type="button"
|
|
757
|
+
className="flex h-7 w-full min-w-0 items-center gap-1.5 rounded pe-1.5 text-start text-sm text-foreground/85 hover:bg-muted hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
758
|
+
style={{
|
|
759
|
+
paddingInlineStart: `${databaseSidebarRowIndent(depth, hasChildren)}px`,
|
|
760
|
+
}}
|
|
761
|
+
aria-label={`${expanded ? t("sidebar.collapse") : t("sidebar.expand")} ${title}`}
|
|
762
|
+
aria-expanded={expanded}
|
|
763
|
+
onClick={() => onToggleExpanded?.(!expanded)}
|
|
764
|
+
onPointerUp={(event) => event.currentTarget.blur()}
|
|
765
|
+
>
|
|
766
|
+
<span className="flex size-7 shrink-0 items-center justify-center text-muted-foreground">
|
|
767
|
+
<IconChevronRight
|
|
768
|
+
className={cn(
|
|
769
|
+
"size-3.5 transition-transform",
|
|
770
|
+
expanded && "rotate-90",
|
|
771
|
+
)}
|
|
772
|
+
/>
|
|
773
|
+
</span>
|
|
774
|
+
{expanded ? (
|
|
775
|
+
<IconFolderOpen className="size-3.5 shrink-0 text-muted-foreground" />
|
|
776
|
+
) : (
|
|
777
|
+
<IconFolder className="size-3.5 shrink-0 text-muted-foreground" />
|
|
778
|
+
)}
|
|
779
|
+
<span className="min-w-0 flex-1 truncate">{title}</span>
|
|
780
|
+
</button>
|
|
781
|
+
</div>
|
|
782
|
+
);
|
|
783
|
+
}
|
|
784
|
+
|
|
749
785
|
return (
|
|
750
786
|
<>
|
|
751
787
|
<div className="group relative min-w-0">
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
export interface PendingLocalSourceWrite {
|
|
2
|
+
title: string;
|
|
3
|
+
content: string;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export type LocalSourceReadDisposition =
|
|
7
|
+
| "pending-self-write"
|
|
8
|
+
| "conflict"
|
|
9
|
+
| "external-change"
|
|
10
|
+
| "unchanged";
|
|
11
|
+
|
|
12
|
+
export function localSourceRevisionForSave(
|
|
13
|
+
queuedRevision: string | null | undefined,
|
|
14
|
+
currentRevision: string | undefined,
|
|
15
|
+
) {
|
|
16
|
+
return queuedRevision === undefined ? currentRevision : queuedRevision;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function localSourceRevisionForQueuedEdit(
|
|
20
|
+
pendingRevision: string | null | undefined,
|
|
21
|
+
currentRevision: string | undefined,
|
|
22
|
+
) {
|
|
23
|
+
return pendingRevision === undefined
|
|
24
|
+
? (currentRevision ?? null)
|
|
25
|
+
: pendingRevision;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function classifyLocalSourceRead(args: {
|
|
29
|
+
diskTitle: string;
|
|
30
|
+
diskContent: string;
|
|
31
|
+
localContent: string;
|
|
32
|
+
lastSavedTitle: string;
|
|
33
|
+
lastSavedContent: string;
|
|
34
|
+
pendingWrite: PendingLocalSourceWrite | null;
|
|
35
|
+
hasPendingSave?: boolean;
|
|
36
|
+
}): LocalSourceReadDisposition {
|
|
37
|
+
if (
|
|
38
|
+
args.pendingWrite?.title === args.diskTitle &&
|
|
39
|
+
args.pendingWrite.content === args.diskContent
|
|
40
|
+
) {
|
|
41
|
+
return "pending-self-write";
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const hasUnsavedEdit =
|
|
45
|
+
args.hasPendingSave === true || args.localContent !== args.lastSavedContent;
|
|
46
|
+
const diskChanged =
|
|
47
|
+
args.diskContent !== args.lastSavedContent ||
|
|
48
|
+
args.diskTitle !== args.lastSavedTitle;
|
|
49
|
+
|
|
50
|
+
if (hasUnsavedEdit && diskChanged) return "conflict";
|
|
51
|
+
return diskChanged ? "external-change" : "unchanged";
|
|
52
|
+
}
|
|
@@ -51,6 +51,7 @@ import {
|
|
|
51
51
|
IconChevronRight,
|
|
52
52
|
IconDots,
|
|
53
53
|
IconTrash,
|
|
54
|
+
IconGitBranch,
|
|
54
55
|
} from "@tabler/icons-react";
|
|
55
56
|
import { useQueryClient } from "@tanstack/react-query";
|
|
56
57
|
import {
|
|
@@ -127,8 +128,20 @@ import {
|
|
|
127
128
|
buildDocumentTree,
|
|
128
129
|
filterDocumentTreeDocuments,
|
|
129
130
|
documentQueryFilter,
|
|
131
|
+
rollbackOptimisticCreatedDocument,
|
|
132
|
+
restoreDeletedDocumentSnapshots,
|
|
130
133
|
} from "@/hooks/use-documents";
|
|
131
134
|
import { useLocalStorage } from "@/hooks/use-local-storage";
|
|
135
|
+
import {
|
|
136
|
+
getDesktopContentFiles,
|
|
137
|
+
type DesktopContentFilesFolder,
|
|
138
|
+
} from "@/lib/desktop-content-files";
|
|
139
|
+
import {
|
|
140
|
+
consumeLiveLocalFolderActivation,
|
|
141
|
+
liveLocalFolderSourceId,
|
|
142
|
+
pendingLiveLocalFolderActivation,
|
|
143
|
+
subscribeLiveLocalFolderActivation,
|
|
144
|
+
} from "@/lib/local-folder-live-sync";
|
|
132
145
|
import {
|
|
133
146
|
markDocumentCreationPending,
|
|
134
147
|
shouldCreateDocumentOptimistically,
|
|
@@ -140,6 +153,11 @@ import {
|
|
|
140
153
|
isDirectLocalDocument,
|
|
141
154
|
} from "./document-sidebar-sections";
|
|
142
155
|
import { DocumentSidebarIcon, DocumentTreeItem } from "./DocumentTreeItem";
|
|
156
|
+
import {
|
|
157
|
+
firstLocalSourceDocumentId,
|
|
158
|
+
localSourceItemIdentity,
|
|
159
|
+
projectLocalSourceHierarchy,
|
|
160
|
+
} from "./local-source-hierarchy";
|
|
143
161
|
import {
|
|
144
162
|
contentSpaceAvailability,
|
|
145
163
|
contentSpaceForStoredSelection,
|
|
@@ -433,6 +451,57 @@ function WorkspaceSidebarItem({
|
|
|
433
451
|
onToggleFavorite: (item: ContentDatabaseItem) => void;
|
|
434
452
|
}) {
|
|
435
453
|
const t = useT();
|
|
454
|
+
const [localWorkingCopies, setLocalWorkingCopies] = useState<
|
|
455
|
+
DesktopContentFilesFolder[]
|
|
456
|
+
>([]);
|
|
457
|
+
const [selectedWorkingCopyId, setSelectedWorkingCopyId] = useState<
|
|
458
|
+
string | null
|
|
459
|
+
>(null);
|
|
460
|
+
useEffect(() => {
|
|
461
|
+
const desktop = getDesktopContentFiles();
|
|
462
|
+
if (!desktop) return;
|
|
463
|
+
let active = true;
|
|
464
|
+
const refresh = async (preferFolderId?: string) => {
|
|
465
|
+
const result = await desktop.getFolder(
|
|
466
|
+
preferFolderId ? { folderId: preferFolderId } : undefined,
|
|
467
|
+
);
|
|
468
|
+
if (!active || !result.ok) return;
|
|
469
|
+
const folders = result.folders ?? [result.folder];
|
|
470
|
+
const main = folders.find((folder) => folder.kind !== "temporary");
|
|
471
|
+
const repositoryId = main?.repository?.localId;
|
|
472
|
+
const related = repositoryId
|
|
473
|
+
? folders.filter(
|
|
474
|
+
(folder) => folder.repository?.localId === repositoryId,
|
|
475
|
+
)
|
|
476
|
+
: folders;
|
|
477
|
+
setLocalWorkingCopies(related);
|
|
478
|
+
setSelectedWorkingCopyId((current) =>
|
|
479
|
+
preferFolderId && related.some((folder) => folder.id === preferFolderId)
|
|
480
|
+
? preferFolderId
|
|
481
|
+
: current && related.some((folder) => folder.id === current)
|
|
482
|
+
? current
|
|
483
|
+
: (main?.id ?? related[0]?.id ?? null),
|
|
484
|
+
);
|
|
485
|
+
if (
|
|
486
|
+
preferFolderId &&
|
|
487
|
+
related.some((folder) => folder.id === preferFolderId)
|
|
488
|
+
) {
|
|
489
|
+
consumeLiveLocalFolderActivation(preferFolderId);
|
|
490
|
+
}
|
|
491
|
+
};
|
|
492
|
+
void refresh(pendingLiveLocalFolderActivation() ?? undefined);
|
|
493
|
+
const remove = desktop.onChange?.((event) => {
|
|
494
|
+
void refresh(event.reason === "attached" ? event.folderId : undefined);
|
|
495
|
+
});
|
|
496
|
+
const removeActivation = subscribeLiveLocalFolderActivation((folderId) => {
|
|
497
|
+
void refresh(folderId);
|
|
498
|
+
});
|
|
499
|
+
return () => {
|
|
500
|
+
active = false;
|
|
501
|
+
remove?.();
|
|
502
|
+
removeActivation();
|
|
503
|
+
};
|
|
504
|
+
}, []);
|
|
436
505
|
const activeFilesDatabaseId = useDeferredFilesDatabaseId(
|
|
437
506
|
space.filesDatabaseId,
|
|
438
507
|
expanded,
|
|
@@ -442,6 +511,89 @@ function WorkspaceSidebarItem({
|
|
|
442
511
|
const filesDatabaseData = isContentDatabaseUnavailable(filesDatabase.data)
|
|
443
512
|
? undefined
|
|
444
513
|
: filesDatabase.data;
|
|
514
|
+
const workspaceSourceIds = new Set(
|
|
515
|
+
filesDatabaseData?.items.flatMap((item) => {
|
|
516
|
+
const identity = localSourceItemIdentity(item);
|
|
517
|
+
return identity ? [identity] : [];
|
|
518
|
+
}) ?? [],
|
|
519
|
+
);
|
|
520
|
+
const workspaceRootPaths = new Set(
|
|
521
|
+
filesDatabaseData?.items.flatMap((item) => {
|
|
522
|
+
const rootPath = item.document.source?.rootPath;
|
|
523
|
+
return rootPath ? [rootPath] : [];
|
|
524
|
+
}) ?? [],
|
|
525
|
+
);
|
|
526
|
+
const relatedWorkingCopies = localWorkingCopies.filter((folder) => {
|
|
527
|
+
const sourceId = folder.id ? liveLocalFolderSourceId(folder.id) : null;
|
|
528
|
+
const rootPath =
|
|
529
|
+
folder.kind === "temporary"
|
|
530
|
+
? folder.name
|
|
531
|
+
: (folder.sourcePrefix ?? folder.name);
|
|
532
|
+
return (
|
|
533
|
+
(typeof sourceId === "string" && workspaceSourceIds.has(sourceId)) ||
|
|
534
|
+
workspaceRootPaths.has(folder.id ?? rootPath) ||
|
|
535
|
+
workspaceRootPaths.has(rootPath)
|
|
536
|
+
);
|
|
537
|
+
});
|
|
538
|
+
const selectedWorkingCopy =
|
|
539
|
+
relatedWorkingCopies.find(
|
|
540
|
+
(folder) => folder.id === selectedWorkingCopyId,
|
|
541
|
+
) ??
|
|
542
|
+
relatedWorkingCopies.find((folder) => folder.kind !== "temporary") ??
|
|
543
|
+
relatedWorkingCopies[0];
|
|
544
|
+
const selectedSourceRoot = selectedWorkingCopy
|
|
545
|
+
? (selectedWorkingCopy.id ??
|
|
546
|
+
(selectedWorkingCopy.kind === "temporary"
|
|
547
|
+
? selectedWorkingCopy.name
|
|
548
|
+
: (selectedWorkingCopy.sourcePrefix ?? selectedWorkingCopy.name)))
|
|
549
|
+
: undefined;
|
|
550
|
+
const selectedSourceId = selectedWorkingCopy?.id
|
|
551
|
+
? liveLocalFolderSourceId(selectedWorkingCopy.id)
|
|
552
|
+
: undefined;
|
|
553
|
+
const workingCopySourceIds = new Set(
|
|
554
|
+
relatedWorkingCopies.flatMap((folder) => {
|
|
555
|
+
const sourceId = folder.id ? liveLocalFolderSourceId(folder.id) : null;
|
|
556
|
+
return sourceId ? [sourceId] : [];
|
|
557
|
+
}),
|
|
558
|
+
);
|
|
559
|
+
const hasRelatedLocalFiles = filesDatabaseData?.items.some(
|
|
560
|
+
(item) =>
|
|
561
|
+
item.document.source?.mode === "local-files" &&
|
|
562
|
+
(workingCopySourceIds.has(localSourceItemIdentity(item) ?? "") ||
|
|
563
|
+
relatedWorkingCopies.some(
|
|
564
|
+
(folder) =>
|
|
565
|
+
item.document.source?.rootPath ===
|
|
566
|
+
(folder.id ??
|
|
567
|
+
(folder.kind === "temporary"
|
|
568
|
+
? folder.name
|
|
569
|
+
: (folder.sourcePrefix ?? folder.name))),
|
|
570
|
+
)),
|
|
571
|
+
);
|
|
572
|
+
const visibleFilesDatabaseData = filesDatabaseData
|
|
573
|
+
? {
|
|
574
|
+
...filesDatabaseData,
|
|
575
|
+
items: projectLocalSourceHierarchy(filesDatabaseData.items, {
|
|
576
|
+
sourceId: selectedSourceId,
|
|
577
|
+
rootPath: selectedSourceRoot,
|
|
578
|
+
}),
|
|
579
|
+
}
|
|
580
|
+
: undefined;
|
|
581
|
+
const openedWorkingCopyIdRef = useRef<string | null>(null);
|
|
582
|
+
useEffect(() => {
|
|
583
|
+
if (
|
|
584
|
+
selectedWorkingCopy?.kind !== "temporary" ||
|
|
585
|
+
!selectedWorkingCopy.id ||
|
|
586
|
+
openedWorkingCopyIdRef.current === selectedWorkingCopy.id
|
|
587
|
+
) {
|
|
588
|
+
return;
|
|
589
|
+
}
|
|
590
|
+
const firstDocumentId = firstLocalSourceDocumentId(
|
|
591
|
+
visibleFilesDatabaseData?.items ?? [],
|
|
592
|
+
);
|
|
593
|
+
if (!firstDocumentId) return;
|
|
594
|
+
openedWorkingCopyIdRef.current = selectedWorkingCopy.id;
|
|
595
|
+
onActivate(space, firstDocumentId);
|
|
596
|
+
}, [onActivate, selectedWorkingCopy, space, visibleFilesDatabaseData]);
|
|
445
597
|
const resolvedFilesDatabaseId = filesDatabaseData?.database.id ?? null;
|
|
446
598
|
const filesPersonalView = useContentDatabasePersonalView(
|
|
447
599
|
resolvedFilesDatabaseId,
|
|
@@ -605,6 +757,62 @@ function WorkspaceSidebarItem({
|
|
|
605
757
|
</div>
|
|
606
758
|
{expanded ? (
|
|
607
759
|
<div className="min-w-0 pb-1 ps-4">
|
|
760
|
+
{relatedWorkingCopies.some((folder) => folder.kind === "temporary") &&
|
|
761
|
+
selectedWorkingCopy &&
|
|
762
|
+
hasRelatedLocalFiles ? (
|
|
763
|
+
<DropdownMenu>
|
|
764
|
+
<DropdownMenuTrigger asChild>
|
|
765
|
+
<button
|
|
766
|
+
type="button"
|
|
767
|
+
className="mb-1 flex min-h-8 w-full min-w-0 items-center gap-2 rounded-md px-2 text-start text-xs hover:bg-accent/40"
|
|
768
|
+
title={selectedWorkingCopy.updatedAt}
|
|
769
|
+
data-working-copy-switcher
|
|
770
|
+
>
|
|
771
|
+
<span className="min-w-0 flex-1">
|
|
772
|
+
<span className="block truncate font-medium">
|
|
773
|
+
{selectedWorkingCopy.kind === "temporary"
|
|
774
|
+
? selectedWorkingCopy.name
|
|
775
|
+
: t("localFiles.mainFolder")}
|
|
776
|
+
</span>
|
|
777
|
+
{selectedWorkingCopy.repository?.branch ||
|
|
778
|
+
selectedWorkingCopy.repository?.commit ? (
|
|
779
|
+
<span className="flex items-center gap-1 truncate font-mono text-[10px] text-muted-foreground">
|
|
780
|
+
<IconGitBranch size={11} />
|
|
781
|
+
{selectedWorkingCopy.repository.branch ??
|
|
782
|
+
selectedWorkingCopy.repository.commit?.slice(0, 8)}
|
|
783
|
+
</span>
|
|
784
|
+
) : null}
|
|
785
|
+
</span>
|
|
786
|
+
<IconChevronDown size={13} className="shrink-0" />
|
|
787
|
+
</button>
|
|
788
|
+
</DropdownMenuTrigger>
|
|
789
|
+
<DropdownMenuContent align="start" className="w-64">
|
|
790
|
+
{relatedWorkingCopies.map((folder) => (
|
|
791
|
+
<DropdownMenuItem
|
|
792
|
+
key={folder.id}
|
|
793
|
+
onSelect={() => setSelectedWorkingCopyId(folder.id ?? null)}
|
|
794
|
+
title={folder.updatedAt}
|
|
795
|
+
>
|
|
796
|
+
<span className="min-w-0">
|
|
797
|
+
<span className="block truncate">
|
|
798
|
+
{folder.kind === "temporary"
|
|
799
|
+
? folder.name
|
|
800
|
+
: t("localFiles.mainFolder")}
|
|
801
|
+
</span>
|
|
802
|
+
{folder.repository?.branch ||
|
|
803
|
+
folder.repository?.commit ? (
|
|
804
|
+
<span className="flex items-center gap-1 truncate font-mono text-[10px] text-muted-foreground">
|
|
805
|
+
<IconGitBranch size={11} />
|
|
806
|
+
{folder.repository.branch ??
|
|
807
|
+
folder.repository.commit?.slice(0, 8)}
|
|
808
|
+
</span>
|
|
809
|
+
) : null}
|
|
810
|
+
</span>
|
|
811
|
+
</DropdownMenuItem>
|
|
812
|
+
))}
|
|
813
|
+
</DropdownMenuContent>
|
|
814
|
+
</DropdownMenu>
|
|
815
|
+
) : null}
|
|
608
816
|
{failed ? (
|
|
609
817
|
<QueryErrorState
|
|
610
818
|
compact
|
|
@@ -618,7 +826,7 @@ function WorkspaceSidebarItem({
|
|
|
618
826
|
/>
|
|
619
827
|
) : (
|
|
620
828
|
<ContentFilesSidebarView
|
|
621
|
-
data={
|
|
829
|
+
data={visibleFilesDatabaseData}
|
|
622
830
|
overrides={filesPersonalView.data?.overrides}
|
|
623
831
|
isLoading={filesDatabase.isLoading || filesPersonalView.isLoading}
|
|
624
832
|
activeDocumentId={activeDocumentId}
|
|
@@ -1153,12 +1361,16 @@ export function DocumentSidebar({
|
|
|
1153
1361
|
createdAt: now,
|
|
1154
1362
|
updatedAt: now,
|
|
1155
1363
|
});
|
|
1364
|
+
const previousDocuments = queryClient.getQueryData(
|
|
1365
|
+
LIST_DOCUMENTS_QUERY_KEY,
|
|
1366
|
+
);
|
|
1367
|
+
const previousPath = `${location.pathname}${location.search}${location.hash}`;
|
|
1156
1368
|
|
|
1157
1369
|
// Optimistically inject into caches so UI updates immediately
|
|
1158
1370
|
queryClient.setQueryData(LIST_DOCUMENTS_QUERY_KEY, (old: any) => {
|
|
1159
1371
|
const docs: Document[] =
|
|
1160
1372
|
old?.documents ?? (Array.isArray(old) ? old : []);
|
|
1161
|
-
return
|
|
1373
|
+
return withDocumentsCacheShape(old, [...docs, tempDoc]);
|
|
1162
1374
|
});
|
|
1163
1375
|
queryClient.setQueryData(["action", "get-document", { id }], tempDoc);
|
|
1164
1376
|
if (rootFilesDatabaseId) {
|
|
@@ -1207,7 +1419,11 @@ export function DocumentSidebar({
|
|
|
1207
1419
|
});
|
|
1208
1420
|
}
|
|
1209
1421
|
} catch (err) {
|
|
1210
|
-
|
|
1422
|
+
rollbackOptimisticCreatedDocument(
|
|
1423
|
+
queryClient,
|
|
1424
|
+
id,
|
|
1425
|
+
previousDocuments !== undefined,
|
|
1426
|
+
);
|
|
1211
1427
|
queryClient.invalidateQueries({
|
|
1212
1428
|
queryKey: ["action", "list-documents"],
|
|
1213
1429
|
});
|
|
@@ -1218,7 +1434,10 @@ export function DocumentSidebar({
|
|
|
1218
1434
|
(current) => removeOptimisticItemFromContentDatabase(current, id),
|
|
1219
1435
|
);
|
|
1220
1436
|
}
|
|
1221
|
-
navigate(
|
|
1437
|
+
navigate(previousPath, {
|
|
1438
|
+
replace: true,
|
|
1439
|
+
flushSync: true,
|
|
1440
|
+
});
|
|
1222
1441
|
toast.error(t("sidebar.failedCreatePage"), {
|
|
1223
1442
|
description:
|
|
1224
1443
|
err instanceof Error ? err.message : t("empty.genericError"),
|
|
@@ -1228,6 +1447,9 @@ export function DocumentSidebar({
|
|
|
1228
1447
|
[
|
|
1229
1448
|
createDocument,
|
|
1230
1449
|
localFileMode,
|
|
1450
|
+
location.hash,
|
|
1451
|
+
location.pathname,
|
|
1452
|
+
location.search,
|
|
1231
1453
|
navigate,
|
|
1232
1454
|
navigateToDocument,
|
|
1233
1455
|
onNavigate,
|
|
@@ -1284,6 +1506,13 @@ export function DocumentSidebar({
|
|
|
1284
1506
|
navigationCandidates.find((doc) => doc.isFavorite) ??
|
|
1285
1507
|
[...navigationCandidates].sort(compareDocumentsByPosition)[0] ??
|
|
1286
1508
|
null;
|
|
1509
|
+
const previousDocuments = queryClient.getQueryData(
|
|
1510
|
+
LIST_DOCUMENTS_QUERY_KEY,
|
|
1511
|
+
);
|
|
1512
|
+
const previousDocumentQueries = [...deletedIds].flatMap((deletedId) =>
|
|
1513
|
+
queryClient.getQueriesData(documentQueryFilter(deletedId)),
|
|
1514
|
+
);
|
|
1515
|
+
const previousPath = `${location.pathname}${location.search}${location.hash}`;
|
|
1287
1516
|
|
|
1288
1517
|
queryClient.setQueryData(LIST_DOCUMENTS_QUERY_KEY, (old: unknown) => {
|
|
1289
1518
|
const cachedDocs: Document[] =
|
|
@@ -1317,11 +1546,17 @@ export function DocumentSidebar({
|
|
|
1317
1546
|
queryKey: ["action", "list-documents"],
|
|
1318
1547
|
});
|
|
1319
1548
|
} catch (err) {
|
|
1549
|
+
restoreDeletedDocumentSnapshots(
|
|
1550
|
+
queryClient,
|
|
1551
|
+
previousDocuments,
|
|
1552
|
+
previousDocumentQueries,
|
|
1553
|
+
deletedIds,
|
|
1554
|
+
);
|
|
1320
1555
|
queryClient.invalidateQueries({
|
|
1321
1556
|
queryKey: ["action", "list-documents"],
|
|
1322
1557
|
});
|
|
1323
|
-
if (activeDeleted
|
|
1324
|
-
navigate(
|
|
1558
|
+
if (activeDeleted) {
|
|
1559
|
+
navigate(previousPath, {
|
|
1325
1560
|
replace: true,
|
|
1326
1561
|
flushSync: true,
|
|
1327
1562
|
});
|
|
@@ -1338,6 +1573,9 @@ export function DocumentSidebar({
|
|
|
1338
1573
|
deleteDocument,
|
|
1339
1574
|
documents,
|
|
1340
1575
|
localFileMode,
|
|
1576
|
+
location.hash,
|
|
1577
|
+
location.pathname,
|
|
1578
|
+
location.search,
|
|
1341
1579
|
navigate,
|
|
1342
1580
|
queryClient,
|
|
1343
1581
|
],
|