@agent-native/core-corpus 0.217.0 → 0.219.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/hubspot/SKILL.md +5 -3
- package/corpus/templates/analytics/CHANGELOG.md +2 -0
- package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +38 -167
- package/corpus/templates/analytics/changelog/2026-10-16-owned-and-shared-chats-appear-in-separate-history-sections.md +6 -0
- package/corpus/templates/assets/CHANGELOG.md +6 -0
- package/corpus/templates/assets/app/components/layout/Sidebar.tsx +51 -117
- package/corpus/templates/assets/changelog/2026-10-16-owned-and-shared-chats-appear-in-separate-history-sections.md +6 -0
- package/corpus/templates/brain/app/components/layout/Sidebar.tsx +34 -101
- package/corpus/templates/brain/app/routes/sources.tsx +1 -1
- package/corpus/templates/brain/vitest.config.ts +7 -0
- package/corpus/templates/calendar/CHANGELOG.md +6 -0
- package/corpus/templates/calendar/app/pages/BookingLinksPage.tsx +1 -1
- package/corpus/templates/calendar/changelog/2026-10-16-booking-links-show-localized-visibility-badges.md +6 -0
- package/corpus/templates/chat/app/components/layout/Sidebar.tsx +62 -101
- package/corpus/templates/clips/CHANGELOG.md +7 -20
- package/corpus/templates/clips/app/components/meetings/share-meeting-dialog.tsx +20 -77
- package/corpus/templates/clips/app/components/player/share-dialog.tsx +63 -90
- package/corpus/templates/clips/app/components/sharing/share-test-support.tsx +180 -0
- package/corpus/templates/clips/app/components/sharing/share-ui.tsx +5 -584
- package/corpus/templates/clips/changelog/2026-10-16-share-dialogs-make-it-easier-to-see-who-has-access-and-manag.md +6 -0
- package/corpus/templates/clips/changelog/2026-10-21-share-panels-describe-general-access-in-a-sentence.md +6 -0
- package/corpus/templates/community/qr/package.json +1 -1
- package/corpus/templates/content/CHANGELOG.md +7 -0
- package/corpus/templates/content/app/components/editor/CommentStack.tsx +301 -0
- package/corpus/templates/content/app/components/editor/CommentsSidebar.tsx +770 -179
- package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +75 -27
- package/corpus/templates/content/app/components/editor/SuggestionText.tsx +18 -3
- package/corpus/templates/content/app/components/editor/VisualEditor.tsx +89 -22
- package/corpus/templates/content/app/components/editor/block-field-comment-threads.ts +15 -0
- package/corpus/templates/content/app/components/editor/block-field-comments.tsx +5 -9
- package/corpus/templates/content/app/components/editor/comment-stacks.ts +143 -0
- package/corpus/templates/content/app/components/editor/extensions/SuggestionHighlight.ts +383 -34
- package/corpus/templates/content/app/components/editor/suggestions/draft-session.ts +31 -0
- package/corpus/templates/content/app/components/editor/suggestions/presentation-rebase.ts +82 -4
- package/corpus/templates/content/app/global.css +31 -0
- package/corpus/templates/content/app/i18n-data.ts +8 -0
- package/corpus/templates/content/changelog/2026-10-16-accepting-a-suggestion-on-a-page-that-changed-since-it-was-s.md +6 -0
- package/corpus/templates/content/changelog/2026-10-20-suggested-edits-that-add-remove-split-or-merge-paragraphs-li.md +6 -0
- package/corpus/templates/content/changelog/2026-10-22-blocks-with-several-comment-threads-fold-into-one-stack-in-t.md +6 -0
- package/corpus/templates/content/changelog/2026-10-22-opening-a-page-no-longer-saves-an-edit-again-after-another-o.md +6 -0
- package/corpus/templates/content/docs/product/capabilities/content.comment.page-owned.md +8 -1
- package/corpus/templates/content/shared/suggestion-diff.ts +23 -6
- package/corpus/templates/content/shared/suggestion-formatting.ts +44 -14
- package/corpus/templates/content/shared/suggestion-text.ts +20 -5
- package/corpus/templates/design/.generated/bridge/interact-links.generated.ts +8 -8
- package/corpus/templates/design/CHANGELOG.md +8 -0
- package/corpus/templates/design/app/components/design/DesignCanvas.tsx +18 -1
- package/corpus/templates/design/app/components/design/bridge/interact-links.bridge.ts +8 -8
- package/corpus/templates/design/app/components/editor/PromptDialog.tsx +15 -13
- package/corpus/templates/design/app/i18n/en-US.ts +1 -1
- package/corpus/templates/design/app/pages/DesignSystems.tsx +1 -1
- package/corpus/templates/design/app/pages/Index.tsx +18 -64
- package/corpus/templates/design/app/pages/design-editor/domains/use-editor-modes.ts +15 -6
- package/corpus/templates/design/app/pages/design-editor/domains/use-editor-screen-rendering.tsx +29 -5
- package/corpus/templates/design/app/pages/design-editor/prototype-navigation-transition.ts +423 -0
- package/corpus/templates/design/app/pages/design-editor/view/single-screen-canvas.tsx +35 -9
- package/corpus/templates/design/changelog/2026-10-16-design-pages-use-localized-visibility-badges-and-a-shared-ow.md +6 -0
- package/corpus/templates/design/changelog/2026-10-20-the-design-full-app-toggle-on-the-home-page-no-longer-hides-.md +6 -0
- package/corpus/templates/design/changelog/2026-10-22-prototype-links-can-play-code-authored-screen-transitions.md +6 -0
- package/corpus/templates/design/shared/motion-authored-css-import.ts +938 -7
- package/corpus/templates/design/shared/motion-html-source.ts +1689 -135
- package/corpus/templates/design/shared/motion-prototype-transition.ts +329 -0
- package/corpus/templates/design/shared/motion-react-source.ts +2154 -126
- package/corpus/templates/design/shared/motion-text-runtime-v1.fixture.txt +221 -0
- package/corpus/templates/dispatch/CHANGELOG.md +4 -0
- package/corpus/templates/dispatch/app/i18n-data.ts +75 -0
- package/corpus/templates/dispatch/changelog/2026-10-21-owned-and-shared-chats-appear-in-separate-history-sections.md +6 -0
- package/corpus/templates/factory/app/components/layout/Sidebar.tsx +34 -100
- package/corpus/templates/forms/app/pages/FormsListPage.tsx +1 -1
- package/corpus/templates/plan/CHANGELOG.md +6 -0
- package/corpus/templates/plan/app/components/layout/Sidebar.tsx +34 -109
- package/corpus/templates/plan/changelog/2026-10-16-owned-and-shared-chats-appear-in-separate-history-sections.md +6 -0
- package/corpus/templates/slides/CHANGELOG.md +1 -0
- package/corpus/templates/slides/app/components/deck/DeckCard.tsx +1 -1
- package/corpus/templates/slides/app/components/design-system/DesignSystemCard.tsx +1 -1
- package/corpus/templates/slides/app/pages/Index.tsx +7 -2
- package/corpus/templates/slides/changelog/2026-10-16-deck-lists-use-the-shared-ownership-filter-and-visibility-ba.md +6 -0
- package/package.json +1 -1
- package/corpus/templates/chat/app/lib/sidebar-thread-state.ts +0 -29
- package/corpus/templates/slides/app/components/deck/DeckFilterMenu.tsx +0 -83
|
@@ -1,91 +1,14 @@
|
|
|
1
1
|
import { trackEvent } from "@agent-native/core/client/analytics";
|
|
2
|
-
import {
|
|
3
|
-
useActionMutation,
|
|
4
|
-
useActionQuery,
|
|
5
|
-
} from "@agent-native/core/client/hooks";
|
|
6
2
|
import { useT } from "@agent-native/core/client/i18n";
|
|
7
|
-
import {
|
|
3
|
+
import type { ShareButtonVisibility } from "@agent-native/core/client/sharing/useShareButtonController";
|
|
8
4
|
import { writeClipboardText } from "@agent-native/toolkit/clipboard";
|
|
9
|
-
import {
|
|
10
|
-
|
|
11
|
-
IconChevronDown,
|
|
12
|
-
IconLink,
|
|
13
|
-
IconLock,
|
|
14
|
-
IconSend2,
|
|
15
|
-
IconTrash,
|
|
16
|
-
IconUsersGroup,
|
|
17
|
-
IconWorld,
|
|
18
|
-
} from "@tabler/icons-react";
|
|
19
|
-
import { useQueryClient } from "@tanstack/react-query";
|
|
20
|
-
import { useEffect, useMemo, useRef, useState, type ReactNode } from "react";
|
|
5
|
+
import { IconCheck, IconLink } from "@tabler/icons-react";
|
|
6
|
+
import { useEffect, useRef, useState, type ReactNode } from "react";
|
|
21
7
|
|
|
22
|
-
import {
|
|
23
|
-
Avatar as UserAvatar,
|
|
24
|
-
AvatarFallback as UserAvatarFallback,
|
|
25
|
-
AvatarImage as UserAvatarImage,
|
|
26
|
-
} from "@/components/ui/avatar";
|
|
27
8
|
import { Button } from "@/components/ui/button";
|
|
28
|
-
import { Checkbox } from "@/components/ui/checkbox";
|
|
29
|
-
import {
|
|
30
|
-
Collapsible,
|
|
31
|
-
CollapsibleContent,
|
|
32
|
-
CollapsibleTrigger,
|
|
33
|
-
} from "@/components/ui/collapsible";
|
|
34
|
-
import { Input } from "@/components/ui/input";
|
|
35
|
-
import {
|
|
36
|
-
Select,
|
|
37
|
-
SelectContent,
|
|
38
|
-
SelectItem,
|
|
39
|
-
SelectTrigger,
|
|
40
|
-
SelectValue,
|
|
41
|
-
} from "@/components/ui/select";
|
|
42
|
-
import { useVisibleAvatarUrl } from "@/lib/use-visible-avatar-url";
|
|
43
9
|
import { cn } from "@/lib/utils";
|
|
44
10
|
|
|
45
|
-
export type Visibility =
|
|
46
|
-
export type Role = "viewer" | "commenter" | "editor" | "admin";
|
|
47
|
-
export type ShareSettingsView = "people" | null;
|
|
48
|
-
|
|
49
|
-
export interface RoleCopy {
|
|
50
|
-
label: string;
|
|
51
|
-
description: string;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
export interface Share {
|
|
55
|
-
id: string;
|
|
56
|
-
principalType: "user" | "org";
|
|
57
|
-
principalId: string;
|
|
58
|
-
role: Role;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
export interface SharesResponse {
|
|
62
|
-
ownerEmail: string | null;
|
|
63
|
-
orgId: string | null;
|
|
64
|
-
visibility: Visibility | null;
|
|
65
|
-
role?: "owner" | Role;
|
|
66
|
-
shares: Share[];
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
export type SharesQuery = ReturnType<typeof useActionQuery<SharesResponse>>;
|
|
70
|
-
|
|
71
|
-
export const VIS_META: Record<Visibility, { Icon: typeof IconLock }> = {
|
|
72
|
-
private: {
|
|
73
|
-
Icon: IconLock,
|
|
74
|
-
},
|
|
75
|
-
org: {
|
|
76
|
-
Icon: IconUsersGroup,
|
|
77
|
-
},
|
|
78
|
-
public: {
|
|
79
|
-
Icon: IconWorld,
|
|
80
|
-
},
|
|
81
|
-
};
|
|
82
|
-
|
|
83
|
-
export const ROLE_OPTIONS: Array<{ value: Role; label: string }> = [
|
|
84
|
-
{ value: "viewer", label: "Viewer" },
|
|
85
|
-
{ value: "commenter", label: "Commenter" },
|
|
86
|
-
{ value: "editor", label: "Editor" },
|
|
87
|
-
{ value: "admin", label: "Admin" },
|
|
88
|
-
];
|
|
11
|
+
export type Visibility = ShareButtonVisibility;
|
|
89
12
|
|
|
90
13
|
export function copyToClipboard(value: string): Promise<boolean> {
|
|
91
14
|
return writeClipboardText(value);
|
|
@@ -98,6 +21,7 @@ const NESTED_LAYER_SELECTOR = [
|
|
|
98
21
|
"[role='menu']",
|
|
99
22
|
"[role='listbox']",
|
|
100
23
|
"[data-sonner-toaster]",
|
|
24
|
+
"[data-agent-native-share-overlay]",
|
|
101
25
|
].join(",");
|
|
102
26
|
|
|
103
27
|
const OPEN_NESTED_LAYER_SELECTOR = [
|
|
@@ -135,51 +59,6 @@ export function nestedLayerDismissGuards(): {
|
|
|
135
59
|
};
|
|
136
60
|
}
|
|
137
61
|
|
|
138
|
-
export function useResourceVisibilityMutation(
|
|
139
|
-
resourceType: string,
|
|
140
|
-
resourceId: string,
|
|
141
|
-
sharesQuery: SharesQuery,
|
|
142
|
-
) {
|
|
143
|
-
const queryClient = useQueryClient();
|
|
144
|
-
const setVisibility = useActionMutation("set-resource-visibility");
|
|
145
|
-
const shareQueryKey = useMemo(
|
|
146
|
-
() =>
|
|
147
|
-
["action", "list-resource-shares", { resourceType, resourceId }] as const,
|
|
148
|
-
[resourceType, resourceId],
|
|
149
|
-
);
|
|
150
|
-
|
|
151
|
-
const setResourceVisibility = (
|
|
152
|
-
next: Visibility,
|
|
153
|
-
options?: { onSuccess?: () => void },
|
|
154
|
-
) => {
|
|
155
|
-
const previous = queryClient.getQueryData<SharesResponse>(shareQueryKey);
|
|
156
|
-
queryClient.setQueryData<SharesResponse>(shareQueryKey, (current) =>
|
|
157
|
-
current ? { ...current, visibility: next } : current,
|
|
158
|
-
);
|
|
159
|
-
setVisibility.mutate(
|
|
160
|
-
{
|
|
161
|
-
resourceType,
|
|
162
|
-
resourceId,
|
|
163
|
-
visibility: next,
|
|
164
|
-
} as any,
|
|
165
|
-
{
|
|
166
|
-
onSuccess: () => {
|
|
167
|
-
void sharesQuery.refetch().finally(() => options?.onSuccess?.());
|
|
168
|
-
},
|
|
169
|
-
onError: () => {
|
|
170
|
-
if (previous) {
|
|
171
|
-
queryClient.setQueryData(shareQueryKey, previous);
|
|
172
|
-
} else {
|
|
173
|
-
void queryClient.invalidateQueries({ queryKey: shareQueryKey });
|
|
174
|
-
}
|
|
175
|
-
},
|
|
176
|
-
},
|
|
177
|
-
);
|
|
178
|
-
};
|
|
179
|
-
|
|
180
|
-
return { setResourceVisibility, isPending: setVisibility.isPending };
|
|
181
|
-
}
|
|
182
|
-
|
|
183
62
|
export function ShareSectionLabel({
|
|
184
63
|
children,
|
|
185
64
|
className,
|
|
@@ -268,461 +147,3 @@ export function CopyButton({
|
|
|
268
147
|
</Button>
|
|
269
148
|
);
|
|
270
149
|
}
|
|
271
|
-
|
|
272
|
-
export function Avatar({ label, org }: { label: string; org?: boolean }) {
|
|
273
|
-
const { avatarRef, avatarUrl } = useVisibleAvatarUrl(org ? null : label);
|
|
274
|
-
|
|
275
|
-
if (org) {
|
|
276
|
-
return (
|
|
277
|
-
<span
|
|
278
|
-
aria-hidden
|
|
279
|
-
className="inline-flex size-6 shrink-0 items-center justify-center rounded-full bg-muted text-[10px] font-semibold text-muted-foreground dark:bg-background"
|
|
280
|
-
>
|
|
281
|
-
<IconUsersGroup size={12} strokeWidth={1.75} />
|
|
282
|
-
</span>
|
|
283
|
-
);
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
return (
|
|
287
|
-
<UserAvatar ref={avatarRef} className="size-6 shrink-0">
|
|
288
|
-
{avatarUrl ? <UserAvatarImage src={avatarUrl} alt={label} /> : null}
|
|
289
|
-
<UserAvatarFallback className="bg-muted text-[10px] font-semibold text-muted-foreground dark:bg-background">
|
|
290
|
-
{(label.split("@")[0]?.[0] ?? label[0] ?? "?").toUpperCase()}
|
|
291
|
-
</UserAvatarFallback>
|
|
292
|
-
</UserAvatar>
|
|
293
|
-
);
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
export function AccessAccordionRow({
|
|
297
|
-
icon,
|
|
298
|
-
label,
|
|
299
|
-
meta,
|
|
300
|
-
disabled,
|
|
301
|
-
open,
|
|
302
|
-
onOpenChange,
|
|
303
|
-
children,
|
|
304
|
-
}: {
|
|
305
|
-
icon: ReactNode;
|
|
306
|
-
label: ReactNode;
|
|
307
|
-
meta?: ReactNode;
|
|
308
|
-
disabled?: boolean;
|
|
309
|
-
open: boolean;
|
|
310
|
-
onOpenChange: (open: boolean) => void;
|
|
311
|
-
children?: ReactNode;
|
|
312
|
-
}) {
|
|
313
|
-
const summary = (
|
|
314
|
-
<>
|
|
315
|
-
{icon}
|
|
316
|
-
<span className="min-w-0 flex-1 truncate text-sm">{label}</span>
|
|
317
|
-
{meta ? (
|
|
318
|
-
<span className="shrink-0 truncate text-xs text-muted-foreground">
|
|
319
|
-
{meta}
|
|
320
|
-
</span>
|
|
321
|
-
) : null}
|
|
322
|
-
</>
|
|
323
|
-
);
|
|
324
|
-
|
|
325
|
-
if (!children) {
|
|
326
|
-
return (
|
|
327
|
-
<div className="flex min-h-8 w-full items-center gap-2 px-1.5 py-1">
|
|
328
|
-
{summary}
|
|
329
|
-
</div>
|
|
330
|
-
);
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
return (
|
|
334
|
-
<Collapsible open={open} onOpenChange={onOpenChange}>
|
|
335
|
-
<CollapsibleTrigger
|
|
336
|
-
disabled={disabled}
|
|
337
|
-
className="flex min-h-8 w-full cursor-pointer items-center gap-2 rounded-md px-1.5 py-1 text-start transition-colors hover:bg-muted/50 disabled:pointer-events-none disabled:opacity-50"
|
|
338
|
-
>
|
|
339
|
-
{summary}
|
|
340
|
-
{/* Sized like the row-level remove button so both trailing icons
|
|
341
|
-
share the same optical center. */}
|
|
342
|
-
<span className="flex size-6 shrink-0 items-center justify-center">
|
|
343
|
-
<IconChevronDown
|
|
344
|
-
aria-hidden
|
|
345
|
-
className={cn(
|
|
346
|
-
"size-3.5 opacity-50 transition-transform",
|
|
347
|
-
open && "rotate-180",
|
|
348
|
-
)}
|
|
349
|
-
/>
|
|
350
|
-
</span>
|
|
351
|
-
</CollapsibleTrigger>
|
|
352
|
-
<CollapsibleContent className="clips-collapsible-content">
|
|
353
|
-
<div className="pt-1">{children}</div>
|
|
354
|
-
</CollapsibleContent>
|
|
355
|
-
</Collapsible>
|
|
356
|
-
);
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
const ACCESS_ORDER: Visibility[] = ["public", "org", "private"];
|
|
360
|
-
|
|
361
|
-
export function GeneralAccessSelect({
|
|
362
|
-
visibility,
|
|
363
|
-
canManage,
|
|
364
|
-
isPending,
|
|
365
|
-
onChange,
|
|
366
|
-
}: {
|
|
367
|
-
visibility: Visibility;
|
|
368
|
-
canManage: boolean;
|
|
369
|
-
isPending: boolean;
|
|
370
|
-
onChange: (next: Visibility) => void;
|
|
371
|
-
}) {
|
|
372
|
-
const t = useT();
|
|
373
|
-
const { data: org } = useOrg();
|
|
374
|
-
const orgName = org?.orgName;
|
|
375
|
-
const meta = VIS_META[visibility];
|
|
376
|
-
|
|
377
|
-
const optionLabel = (value: Visibility) => {
|
|
378
|
-
if (value !== "org") return t(`shareUi.accessOptions.${value}`);
|
|
379
|
-
return orgName
|
|
380
|
-
? t("shareUi.accessOptions.org", { orgName })
|
|
381
|
-
: t("shareUi.accessOptions.orgFallback");
|
|
382
|
-
};
|
|
383
|
-
|
|
384
|
-
return (
|
|
385
|
-
<Select
|
|
386
|
-
value={visibility}
|
|
387
|
-
onValueChange={(v) => onChange(v as Visibility)}
|
|
388
|
-
disabled={!canManage || isPending}
|
|
389
|
-
>
|
|
390
|
-
{/* The trigger spans the whole row so the hover affordance matches the
|
|
391
|
-
hit area. The icon is a bare svg, not a wrapped span, because the
|
|
392
|
-
shared trigger applies line-clamp to every direct span child. */}
|
|
393
|
-
<SelectTrigger
|
|
394
|
-
size="sm"
|
|
395
|
-
aria-label={t("shareUi.selectAccess")}
|
|
396
|
-
className="w-full cursor-pointer justify-start gap-2 rounded-md border-0 bg-transparent px-1.5 py-1 text-sm shadow-none transition-colors hover:bg-muted/50 focus:ring-0 focus:ring-offset-0 [&>span]:flex-1 [&>span]:text-start [&>svg:last-child]:me-1"
|
|
397
|
-
>
|
|
398
|
-
<meta.Icon
|
|
399
|
-
aria-hidden
|
|
400
|
-
strokeWidth={1.75}
|
|
401
|
-
className="size-6 shrink-0 rounded-full bg-muted p-1.5 text-muted-foreground"
|
|
402
|
-
/>
|
|
403
|
-
<SelectValue>{optionLabel(visibility)}</SelectValue>
|
|
404
|
-
</SelectTrigger>
|
|
405
|
-
<SelectContent align="start">
|
|
406
|
-
{ACCESS_ORDER.map((value) => {
|
|
407
|
-
const OptionIcon = VIS_META[value].Icon;
|
|
408
|
-
return (
|
|
409
|
-
<SelectItem
|
|
410
|
-
key={value}
|
|
411
|
-
value={value}
|
|
412
|
-
className="ps-2 pe-8 [&>span:first-child]:start-auto [&>span:first-child]:end-2"
|
|
413
|
-
>
|
|
414
|
-
<span className="flex items-center gap-2">
|
|
415
|
-
<OptionIcon
|
|
416
|
-
aria-hidden
|
|
417
|
-
size={16}
|
|
418
|
-
strokeWidth={1.75}
|
|
419
|
-
className="shrink-0 text-muted-foreground"
|
|
420
|
-
/>
|
|
421
|
-
{optionLabel(value)}
|
|
422
|
-
</span>
|
|
423
|
-
</SelectItem>
|
|
424
|
-
);
|
|
425
|
-
})}
|
|
426
|
-
</SelectContent>
|
|
427
|
-
</Select>
|
|
428
|
-
);
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
export function InvitePeopleField({
|
|
432
|
-
resourceType,
|
|
433
|
-
resourceId,
|
|
434
|
-
resourceUrl,
|
|
435
|
-
sharesQuery,
|
|
436
|
-
onError,
|
|
437
|
-
}: {
|
|
438
|
-
resourceType: string;
|
|
439
|
-
resourceId: string;
|
|
440
|
-
resourceUrl?: string;
|
|
441
|
-
sharesQuery: SharesQuery;
|
|
442
|
-
onError?: (err: unknown) => void;
|
|
443
|
-
}) {
|
|
444
|
-
const t = useT();
|
|
445
|
-
const share = useActionMutation("share-resource");
|
|
446
|
-
const [email, setEmail] = useState("");
|
|
447
|
-
const [role, setRole] = useState<Role>("viewer");
|
|
448
|
-
const [notifyPeople, setNotifyPeople] = useState(true);
|
|
449
|
-
const hasInviteEmail = email.trim().length > 0;
|
|
450
|
-
|
|
451
|
-
const handleAdd = () => {
|
|
452
|
-
const trimmed = email.trim().toLowerCase();
|
|
453
|
-
if (!trimmed) return;
|
|
454
|
-
share.mutate(
|
|
455
|
-
{
|
|
456
|
-
resourceType,
|
|
457
|
-
resourceId,
|
|
458
|
-
principalType: "user",
|
|
459
|
-
principalId: trimmed,
|
|
460
|
-
role,
|
|
461
|
-
notify: notifyPeople,
|
|
462
|
-
...(resourceUrl ? { resourceUrl } : {}),
|
|
463
|
-
} as any,
|
|
464
|
-
{
|
|
465
|
-
onSuccess: () => {
|
|
466
|
-
setEmail("");
|
|
467
|
-
void sharesQuery.refetch();
|
|
468
|
-
},
|
|
469
|
-
onError: (err: unknown) => onError?.(err),
|
|
470
|
-
},
|
|
471
|
-
);
|
|
472
|
-
};
|
|
473
|
-
|
|
474
|
-
return (
|
|
475
|
-
<div className="space-y-2">
|
|
476
|
-
<div className="flex items-stretch gap-1.5">
|
|
477
|
-
<div className="flex h-8 min-w-0 flex-1 items-center overflow-hidden rounded-md border border-input bg-background focus-within:ring-1 focus-within:ring-ring">
|
|
478
|
-
<Input
|
|
479
|
-
type="email"
|
|
480
|
-
placeholder={t("shareUi.addPeopleByEmail")}
|
|
481
|
-
value={email}
|
|
482
|
-
onChange={(e) => setEmail(e.target.value)}
|
|
483
|
-
onKeyDown={(e) => {
|
|
484
|
-
if (e.key === "Enter") handleAdd();
|
|
485
|
-
}}
|
|
486
|
-
autoComplete="off"
|
|
487
|
-
className="h-full min-w-0 flex-1 rounded-none border-0 bg-transparent text-sm shadow-none focus-visible:ring-0 focus-visible:ring-offset-0"
|
|
488
|
-
/>
|
|
489
|
-
<Select value={role} onValueChange={(v) => setRole(v as Role)}>
|
|
490
|
-
<SelectTrigger className="h-full w-auto shrink-0 gap-1 rounded-none border-0 bg-transparent px-2 text-xs text-muted-foreground shadow-none focus:ring-0 focus:ring-offset-0">
|
|
491
|
-
<SelectValue>{t(`shareUi.roles.${role}`)}</SelectValue>
|
|
492
|
-
</SelectTrigger>
|
|
493
|
-
<SelectContent align="end">
|
|
494
|
-
{ROLE_OPTIONS.map((opt) => (
|
|
495
|
-
<SelectItem key={opt.value} value={opt.value}>
|
|
496
|
-
{t(`shareUi.roles.${opt.value}`)}
|
|
497
|
-
</SelectItem>
|
|
498
|
-
))}
|
|
499
|
-
</SelectContent>
|
|
500
|
-
</Select>
|
|
501
|
-
</div>
|
|
502
|
-
<Button
|
|
503
|
-
type="button"
|
|
504
|
-
size="icon-sm"
|
|
505
|
-
onClick={handleAdd}
|
|
506
|
-
disabled={!hasInviteEmail || share.isPending}
|
|
507
|
-
aria-label={t("shareUi.invite")}
|
|
508
|
-
title={t("shareUi.invite")}
|
|
509
|
-
className="shrink-0"
|
|
510
|
-
>
|
|
511
|
-
<IconSend2 size={16} />
|
|
512
|
-
</Button>
|
|
513
|
-
</div>
|
|
514
|
-
{hasInviteEmail ? (
|
|
515
|
-
<label className="flex items-center gap-2 text-xs text-muted-foreground">
|
|
516
|
-
<Checkbox
|
|
517
|
-
checked={notifyPeople}
|
|
518
|
-
onCheckedChange={(checked) => setNotifyPeople(checked === true)}
|
|
519
|
-
/>
|
|
520
|
-
{t("shareUi.notifyPeople")}
|
|
521
|
-
</label>
|
|
522
|
-
) : null}
|
|
523
|
-
</div>
|
|
524
|
-
);
|
|
525
|
-
}
|
|
526
|
-
|
|
527
|
-
export function PeopleAccessSection({
|
|
528
|
-
resourceType,
|
|
529
|
-
resourceId,
|
|
530
|
-
sharesQuery,
|
|
531
|
-
canManage,
|
|
532
|
-
roleCopy,
|
|
533
|
-
onError,
|
|
534
|
-
}: {
|
|
535
|
-
resourceType: string;
|
|
536
|
-
resourceId: string;
|
|
537
|
-
sharesQuery: SharesQuery;
|
|
538
|
-
canManage: boolean;
|
|
539
|
-
roleCopy?: Partial<Record<Role, RoleCopy>>;
|
|
540
|
-
onError?: (err: unknown, action: "permission" | "remove") => void;
|
|
541
|
-
}) {
|
|
542
|
-
const t = useT();
|
|
543
|
-
const data = sharesQuery.data;
|
|
544
|
-
const people = useMemo(
|
|
545
|
-
() => [
|
|
546
|
-
...(data?.ownerEmail ? [data.ownerEmail] : []),
|
|
547
|
-
...(data?.shares.map((s) => s.principalId) ?? []),
|
|
548
|
-
],
|
|
549
|
-
[data],
|
|
550
|
-
);
|
|
551
|
-
const [first, ...rest] = people;
|
|
552
|
-
const [open, setOpen] = useState(false);
|
|
553
|
-
|
|
554
|
-
const firstShare = data?.shares.find((s) => s.principalId === first);
|
|
555
|
-
const firstRole = firstShare
|
|
556
|
-
? (roleCopy?.[firstShare.role]?.label ??
|
|
557
|
-
t(`shareUi.roles.${firstShare.role}`))
|
|
558
|
-
: t("shareUi.ownerRole");
|
|
559
|
-
|
|
560
|
-
return (
|
|
561
|
-
<AccessAccordionRow
|
|
562
|
-
icon={
|
|
563
|
-
first ? (
|
|
564
|
-
<Avatar label={first} />
|
|
565
|
-
) : (
|
|
566
|
-
<span className="inline-block size-6 shrink-0 rounded-full bg-muted" />
|
|
567
|
-
)
|
|
568
|
-
}
|
|
569
|
-
label={
|
|
570
|
-
!first
|
|
571
|
-
? t("shareUi.onlyYou")
|
|
572
|
-
: open || rest.length === 0
|
|
573
|
-
? first
|
|
574
|
-
: t("shareUi.othersCount", { count: rest.length, email: first })
|
|
575
|
-
}
|
|
576
|
-
meta={
|
|
577
|
-
first && (open || rest.length === 0)
|
|
578
|
-
? firstRole
|
|
579
|
-
: t("shareUi.canAccess")
|
|
580
|
-
}
|
|
581
|
-
disabled={sharesQuery.isLoading}
|
|
582
|
-
open={open}
|
|
583
|
-
onOpenChange={setOpen}
|
|
584
|
-
>
|
|
585
|
-
{rest.length > 0 ? (
|
|
586
|
-
<PeopleAccessSettingsBody
|
|
587
|
-
resourceType={resourceType}
|
|
588
|
-
resourceId={resourceId}
|
|
589
|
-
sharesQuery={sharesQuery}
|
|
590
|
-
canManage={canManage}
|
|
591
|
-
roleCopy={roleCopy}
|
|
592
|
-
onError={onError}
|
|
593
|
-
excludePrincipalId={first}
|
|
594
|
-
/>
|
|
595
|
-
) : null}
|
|
596
|
-
</AccessAccordionRow>
|
|
597
|
-
);
|
|
598
|
-
}
|
|
599
|
-
|
|
600
|
-
export function PeopleAccessSettingsBody({
|
|
601
|
-
resourceType,
|
|
602
|
-
resourceId,
|
|
603
|
-
sharesQuery,
|
|
604
|
-
canManage,
|
|
605
|
-
roleCopy,
|
|
606
|
-
onError,
|
|
607
|
-
excludePrincipalId,
|
|
608
|
-
}: {
|
|
609
|
-
resourceType: string;
|
|
610
|
-
resourceId: string;
|
|
611
|
-
sharesQuery: SharesQuery;
|
|
612
|
-
canManage: boolean;
|
|
613
|
-
roleCopy?: Partial<Record<Role, RoleCopy>>;
|
|
614
|
-
onError?: (err: unknown, action: "permission" | "remove") => void;
|
|
615
|
-
excludePrincipalId?: string;
|
|
616
|
-
}) {
|
|
617
|
-
const t = useT();
|
|
618
|
-
const share = useActionMutation("share-resource");
|
|
619
|
-
const unshare = useActionMutation("unshare-resource");
|
|
620
|
-
const data = sharesQuery.data;
|
|
621
|
-
const ownerEmail = data?.ownerEmail ?? null;
|
|
622
|
-
const showOwner = ownerEmail !== null && ownerEmail !== excludePrincipalId;
|
|
623
|
-
const shares = (data?.shares ?? []).filter(
|
|
624
|
-
(s) => s.principalId !== excludePrincipalId,
|
|
625
|
-
);
|
|
626
|
-
const getRoleLabel = (value: Role) =>
|
|
627
|
-
roleCopy?.[value]?.label ?? t(`shareUi.roles.${value}`);
|
|
628
|
-
|
|
629
|
-
const handleChangeRole = (s: Share, nextRole: Role) => {
|
|
630
|
-
if (nextRole === s.role) return;
|
|
631
|
-
share.mutate(
|
|
632
|
-
{
|
|
633
|
-
resourceType,
|
|
634
|
-
resourceId,
|
|
635
|
-
principalType: s.principalType,
|
|
636
|
-
principalId: s.principalId,
|
|
637
|
-
role: nextRole,
|
|
638
|
-
notify: false,
|
|
639
|
-
} as any,
|
|
640
|
-
{
|
|
641
|
-
onSuccess: () => sharesQuery.refetch(),
|
|
642
|
-
onError: (err: unknown) => onError?.(err, "permission"),
|
|
643
|
-
},
|
|
644
|
-
);
|
|
645
|
-
};
|
|
646
|
-
|
|
647
|
-
const handleRemove = (s: Share) => {
|
|
648
|
-
unshare.mutate(
|
|
649
|
-
{
|
|
650
|
-
resourceType,
|
|
651
|
-
resourceId,
|
|
652
|
-
principalType: s.principalType,
|
|
653
|
-
principalId: s.principalId,
|
|
654
|
-
} as any,
|
|
655
|
-
{
|
|
656
|
-
onSuccess: () => sharesQuery.refetch(),
|
|
657
|
-
onError: (err: unknown) => onError?.(err, "remove"),
|
|
658
|
-
},
|
|
659
|
-
);
|
|
660
|
-
};
|
|
661
|
-
|
|
662
|
-
return (
|
|
663
|
-
<ul className="flex max-h-72 flex-col gap-1 overflow-y-auto p-0 m-0">
|
|
664
|
-
{showOwner ? (
|
|
665
|
-
<li className="flex min-h-8 items-center gap-2 px-1.5 py-1 text-sm">
|
|
666
|
-
<Avatar label={ownerEmail} />
|
|
667
|
-
<span className="min-w-0 flex-1 truncate">{ownerEmail}</span>
|
|
668
|
-
<span className="text-xs text-muted-foreground">
|
|
669
|
-
{t("shareUi.ownerRole")}
|
|
670
|
-
</span>
|
|
671
|
-
</li>
|
|
672
|
-
) : null}
|
|
673
|
-
|
|
674
|
-
{shares.map((s) => (
|
|
675
|
-
<li
|
|
676
|
-
key={`${s.principalType}:${s.principalId}`}
|
|
677
|
-
className="flex min-h-8 items-center gap-2 px-1.5 py-1 text-sm"
|
|
678
|
-
>
|
|
679
|
-
<Avatar label={s.principalId} org={s.principalType === "org"} />
|
|
680
|
-
<span className="min-w-0 flex-1 truncate">{s.principalId}</span>
|
|
681
|
-
{canManage ? (
|
|
682
|
-
<Select
|
|
683
|
-
value={s.role}
|
|
684
|
-
onValueChange={(value) => handleChangeRole(s, value as Role)}
|
|
685
|
-
disabled={share.isPending}
|
|
686
|
-
>
|
|
687
|
-
<SelectTrigger
|
|
688
|
-
size="sm"
|
|
689
|
-
className="w-auto shrink-0 gap-1 border-0 bg-transparent px-2 text-xs text-muted-foreground shadow-none focus:ring-0"
|
|
690
|
-
>
|
|
691
|
-
<SelectValue>{getRoleLabel(s.role)}</SelectValue>
|
|
692
|
-
</SelectTrigger>
|
|
693
|
-
<SelectContent align="end">
|
|
694
|
-
{ROLE_OPTIONS.map((opt) => (
|
|
695
|
-
<SelectItem key={opt.value} value={opt.value}>
|
|
696
|
-
{getRoleLabel(opt.value)}
|
|
697
|
-
</SelectItem>
|
|
698
|
-
))}
|
|
699
|
-
</SelectContent>
|
|
700
|
-
</Select>
|
|
701
|
-
) : (
|
|
702
|
-
<span className="shrink-0 text-xs text-muted-foreground">
|
|
703
|
-
{getRoleLabel(s.role)}
|
|
704
|
-
</span>
|
|
705
|
-
)}
|
|
706
|
-
{canManage ? (
|
|
707
|
-
<Button
|
|
708
|
-
type="button"
|
|
709
|
-
variant="ghost"
|
|
710
|
-
size="icon"
|
|
711
|
-
aria-label={t("shareUi.remove")}
|
|
712
|
-
onClick={() => handleRemove(s)}
|
|
713
|
-
className="h-7 w-7 shrink-0 text-muted-foreground"
|
|
714
|
-
>
|
|
715
|
-
<IconTrash size={14} />
|
|
716
|
-
</Button>
|
|
717
|
-
) : null}
|
|
718
|
-
</li>
|
|
719
|
-
))}
|
|
720
|
-
|
|
721
|
-
{!shares.length && !showOwner ? (
|
|
722
|
-
<li className="px-1 py-1.5 text-sm text-muted-foreground">
|
|
723
|
-
{t("shareUi.noAccessYet")}
|
|
724
|
-
</li>
|
|
725
|
-
) : null}
|
|
726
|
-
</ul>
|
|
727
|
-
);
|
|
728
|
-
}
|
|
@@ -5,6 +5,10 @@ time from the command menu (Cmd+K → "What's new").
|
|
|
5
5
|
|
|
6
6
|
## 2026-10-22
|
|
7
7
|
|
|
8
|
+
### Added
|
|
9
|
+
|
|
10
|
+
- Blocks with several comment threads fold into one stack in the margin, which opens in place when you click it or a highlight
|
|
11
|
+
|
|
8
12
|
### Improved
|
|
9
13
|
|
|
10
14
|
- A collection linked to a big GitHub folder fills faster, and pushes to other linked collections aren't held up while it does
|
|
@@ -13,6 +17,7 @@ time from the command menu (Cmd+K → "What's new").
|
|
|
13
17
|
|
|
14
18
|
### Fixed
|
|
15
19
|
|
|
20
|
+
- Opening a page no longer saves an edit again after another open tab's save already stored it.
|
|
16
21
|
- Changes made from a menu or popover, such as an image's alt text, are always saved.
|
|
17
22
|
- Agents can read saved page titles and text after an unrelated icon change fails.
|
|
18
23
|
- Text an agent writes inside a callout, column or synced block without indenting it now shows on the page and survives your edits.
|
|
@@ -42,6 +47,7 @@ time from the command menu (Cmd+K → "What's new").
|
|
|
42
47
|
|
|
43
48
|
### Fixed
|
|
44
49
|
|
|
50
|
+
- Suggested edits that add, remove, split, or merge paragraphs, line breaks, and list items now show where the break goes and save as one clear suggestion
|
|
45
51
|
- Builder content sync keeps read and write status accurate when API responses fail.
|
|
46
52
|
- HTML table cells preserve a trailing backslash when opening Markdown.
|
|
47
53
|
- Preserve empty paragraph colors and refuse unsupported inline image and oversized math formatting before content is stored.
|
|
@@ -109,6 +115,7 @@ time from the command menu (Cmd+K → "What's new").
|
|
|
109
115
|
|
|
110
116
|
### Fixed
|
|
111
117
|
|
|
118
|
+
- Accepting a suggestion on a Page that changed since it was suggested no longer shows "Something went wrong"
|
|
112
119
|
- A page open in a tab that shows it's in the trash reopens within seconds when someone restores it.
|
|
113
120
|
- A page restored from Trash returns to its place in its collection, and edits to a page in Trash are refused
|
|
114
121
|
- Agent-written Markdown keeps nested lists indented with spaces, underscore bold and italics, and escaped punctuation
|