@agent-native/core-corpus 0.212.1 → 0.214.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/corpus/README.md +1 -1
- package/corpus/templates/analytics/.agents/skills/onboarding-journeys/SKILL.md +29 -14
- package/corpus/templates/analytics/CHANGELOG.md +11 -0
- package/corpus/templates/analytics/actions/estimate-onboarding-journey-cost.ts +23 -6
- package/corpus/templates/analytics/actions/get-onboarding-journey.ts +20 -5
- package/corpus/templates/analytics/actions/provider-api-register.ts +2 -7
- package/corpus/templates/analytics/changelog/2026-10-21-analytics-recognizes-questions-about-connected-data-product-.md +6 -0
- package/corpus/templates/analytics/changelog/2026-10-21-onboarding-journeys-now-honor-larger-event-row-budgets.md +6 -0
- package/corpus/templates/analytics/changelog/2026-10-21-preserve-unknown-replay-availability-in-partial-journeys.md +6 -0
- package/corpus/templates/analytics/package.json +6 -0
- package/corpus/templates/analytics/scripts/journey-capture-plan.ts +18 -7
- package/corpus/templates/analytics/scripts/journey-capture.ts +3 -1
- package/corpus/templates/analytics/server/lib/db-admin-connections.ts +19 -12
- package/corpus/templates/analytics/server/lib/journey-tree.ts +24 -9
- package/corpus/templates/analytics/server/lib/onboarding-journey-cost.ts +163 -94
- package/corpus/templates/analytics/server/lib/onboarding-journey.ts +162 -74
- package/corpus/templates/assets/server/lib/org-admin.ts +20 -16
- package/corpus/templates/brain/server/lib/zoom-governance.ts +11 -6
- package/corpus/templates/clips/CHANGELOG.md +1 -0
- package/corpus/templates/clips/changelog/2026-10-16-rewind-status-says-when-it-remembers-audio.md +6 -0
- package/corpus/templates/clips/desktop/src/app.tsx +7 -4
- package/corpus/templates/clips/desktop/src/components/RewindCaptureStatus.tsx +25 -0
- package/corpus/templates/clips/desktop/src/lib/rewind-status.ts +6 -1
- package/corpus/templates/clips/desktop/src/shared/config.ts +3 -0
- package/corpus/templates/clips/desktop/src-tauri/src/screen_memory.rs +2 -0
- package/corpus/templates/clips/server/lib/recordings.ts +4 -19
- package/corpus/templates/community/qr/package.json +1 -1
- package/corpus/templates/content/.agents/skills/document-editing/references/databases.md +15 -1
- package/corpus/templates/content/CHANGELOG.md +13 -0
- package/corpus/templates/content/actions/_content-space-access.ts +95 -6
- package/corpus/templates/content/actions/_database-utils.ts +20 -16
- package/corpus/templates/content/actions/_database-view-setup.ts +305 -11
- package/corpus/templates/content/actions/_github-source-freshness.ts +72 -17
- package/corpus/templates/content/actions/_github-source-metadata.ts +63 -17
- package/corpus/templates/content/actions/_github-source.ts +141 -77
- package/corpus/templates/content/actions/_property-utils.ts +3 -0
- package/corpus/templates/content/actions/check-github-connection.ts +15 -0
- package/corpus/templates/content/actions/create-github-collection.ts +180 -0
- package/corpus/templates/content/actions/disconnect-content-database-source.ts +5 -0
- package/corpus/templates/content/actions/link-github-folder.ts +104 -36
- package/corpus/templates/content/actions/refresh-content-database-source.ts +37 -4
- package/corpus/templates/content/actions/sync-github-folder.ts +23 -5
- package/corpus/templates/content/actions/update-content-database-view.ts +18 -2
- package/corpus/templates/content/actions/view-screen.ts +12 -0
- package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +263 -35
- package/corpus/templates/content/app/components/editor/database/GitHubLinkDialog.tsx +938 -0
- package/corpus/templates/content/app/components/editor/database/GitHubSourcePanel.tsx +228 -0
- package/corpus/templates/content/app/components/editor/database/GitHubSourceStatusLine.tsx +561 -0
- package/corpus/templates/content/app/components/editor/database/GitHubUnlinkDialog.tsx +96 -0
- package/corpus/templates/content/app/components/editor/database/ListItemLayout.tsx +410 -0
- package/corpus/templates/content/app/components/editor/database/ListItemRow.tsx +426 -0
- package/corpus/templates/content/app/components/editor/database/ListView.tsx +136 -18
- package/corpus/templates/content/app/components/editor/database/github-availability.ts +147 -0
- package/corpus/templates/content/app/components/editor/database/github-source-text.ts +280 -0
- package/corpus/templates/content/app/components/editor/database/navigation-state.ts +3 -0
- package/corpus/templates/content/app/components/editor/database/settings.tsx +3 -3
- package/corpus/templates/content/app/components/editor/database/view-config.ts +4 -0
- package/corpus/templates/content/app/components/layout/Layout.tsx +2 -0
- package/corpus/templates/content/app/global.css +13 -0
- package/corpus/templates/content/app/hooks/content-action-refresh.ts +6 -0
- package/corpus/templates/content/app/hooks/use-content-database.ts +3 -1
- package/corpus/templates/content/app/hooks/use-github-folder.ts +199 -0
- package/corpus/templates/content/app/i18n-data.ts +186 -1
- package/corpus/templates/content/app/lib/github-link-dialog.ts +86 -0
- package/corpus/templates/content/changelog/2026-10-18-connected-ai-assistants-can-link-a-new-collection-to-a-github.md +1 -1
- package/corpus/templates/content/changelog/2026-10-18-link-a-new-collection-to-a-github-folder-from-its-sources.md +6 -0
- package/corpus/templates/content/changelog/2026-10-19-list-rows-can-be-customized.md +6 -0
- package/corpus/templates/content/changelog/2026-10-19-list-views-fit-phone-screens.md +6 -0
- package/corpus/templates/content/changelog/2026-10-21-organization-favorites-include-documents-shared-with-the-cur.md +6 -0
- package/corpus/templates/content/docs/product/capabilities/content.source.sync-policy.md +32 -7
- package/corpus/templates/content/docs/product/encyclopedia.md +3 -3
- package/corpus/templates/content/docs/product/features/content.feature.trust-your-connected-sources.md +2 -2
- package/corpus/templates/content/docs/product/roadmap.md +1 -1
- package/corpus/templates/content/parity/matrix.md +42 -42
- package/corpus/templates/content/parity/matrix.ts +14 -0
- package/corpus/templates/content/server/lib/github-source-client.ts +50 -1
- package/corpus/templates/content/shared/api.ts +21 -0
- package/corpus/templates/content/shared/database-item-layout.ts +591 -0
- package/corpus/templates/content/shared/github-source.ts +68 -0
- package/corpus/templates/design/CHANGELOG.md +1 -0
- package/corpus/templates/design/actions/connect-assets-mcp.ts +5 -9
- package/corpus/templates/design/changelog/2026-10-21-journey-canvases-accept-partial-analytics-samples-when-repla.md +6 -0
- package/corpus/templates/design/shared/journey-canvas.ts +6 -4
- package/corpus/templates/dispatch/.agents/skills/a2a-protocol/SKILL.md +16 -13
- package/corpus/templates/dispatch/CHANGELOG.md +4 -0
- package/corpus/templates/dispatch/changelog/2026-10-21-customer-ai-credit-questions-route-to-account-health-instead.md +6 -0
- package/corpus/templates/factory/server/lib/require-workspace-member.ts +5 -17
- package/corpus/templates/mail/server/lib/queued-drafts.ts +9 -4
- package/corpus/templates/slides/server/workspace-defaults.ts +12 -15
- package/package.json +1 -1
|
@@ -0,0 +1,561 @@
|
|
|
1
|
+
import { actionErrorMessage } from "@agent-native/core/client/hooks";
|
|
2
|
+
import { startWorkspaceProviderOAuth } from "@agent-native/core/client/integrations";
|
|
3
|
+
import type {
|
|
4
|
+
ContentDatabaseSource,
|
|
5
|
+
ContentGitHubSourceStatus,
|
|
6
|
+
} from "@shared/api";
|
|
7
|
+
import {
|
|
8
|
+
GITHUB_SOURCE_TYPE,
|
|
9
|
+
githubFolderLabel,
|
|
10
|
+
githubFolderName,
|
|
11
|
+
type GitHubSourceProblemCode,
|
|
12
|
+
} from "@shared/github-source";
|
|
13
|
+
import {
|
|
14
|
+
IconBrandGithub,
|
|
15
|
+
IconChevronDown,
|
|
16
|
+
IconExternalLink,
|
|
17
|
+
IconRefresh,
|
|
18
|
+
} from "@tabler/icons-react";
|
|
19
|
+
import {
|
|
20
|
+
Fragment,
|
|
21
|
+
useEffect,
|
|
22
|
+
useState,
|
|
23
|
+
type CSSProperties,
|
|
24
|
+
type ReactNode,
|
|
25
|
+
} from "react";
|
|
26
|
+
import { toast } from "sonner";
|
|
27
|
+
|
|
28
|
+
import { Button } from "@/components/ui/button";
|
|
29
|
+
import {
|
|
30
|
+
Popover,
|
|
31
|
+
PopoverContent,
|
|
32
|
+
PopoverTrigger,
|
|
33
|
+
} from "@/components/ui/popover";
|
|
34
|
+
import { Spinner } from "@/components/ui/spinner";
|
|
35
|
+
import {
|
|
36
|
+
useSyncGitHubFolder,
|
|
37
|
+
type SyncGitHubFolderResponse,
|
|
38
|
+
} from "@/hooks/use-github-folder";
|
|
39
|
+
|
|
40
|
+
import {
|
|
41
|
+
githubProblemFromErrorCode,
|
|
42
|
+
githubProblemStopsUpdates,
|
|
43
|
+
githubProblemText,
|
|
44
|
+
githubRelativeTime,
|
|
45
|
+
githubSkipReasonText,
|
|
46
|
+
shortCommit,
|
|
47
|
+
} from "./github-source-text";
|
|
48
|
+
import { GitHubUnlinkDialog } from "./GitHubUnlinkDialog";
|
|
49
|
+
import { dbText } from "./text";
|
|
50
|
+
|
|
51
|
+
/** How often "2 minutes ago" moves on. */
|
|
52
|
+
const RELATIVE_TIME_TICK_MS = 30_000;
|
|
53
|
+
|
|
54
|
+
/** Stands in for a name while a sentence is split around it. */
|
|
55
|
+
const NAME_MARK = "\u0000";
|
|
56
|
+
|
|
57
|
+
export function useNow(intervalMs = RELATIVE_TIME_TICK_MS) {
|
|
58
|
+
const [now, setNow] = useState(() => Date.now());
|
|
59
|
+
useEffect(() => {
|
|
60
|
+
const timer = window.setInterval(() => setNow(Date.now()), intervalMs);
|
|
61
|
+
return () => window.clearInterval(timer);
|
|
62
|
+
}, [intervalMs]);
|
|
63
|
+
return now;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** "From the **docs** folder on GitHub", with the name in bold in any language. */
|
|
67
|
+
function FromFolder({ status }: { status: ContentGitHubSourceStatus }) {
|
|
68
|
+
const name = githubFolderName(status);
|
|
69
|
+
const sentence = status.path
|
|
70
|
+
? dbText("githubFromFolder", { folder: NAME_MARK })
|
|
71
|
+
: dbText("githubFromRepo", { repo: NAME_MARK });
|
|
72
|
+
const [before, after = ""] = sentence.split(NAME_MARK);
|
|
73
|
+
return (
|
|
74
|
+
<span className="min-w-0 break-words text-foreground">
|
|
75
|
+
{before}
|
|
76
|
+
<strong className="font-medium">{name}</strong>
|
|
77
|
+
{after}
|
|
78
|
+
</span>
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** "Updated 2 minutes ago", or "Not updated yet" before the first update. */
|
|
83
|
+
export function githubUpdatedText(
|
|
84
|
+
status: ContentGitHubSourceStatus,
|
|
85
|
+
now: number,
|
|
86
|
+
): string {
|
|
87
|
+
return status.syncedAt
|
|
88
|
+
? dbText("githubUpdatedAgo", {
|
|
89
|
+
time: githubRelativeTime(status.syncedAt, now),
|
|
90
|
+
})
|
|
91
|
+
: dbText("githubNotUpdatedYet");
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function announceSync(
|
|
95
|
+
result: SyncGitHubFolderResponse,
|
|
96
|
+
status: ContentGitHubSourceStatus,
|
|
97
|
+
) {
|
|
98
|
+
const { state, problem, created, updated, renamed, trashed } = result.sync;
|
|
99
|
+
if (state === "error") {
|
|
100
|
+
toast.error(githubProblemText(problem?.code ?? "unavailable", status));
|
|
101
|
+
} else if (state === "busy") {
|
|
102
|
+
toast(dbText("githubSyncBusy"));
|
|
103
|
+
} else if (state === "unchanged") {
|
|
104
|
+
toast.success(dbText("githubAlreadyUpToDate"));
|
|
105
|
+
} else if (state === "partial") {
|
|
106
|
+
const progress = result.source?.metadata.github?.progress;
|
|
107
|
+
toast.success(
|
|
108
|
+
dbText("githubSyncPartialToast", {
|
|
109
|
+
done: progress?.done ?? created + updated,
|
|
110
|
+
total: progress?.total ?? created + updated + result.sync.remaining,
|
|
111
|
+
}),
|
|
112
|
+
);
|
|
113
|
+
} else {
|
|
114
|
+
const changed = created + updated + renamed + trashed;
|
|
115
|
+
// A new commit that touched nothing in the folder leaves the pages as current as "unchanged" does.
|
|
116
|
+
toast.success(
|
|
117
|
+
changed === 0
|
|
118
|
+
? dbText("githubAlreadyUpToDate")
|
|
119
|
+
: changed === 1
|
|
120
|
+
? dbText("githubSyncedToastOne")
|
|
121
|
+
: dbText("githubSyncedToast", { count: changed }),
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/** "Check for updates", which asks GitHub now instead of waiting. */
|
|
127
|
+
export function useCheckGitHubForUpdates(source: ContentDatabaseSource) {
|
|
128
|
+
const sync = useSyncGitHubFolder();
|
|
129
|
+
const status = source.metadata.github;
|
|
130
|
+
return {
|
|
131
|
+
pending: sync.isPending,
|
|
132
|
+
run: () =>
|
|
133
|
+
sync.mutate(
|
|
134
|
+
{ databaseId: source.databaseId },
|
|
135
|
+
{
|
|
136
|
+
onSuccess: (result) => status && announceSync(result, status),
|
|
137
|
+
onError: (error) => {
|
|
138
|
+
const problem = githubProblemFromErrorCode(
|
|
139
|
+
(error as { errorCode?: unknown } | null)?.errorCode,
|
|
140
|
+
);
|
|
141
|
+
toast.error(
|
|
142
|
+
problem
|
|
143
|
+
? githubProblemText(problem, status)
|
|
144
|
+
: (actionErrorMessage(error) ??
|
|
145
|
+
dbText("githubLinkUnavailable")),
|
|
146
|
+
);
|
|
147
|
+
},
|
|
148
|
+
},
|
|
149
|
+
),
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function DetailRow({
|
|
154
|
+
label,
|
|
155
|
+
children,
|
|
156
|
+
}: {
|
|
157
|
+
label: string;
|
|
158
|
+
children: ReactNode;
|
|
159
|
+
}) {
|
|
160
|
+
return (
|
|
161
|
+
<div className="flex min-w-0 gap-2">
|
|
162
|
+
<dt className="w-20 shrink-0 text-muted-foreground">{label}</dt>
|
|
163
|
+
<dd className="min-w-0 flex-1 break-words">{children}</dd>
|
|
164
|
+
</div>
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/** An item of the status line, led by its "·" (see the line for why). */
|
|
169
|
+
function Dotted({ children }: { children: ReactNode }) {
|
|
170
|
+
return (
|
|
171
|
+
<span className="inline-flex min-w-0 items-center">
|
|
172
|
+
<span aria-hidden className="w-3 shrink-0 text-center">
|
|
173
|
+
·
|
|
174
|
+
</span>
|
|
175
|
+
{children}
|
|
176
|
+
</span>
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/** A path that wraps after a slash rather than in the middle of a name. */
|
|
181
|
+
function WrapsAtSlashes({ text }: { text: string }) {
|
|
182
|
+
return text.split("/").map((part, index) => (
|
|
183
|
+
<Fragment key={index}>
|
|
184
|
+
{index > 0 ? (
|
|
185
|
+
<>
|
|
186
|
+
/<wbr />
|
|
187
|
+
</>
|
|
188
|
+
) : null}
|
|
189
|
+
{part}
|
|
190
|
+
</Fragment>
|
|
191
|
+
));
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/** Where the folder is and how current it is, as Details and the Sources panel show it. */
|
|
195
|
+
export function GitHubFolderFacts({
|
|
196
|
+
status,
|
|
197
|
+
now,
|
|
198
|
+
}: {
|
|
199
|
+
status: ContentGitHubSourceStatus;
|
|
200
|
+
now: number;
|
|
201
|
+
}) {
|
|
202
|
+
return (
|
|
203
|
+
<dl className="grid min-w-0 gap-1.5 text-xs">
|
|
204
|
+
<DetailRow label={dbText("githubDetailFolder")}>
|
|
205
|
+
<a
|
|
206
|
+
href={status.folderUrl}
|
|
207
|
+
target="_blank"
|
|
208
|
+
rel="noreferrer"
|
|
209
|
+
className="underline-offset-2 hover:underline"
|
|
210
|
+
>
|
|
211
|
+
<WrapsAtSlashes text={githubFolderLabel(status)} />
|
|
212
|
+
</a>
|
|
213
|
+
</DetailRow>
|
|
214
|
+
<DetailRow label={dbText("githubDetailBranch")}>
|
|
215
|
+
{status.branch}
|
|
216
|
+
</DetailRow>
|
|
217
|
+
{status.syncedCommitSha ? (
|
|
218
|
+
<DetailRow label={dbText("githubDetailVersion")}>
|
|
219
|
+
{shortCommit(status.syncedCommitSha)}
|
|
220
|
+
</DetailRow>
|
|
221
|
+
) : null}
|
|
222
|
+
<DetailRow label={dbText("githubDetailUpdated")}>
|
|
223
|
+
{status.syncedAt
|
|
224
|
+
? githubRelativeTime(status.syncedAt, now)
|
|
225
|
+
: dbText("githubNotUpdatedYet")}
|
|
226
|
+
</DetailRow>
|
|
227
|
+
</dl>
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/** "1 file not shown", which opens the list of files and why. */
|
|
232
|
+
function NotShownFiles({
|
|
233
|
+
files,
|
|
234
|
+
count,
|
|
235
|
+
}: {
|
|
236
|
+
files: ContentGitHubSourceStatus["skipped"];
|
|
237
|
+
count: number;
|
|
238
|
+
}) {
|
|
239
|
+
return (
|
|
240
|
+
<Popover>
|
|
241
|
+
<PopoverTrigger asChild>
|
|
242
|
+
<button
|
|
243
|
+
type="button"
|
|
244
|
+
className="rounded-sm underline decoration-dotted underline-offset-2 hover:text-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
|
|
245
|
+
>
|
|
246
|
+
{count === 1
|
|
247
|
+
? dbText("githubNotShownOne")
|
|
248
|
+
: dbText("githubNotShown", { count })}
|
|
249
|
+
</button>
|
|
250
|
+
</PopoverTrigger>
|
|
251
|
+
<PopoverContent align="start" className="w-80">
|
|
252
|
+
<div className="grid min-w-0 gap-2 text-xs">
|
|
253
|
+
<div className="font-medium">{dbText("githubNotShownHeading")}</div>
|
|
254
|
+
<ul className="grid max-h-60 min-w-0 gap-1 overflow-y-auto">
|
|
255
|
+
{files.map((file) => (
|
|
256
|
+
<li key={file.path} className="flex min-w-0 items-baseline gap-2">
|
|
257
|
+
<span className="min-w-0 truncate" title={file.path}>
|
|
258
|
+
{file.path}
|
|
259
|
+
</span>
|
|
260
|
+
<span className="shrink-0 text-muted-foreground">
|
|
261
|
+
{githubSkipReasonText(file.reason)}
|
|
262
|
+
</span>
|
|
263
|
+
</li>
|
|
264
|
+
))}
|
|
265
|
+
{count > files.length ? (
|
|
266
|
+
<li className="text-muted-foreground">
|
|
267
|
+
{dbText("githubLinkMore", { count: count - files.length })}
|
|
268
|
+
</li>
|
|
269
|
+
) : null}
|
|
270
|
+
</ul>
|
|
271
|
+
</div>
|
|
272
|
+
</PopoverContent>
|
|
273
|
+
</Popover>
|
|
274
|
+
);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/** What each stopping problem offers, besides waiting. */
|
|
278
|
+
function problemActions(code: GitHubSourceProblemCode): {
|
|
279
|
+
reconnect: boolean;
|
|
280
|
+
open: boolean;
|
|
281
|
+
unlink: boolean;
|
|
282
|
+
} {
|
|
283
|
+
return {
|
|
284
|
+
reconnect: code === "connection",
|
|
285
|
+
open:
|
|
286
|
+
code === "forbidden" ||
|
|
287
|
+
code === "repository_not_found" ||
|
|
288
|
+
code === "folder_not_found",
|
|
289
|
+
unlink:
|
|
290
|
+
code === "repository_not_found" ||
|
|
291
|
+
code === "branch_not_found" ||
|
|
292
|
+
code === "folder_not_found" ||
|
|
293
|
+
code === "too_large",
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function reconnectGitHub() {
|
|
298
|
+
startWorkspaceProviderOAuth("github", {
|
|
299
|
+
appId: "content",
|
|
300
|
+
returnPath: `${window.location.pathname}${window.location.search}`,
|
|
301
|
+
scope: "user",
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* The line above a collection that comes from a GitHub folder: which folder,
|
|
307
|
+
* when it last updated, where to edit it, and Details. A problem that stops
|
|
308
|
+
* updates gets its own red line with the one thing to do about it.
|
|
309
|
+
*/
|
|
310
|
+
export function GitHubSourceStatusLine({
|
|
311
|
+
source,
|
|
312
|
+
documentId,
|
|
313
|
+
canEdit,
|
|
314
|
+
pageCount,
|
|
315
|
+
}: {
|
|
316
|
+
source: ContentDatabaseSource | null;
|
|
317
|
+
documentId: string;
|
|
318
|
+
canEdit: boolean;
|
|
319
|
+
pageCount: number;
|
|
320
|
+
}) {
|
|
321
|
+
const now = useNow();
|
|
322
|
+
const [unlinkOpen, setUnlinkOpen] = useState(false);
|
|
323
|
+
const [detailsOpen, setDetailsOpen] = useState(false);
|
|
324
|
+
const status =
|
|
325
|
+
source?.sourceType === GITHUB_SOURCE_TYPE
|
|
326
|
+
? source.metadata.github
|
|
327
|
+
: undefined;
|
|
328
|
+
if (!source || !status) return null;
|
|
329
|
+
return (
|
|
330
|
+
<GitHubStatusLineContent
|
|
331
|
+
source={source}
|
|
332
|
+
status={status}
|
|
333
|
+
now={now}
|
|
334
|
+
documentId={documentId}
|
|
335
|
+
canEdit={canEdit}
|
|
336
|
+
pageCount={pageCount}
|
|
337
|
+
detailsOpen={detailsOpen}
|
|
338
|
+
onDetailsOpenChange={setDetailsOpen}
|
|
339
|
+
unlinkOpen={unlinkOpen}
|
|
340
|
+
onUnlinkOpenChange={setUnlinkOpen}
|
|
341
|
+
/>
|
|
342
|
+
);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
function GitHubStatusLineContent({
|
|
346
|
+
source,
|
|
347
|
+
status,
|
|
348
|
+
now,
|
|
349
|
+
documentId,
|
|
350
|
+
canEdit,
|
|
351
|
+
pageCount,
|
|
352
|
+
detailsOpen,
|
|
353
|
+
onDetailsOpenChange,
|
|
354
|
+
unlinkOpen,
|
|
355
|
+
onUnlinkOpenChange,
|
|
356
|
+
}: {
|
|
357
|
+
source: ContentDatabaseSource;
|
|
358
|
+
status: ContentGitHubSourceStatus;
|
|
359
|
+
now: number;
|
|
360
|
+
documentId: string;
|
|
361
|
+
canEdit: boolean;
|
|
362
|
+
pageCount: number;
|
|
363
|
+
detailsOpen: boolean;
|
|
364
|
+
onDetailsOpenChange: (open: boolean) => void;
|
|
365
|
+
unlinkOpen: boolean;
|
|
366
|
+
onUnlinkOpenChange: (open: boolean) => void;
|
|
367
|
+
}) {
|
|
368
|
+
const check = useCheckGitHubForUpdates(source);
|
|
369
|
+
const problem = status.problem;
|
|
370
|
+
const stops = problem ? githubProblemStopsUpdates(problem.code) : false;
|
|
371
|
+
const actions = problem && stops ? problemActions(problem.code) : null;
|
|
372
|
+
const syncing = !!status.pendingCommitSha && !!status.progress;
|
|
373
|
+
// Pictures waiting on storage are a note, not a stop: the pages still update.
|
|
374
|
+
const picturesWaiting =
|
|
375
|
+
problem?.code === "storage_unavailable" || status.imagesWaiting > 0;
|
|
376
|
+
const paused = !!problem && !stops && problem.code !== "storage_unavailable";
|
|
377
|
+
const openUnlink = () => {
|
|
378
|
+
onDetailsOpenChange(false);
|
|
379
|
+
onUnlinkOpenChange(true);
|
|
380
|
+
};
|
|
381
|
+
|
|
382
|
+
return (
|
|
383
|
+
<div className="mb-2 grid min-w-0 gap-1 text-xs" data-github-source-status>
|
|
384
|
+
{/* Every item starts with its dot, and the row sits one dot to the left
|
|
385
|
+
of a clipped box, so the dot of whichever item starts a line is cut
|
|
386
|
+
off: no line starts or ends with a dot, however the row wraps. */}
|
|
387
|
+
<div className="-m-px overflow-hidden p-px">
|
|
388
|
+
<div className="-ms-3 flex min-w-0 flex-wrap items-center gap-y-1 text-muted-foreground">
|
|
389
|
+
<Dotted>
|
|
390
|
+
<IconBrandGithub className="me-1.5 size-3.5 shrink-0" aria-hidden />
|
|
391
|
+
<FromFolder status={status} />
|
|
392
|
+
</Dotted>
|
|
393
|
+
{stops ? null : (
|
|
394
|
+
<Dotted>
|
|
395
|
+
<span>{githubUpdatedText(status, now)}</span>
|
|
396
|
+
</Dotted>
|
|
397
|
+
)}
|
|
398
|
+
<Dotted>
|
|
399
|
+
<a
|
|
400
|
+
href={status.folderUrl}
|
|
401
|
+
target="_blank"
|
|
402
|
+
rel="noreferrer"
|
|
403
|
+
className="inline-flex items-center gap-0.5 rounded-sm underline-offset-2 hover:text-foreground hover:underline focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
|
|
404
|
+
>
|
|
405
|
+
{dbText("githubEditOnGitHub")}
|
|
406
|
+
<IconExternalLink className="size-3" aria-hidden />
|
|
407
|
+
</a>
|
|
408
|
+
</Dotted>
|
|
409
|
+
{status.skippedCount > 0 ? (
|
|
410
|
+
<Dotted>
|
|
411
|
+
<NotShownFiles
|
|
412
|
+
files={status.skipped}
|
|
413
|
+
count={status.skippedCount}
|
|
414
|
+
/>
|
|
415
|
+
</Dotted>
|
|
416
|
+
) : null}
|
|
417
|
+
{/* No dot before Details, but the same lead, so it isn't cut off. */}
|
|
418
|
+
<span className="ps-3">
|
|
419
|
+
<Popover open={detailsOpen} onOpenChange={onDetailsOpenChange}>
|
|
420
|
+
<PopoverTrigger asChild>
|
|
421
|
+
<button
|
|
422
|
+
type="button"
|
|
423
|
+
className="inline-flex items-center gap-0.5 rounded-sm px-1 py-0.5 hover:bg-muted hover:text-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
|
|
424
|
+
>
|
|
425
|
+
{dbText("githubDetails")}
|
|
426
|
+
<IconChevronDown className="size-3" aria-hidden />
|
|
427
|
+
</button>
|
|
428
|
+
</PopoverTrigger>
|
|
429
|
+
<PopoverContent align="start" className="w-72">
|
|
430
|
+
<div className="grid min-w-0 gap-3">
|
|
431
|
+
<GitHubFolderFacts status={status} now={now} />
|
|
432
|
+
<div className="flex flex-wrap gap-1.5">
|
|
433
|
+
<Button
|
|
434
|
+
type="button"
|
|
435
|
+
size="xs"
|
|
436
|
+
variant="outline"
|
|
437
|
+
disabled={check.pending}
|
|
438
|
+
onClick={check.run}
|
|
439
|
+
>
|
|
440
|
+
{check.pending ? (
|
|
441
|
+
<Spinner />
|
|
442
|
+
) : (
|
|
443
|
+
<IconRefresh aria-hidden />
|
|
444
|
+
)}
|
|
445
|
+
{dbText("githubCheckForUpdates")}
|
|
446
|
+
</Button>
|
|
447
|
+
{canEdit ? (
|
|
448
|
+
<Button
|
|
449
|
+
type="button"
|
|
450
|
+
size="xs"
|
|
451
|
+
variant="outline-destructive"
|
|
452
|
+
onClick={openUnlink}
|
|
453
|
+
>
|
|
454
|
+
{dbText("githubUnlinkFolder")}
|
|
455
|
+
</Button>
|
|
456
|
+
) : null}
|
|
457
|
+
</div>
|
|
458
|
+
</div>
|
|
459
|
+
</PopoverContent>
|
|
460
|
+
</Popover>
|
|
461
|
+
</span>
|
|
462
|
+
</div>
|
|
463
|
+
</div>
|
|
464
|
+
|
|
465
|
+
{problem && stops ? (
|
|
466
|
+
// Only the problem is announced: the line above holds controls and a
|
|
467
|
+
// time that moves on every half minute.
|
|
468
|
+
<div
|
|
469
|
+
className="flex min-w-0 flex-wrap items-center gap-x-2 gap-y-1 text-destructive"
|
|
470
|
+
role="alert"
|
|
471
|
+
data-github-source-problem
|
|
472
|
+
>
|
|
473
|
+
<span className="min-w-0 break-words">
|
|
474
|
+
{githubProblemText(problem.code, status)}{" "}
|
|
475
|
+
{status.syncedAt
|
|
476
|
+
? dbText("githubShowingAsOf", {
|
|
477
|
+
time: githubRelativeTime(status.syncedAt, now),
|
|
478
|
+
})
|
|
479
|
+
: null}
|
|
480
|
+
</span>
|
|
481
|
+
{actions?.reconnect && canEdit ? (
|
|
482
|
+
<Button
|
|
483
|
+
type="button"
|
|
484
|
+
size="xs"
|
|
485
|
+
variant="outline"
|
|
486
|
+
onClick={reconnectGitHub}
|
|
487
|
+
>
|
|
488
|
+
<IconBrandGithub aria-hidden />
|
|
489
|
+
{dbText("githubReconnect")}
|
|
490
|
+
</Button>
|
|
491
|
+
) : null}
|
|
492
|
+
{actions?.open ? (
|
|
493
|
+
<Button asChild size="xs" variant="outline">
|
|
494
|
+
<a href={status.folderUrl} target="_blank" rel="noreferrer">
|
|
495
|
+
{dbText("githubOpenFolder")}
|
|
496
|
+
<IconExternalLink aria-hidden />
|
|
497
|
+
</a>
|
|
498
|
+
</Button>
|
|
499
|
+
) : null}
|
|
500
|
+
{actions?.unlink && canEdit ? (
|
|
501
|
+
<Button
|
|
502
|
+
type="button"
|
|
503
|
+
size="xs"
|
|
504
|
+
variant="outline-destructive"
|
|
505
|
+
onClick={openUnlink}
|
|
506
|
+
>
|
|
507
|
+
{dbText("githubUnlinkFolder")}
|
|
508
|
+
</Button>
|
|
509
|
+
) : null}
|
|
510
|
+
</div>
|
|
511
|
+
) : null}
|
|
512
|
+
|
|
513
|
+
{paused && problem ? (
|
|
514
|
+
<div className="break-words text-muted-foreground">
|
|
515
|
+
{githubProblemText(problem.code, status)}
|
|
516
|
+
</div>
|
|
517
|
+
) : null}
|
|
518
|
+
|
|
519
|
+
{picturesWaiting ? (
|
|
520
|
+
<div className="break-words text-muted-foreground">
|
|
521
|
+
{dbText("githubPicturesWaiting")}
|
|
522
|
+
</div>
|
|
523
|
+
) : null}
|
|
524
|
+
|
|
525
|
+
{syncing && status.progress ? (
|
|
526
|
+
<div className="grid max-w-sm gap-1">
|
|
527
|
+
<div className="text-muted-foreground">
|
|
528
|
+
{dbText("githubSyncingProgress", {
|
|
529
|
+
done: status.progress.done,
|
|
530
|
+
total: status.progress.total,
|
|
531
|
+
})}
|
|
532
|
+
</div>
|
|
533
|
+
<div className="h-1.5 overflow-hidden rounded-full bg-muted">
|
|
534
|
+
<div
|
|
535
|
+
className="h-full w-(--github-sync-progress) rounded-full bg-foreground/70 transition-all"
|
|
536
|
+
style={
|
|
537
|
+
{
|
|
538
|
+
"--github-sync-progress": `${Math.round(
|
|
539
|
+
(status.progress.done /
|
|
540
|
+
Math.max(status.progress.total, 1)) *
|
|
541
|
+
100,
|
|
542
|
+
)}%`,
|
|
543
|
+
} as CSSProperties
|
|
544
|
+
}
|
|
545
|
+
/>
|
|
546
|
+
</div>
|
|
547
|
+
</div>
|
|
548
|
+
) : null}
|
|
549
|
+
|
|
550
|
+
{canEdit ? (
|
|
551
|
+
<GitHubUnlinkDialog
|
|
552
|
+
open={unlinkOpen}
|
|
553
|
+
onOpenChange={onUnlinkOpenChange}
|
|
554
|
+
documentId={documentId}
|
|
555
|
+
sourceId={source.id}
|
|
556
|
+
pageCount={pageCount}
|
|
557
|
+
/>
|
|
558
|
+
) : null}
|
|
559
|
+
</div>
|
|
560
|
+
);
|
|
561
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { actionErrorMessage } from "@agent-native/core/client/hooks";
|
|
2
|
+
import { toast } from "sonner";
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
AlertDialog,
|
|
6
|
+
AlertDialogAction,
|
|
7
|
+
AlertDialogCancel,
|
|
8
|
+
AlertDialogContent,
|
|
9
|
+
AlertDialogDescription,
|
|
10
|
+
AlertDialogFooter,
|
|
11
|
+
AlertDialogHeader,
|
|
12
|
+
AlertDialogTitle,
|
|
13
|
+
} from "@/components/ui/alert-dialog";
|
|
14
|
+
import { Spinner } from "@/components/ui/spinner";
|
|
15
|
+
import { useDisconnectContentDatabaseSource } from "@/hooks/use-content-database";
|
|
16
|
+
|
|
17
|
+
import { dbText } from "./text";
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Asks before unlinking a GitHub folder: the pages stay and become editable,
|
|
21
|
+
* and the folder can't come back into this collection, so it says both.
|
|
22
|
+
*/
|
|
23
|
+
export function GitHubUnlinkDialog({
|
|
24
|
+
open,
|
|
25
|
+
onOpenChange,
|
|
26
|
+
documentId,
|
|
27
|
+
sourceId,
|
|
28
|
+
pageCount,
|
|
29
|
+
onUnlinked,
|
|
30
|
+
}: {
|
|
31
|
+
open: boolean;
|
|
32
|
+
onOpenChange: (open: boolean) => void;
|
|
33
|
+
documentId: string;
|
|
34
|
+
sourceId: string;
|
|
35
|
+
pageCount: number;
|
|
36
|
+
onUnlinked?: () => void;
|
|
37
|
+
}) {
|
|
38
|
+
const unlink = useDisconnectContentDatabaseSource(documentId);
|
|
39
|
+
return (
|
|
40
|
+
<AlertDialog
|
|
41
|
+
open={open}
|
|
42
|
+
onOpenChange={(next) => {
|
|
43
|
+
if (unlink.isPending && !next) return;
|
|
44
|
+
onOpenChange(next);
|
|
45
|
+
}}
|
|
46
|
+
>
|
|
47
|
+
<AlertDialogContent>
|
|
48
|
+
<AlertDialogHeader>
|
|
49
|
+
<AlertDialogTitle>{dbText("githubUnlinkTitle")}</AlertDialogTitle>
|
|
50
|
+
<AlertDialogDescription>
|
|
51
|
+
{pageCount === 1
|
|
52
|
+
? dbText("githubUnlinkBodyOne")
|
|
53
|
+
: dbText("githubUnlinkBody", { count: pageCount })}
|
|
54
|
+
</AlertDialogDescription>
|
|
55
|
+
</AlertDialogHeader>
|
|
56
|
+
<AlertDialogFooter>
|
|
57
|
+
<AlertDialogCancel disabled={unlink.isPending}>
|
|
58
|
+
{dbText("cancel")}
|
|
59
|
+
</AlertDialogCancel>
|
|
60
|
+
<AlertDialogAction
|
|
61
|
+
variant="destructive"
|
|
62
|
+
disabled={unlink.isPending}
|
|
63
|
+
onClick={(event) => {
|
|
64
|
+
// Stays open until the unlink is saved, so a failure is seen here.
|
|
65
|
+
event.preventDefault();
|
|
66
|
+
unlink.mutate(
|
|
67
|
+
{ documentId, sourceId },
|
|
68
|
+
{
|
|
69
|
+
onSuccess: () => {
|
|
70
|
+
toast.success(dbText("githubUnlinkedToast"));
|
|
71
|
+
onOpenChange(false);
|
|
72
|
+
onUnlinked?.();
|
|
73
|
+
},
|
|
74
|
+
onError: (error) => {
|
|
75
|
+
toast.error(dbText("githubUnlinkFailed"), {
|
|
76
|
+
description:
|
|
77
|
+
actionErrorMessage(error) ??
|
|
78
|
+
(error instanceof Error
|
|
79
|
+
? error.message
|
|
80
|
+
: dbText("tryAgain")),
|
|
81
|
+
});
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
);
|
|
85
|
+
}}
|
|
86
|
+
>
|
|
87
|
+
{unlink.isPending ? <Spinner className="mr-1 size-3.5" /> : null}
|
|
88
|
+
{unlink.isPending
|
|
89
|
+
? dbText("githubUnlinking")
|
|
90
|
+
: dbText("githubUnlink")}
|
|
91
|
+
</AlertDialogAction>
|
|
92
|
+
</AlertDialogFooter>
|
|
93
|
+
</AlertDialogContent>
|
|
94
|
+
</AlertDialog>
|
|
95
|
+
);
|
|
96
|
+
}
|