Skip to content

Commit 7d91e65

Browse files
committed
feat(admin): adopt remote draft updates without a manual refresh
Subscribe to the core DRAFT_UPDATE broadcast through a typed signal in the socket layer, so the write page reacts to a head moving elsewhere instead of discovering it on the next save. With no unsaved edits the open draft is adopted silently through the existing load path, keeping scroll and panels; with unsaved edits a dismissible banner offers 'use new version' (confirmed, since it discards local work) or merge, reusing the same merge path as save conflicts. The subscription installs once and reads its handler through a ref, so it no longer re-subscribes on every keystroke.
1 parent 2703448 commit 7d91e65

8 files changed

Lines changed: 372 additions & 51 deletions

File tree

apps/admin/src/features/write/components/DraftHintBanner.tsx

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
import type { LucideIcon } from 'lucide-react'
2-
import { AlertCircle, History, X } from 'lucide-react'
2+
import { AlertCircle, CloudDownload, History, X } from 'lucide-react'
33

44
import { useI18n } from '~/i18n'
55
import { Button } from '~/ui/primitives/button'
66
import { cn } from '~/utils/cn'
77

88
interface DraftHintBannerProps {
9-
variant: 'list' | 'recovery'
9+
variant: 'list' | 'recovery' | 'remote-update'
1010
message: string
1111
actionLabel: string
1212
onAction: () => void
@@ -22,6 +22,7 @@ const variantConfig: Record<
2222
icon: LucideIcon
2323
cardClass: string
2424
iconClass: string
25+
messageClass: string
2526
actionVariant: 'primary' | 'secondary'
2627
dismissClass: string
2728
}
@@ -30,6 +31,7 @@ const variantConfig: Record<
3031
icon: History,
3132
cardClass: 'bg-surface-card border-border',
3233
iconClass: 'text-fg-muted',
34+
messageClass: 'text-fg',
3335
actionVariant: 'secondary',
3436
dismissClass:
3537
'text-fg-subtle hover:bg-black/[0.06] hover:text-fg dark:hover:bg-white/[0.08]',
@@ -39,17 +41,28 @@ const variantConfig: Record<
3941
cardClass:
4042
'bg-amber-50 border-amber-200 dark:bg-amber-950/30 dark:border-amber-900/40',
4143
iconClass: 'text-amber-600 dark:text-amber-400',
44+
messageClass: 'text-amber-900 dark:text-amber-100',
4245
actionVariant: 'primary',
4346
dismissClass:
4447
'text-amber-700/70 hover:bg-amber-100 hover:text-amber-900 dark:text-amber-300/70 dark:hover:bg-amber-900/40 dark:hover:text-amber-200',
4548
},
49+
'remote-update': {
50+
icon: CloudDownload,
51+
cardClass: 'bg-surface-card border-border',
52+
iconClass: 'text-accent',
53+
messageClass: 'text-fg',
54+
actionVariant: 'primary',
55+
dismissClass:
56+
'text-fg-subtle hover:bg-black/[0.06] hover:text-fg dark:hover:bg-white/[0.08]',
57+
},
4658
}
4759

4860
export function DraftHintBanner(props: DraftHintBannerProps) {
4961
const {
5062
icon: Icon,
5163
cardClass,
5264
iconClass,
65+
messageClass,
5366
actionVariant,
5467
dismissClass,
5568
} = variantConfig[props.variant]
@@ -67,9 +80,7 @@ export function DraftHintBanner(props: DraftHintBannerProps) {
6780
<span
6881
className={cn(
6982
'min-w-0 flex-1 text-sm max-sm:basis-[calc(100%-2rem)]',
70-
props.variant === 'recovery'
71-
? 'text-amber-900 dark:text-amber-100'
72-
: 'text-fg',
83+
messageClass,
7384
)}
7485
>
7586
{props.message}

0 commit comments

Comments
 (0)