feat: workflow sharing and ComfyHub publish flow (#8951)

## Summary

Add workflow sharing by URL and a multi-step ComfyHub publish wizard,
gated by feature flags and an optional profile gate.

## Changes

- **What**: Share dialog with URL generation and asset warnings;
ComfyHub publish wizard (Describe → Examples → Finish) with thumbnail
upload and tags; profile gate flow; shared workflow URL loader with
confirmation dialog
- **Dependencies**: None (new `sharing/` module under
`src/platform/workflow/`)

## Review Focus

- Three new feature flags: `workflow_sharing_enabled`,
`comfyhub_upload_enabled`, `comfyhub_profile_gate_enabled`
- Share service API contract and stale-share detection
(`workflowShareService.ts`)
- Publish wizard and profile gate state management
- Shared workflow URL loading and query-param preservation

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8951-feat-share-workflow-by-URL-30b6d73d3650813ebbfafdad775bfb33)
by [Unito](https://www.unito.io)

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Amp <amp@ampcode.com>
Co-authored-by: github-actions <github-actions@github.com>
Co-authored-by: GitHub Action <action@github.com>
This commit is contained in:
Alexander Brown
2026-03-05 16:33:06 -08:00
committed by GitHub
parent 6c2680f0ba
commit 1bac5d9bdd
71 changed files with 6448 additions and 598 deletions

View File

@@ -77,9 +77,7 @@
>
{{ contentTitle }}
</h2>
<div
class="min-h-0 flex-1 px-6 pt-0 pb-10 overflow-y-auto scrollbar-custom"
>
<div :class="contentContainerClass">
<slot name="content" />
</div>
</main>
@@ -153,15 +151,20 @@ const SIZE_CLASSES = {
} as const
type ModalSize = keyof typeof SIZE_CLASSES
type ContentPadding = 'default' | 'compact' | 'none'
const {
contentTitle,
rightPanelTitle,
size = 'lg'
size = 'lg',
leftPanelWidth = '14rem',
contentPadding = 'default'
} = defineProps<{
contentTitle: string
rightPanelTitle?: string
size?: ModalSize
leftPanelWidth?: string
contentPadding?: ContentPadding
}>()
const sizeClasses = computed(() => SIZE_CLASSES[size])
@@ -197,10 +200,18 @@ const showLeftPanel = computed(() => {
return shouldShow
})
const contentContainerClass = computed(() =>
cn(
'flex min-h-0 flex-1 flex-col overflow-y-auto scrollbar-custom',
contentPadding === 'default' && 'px-6 pt-0 pb-10',
contentPadding === 'compact' && 'px-6 pt-0 pb-2'
)
)
const gridStyle = computed(() => ({
gridTemplateColumns: hasRightPanel.value
? `${hasLeftPanel.value && showLeftPanel.value ? '14rem' : '0rem'} 1fr ${isRightPanelOpen.value ? '18rem' : '0rem'}`
: `${hasLeftPanel.value && showLeftPanel.value ? '14rem' : '0rem'} 1fr`
? `${hasLeftPanel.value && showLeftPanel.value ? leftPanelWidth : '0rem'} 1fr ${isRightPanelOpen.value ? '18rem' : '0rem'}`
: `${hasLeftPanel.value && showLeftPanel.value ? leftPanelWidth : '0rem'} 1fr`
}))
const toggleLeftPanel = () => {