Files
windmill/frontend/src/lib/components/sessions/SessionDiffDrawer.svelte
T
GuilhemandClaude Opus 4.8 a6c0b3756b feat: chat-scoped session changes bar + unified diff drawer (#9762)
* feat(frontend): chat-scoped unified session changes bar

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* refactor(frontend): drop diff-baseline toggle, show natural per-row diffs

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(frontend): carry Draft marker to expanded raw-app file rows

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(frontend): show raw-app Draft badge once at tree root, not per file

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* refactor(frontend): reuse shared DraftBadge in session diff drawer

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(frontend): show draft-author avatars in session diff badge, icon-only in sidebar

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(frontend): keep badge pill around avatar in icon-only DraftBadge

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(frontend): small draft marker = indigo pen + avatar; correct itemKind label

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* chore(frontend): drop package-lock churn from merge (match origin/main)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(frontend): dedup diff-button count for legacy fork sessions; test mask helper

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(frontend): collapsible session diff panel + per-row open-diff action

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(frontend): shared sessionDeployModel for review & deploy (S1)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(frontend): model-driven session review drawer, deploy inert (S2)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(frontend): wire session deploy + on-behalf/conflict gating (S3)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(frontend): behind banner + Update fork + deployment request (S4)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(frontend): session changes dock opens drawer by filter (S5)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(frontend): session review UI polish (badge, dock, In parent, tree width)

- draft rows show only the avatar DraftBadge, not a duplicate state pill
- drop redundant dock Review button (same as "N to review")
- rename Done -> In parent with a "deployed in parent workspace" tooltip
- widen the file tree

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(frontend): existence-gate In parent rows; Badge filters; badge hover

- drop discarded mask-only items from the In-parent segment (existence check)
- use the Badge component for the drawer filter segments and the changes dock
- soften the blue Badge hover (blue-50 base was jumping to blue-200)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(frontend): polish session diff drawer (layout, badges, actions)

- remove empty fork-banner gap; uniform sidebar tree padding
- full-bleed diff list: drop card borders/side padding, separators between items
- clamp tree x-overflow; right-align tree badges (min-w-0 on the row button)
- brand-compliant selected filter badges; smaller draft badge
- hide per-row open-diff button when the panel is open
- rename "Delete draft" to "Discard draft" (destructive); remove header Review button
- larger sm deploy/discard action buttons; remove per-item diff-content collapse

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(frontend): session Edits dock — deploy gating + change-op tracking

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(frontend): session bar per-status badges; drop change-op tracking

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(frontend): unwrap raw apps into per-file tree in session diff sidebar

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(frontend): dot-parcours pipeline (badge-derived, melt tooltip) + discard confirm

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(frontend): behind-only session item reads as deployed, not bare

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* style(frontend): even sidebar tree margins; gutter-aware right padding

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(frontend): pass chat id as from_session; wire deploying flag

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(frontend): session diff drawer scroll-to-flush, ordering, spacer, deploy gating

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(frontend): preserve chat mask on compact; guard stale existence checks; clear poll timers

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(frontend): refresh bar after drawer deploys; conflict hint over chip; plain conflict badge

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* style(frontend): diff drawer card layout with flash ring and aligned insets

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(frontend): hide stale deployed chip once row status badge reads deployed

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* refactor(frontend): session dock to two states; drop parent deploy

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(frontend): staged deploy animation in session edits drawer

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(frontend): stale-draft warning in session edits drawer

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(frontend): keep chat mask honest on deploy and discard

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(frontend): map trigger_email deploy kind; serialize mask persists

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(frontend): reset mask on new chat; close review-flagged races

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* style(frontend): rename session drawer title to Edited during session

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(frontend): keep mask persist queue alive after a failed save

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(frontend): sync session chatId on chat rotation; gate deploy on canWrite

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(frontend): keep compare handoff for deletion-only session edits

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(frontend): hold deploy success beat across re-keyed rows

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-06 09:23:54 +02:00

108 lines
3.7 KiB
Svelte

<script lang="ts">
import WorkspaceDiffDrawer from './WorkspaceDiffDrawer.svelte'
import { ArrowRight, GitFork, Pencil } from 'lucide-svelte'
import { userWorkspaces } from '$lib/stores'
import { useSessionDeployModel } from './sessionDeployModel.svelte'
import type { DeployItem } from './sessionDeployModel'
// Session "Edits" drawer. Builds the deploy model over the session
// workspace's drafts and hands it to WorkspaceDiffDrawer, which renders the
// tree + scroll-through diff column. The parent workspace only informs the
// title identity and the compare-page link — deploys land in the session
// workspace itself.
let {
workspaceId,
parentWorkspaceId,
chatId,
keys,
onDataChanged,
onItemDeployed,
onItemDiscarded
}: {
workspaceId: string
parentWorkspaceId?: string
/** CHAT id (Session.chatId — the IndexedDB `chats` key, NOT the session id),
* threaded to the footer's compare-page link as `from_session`: the compare
* page resolves it via readChatModifiedItems to preselect this chat's edits. */
chatId?: string
keys?: Set<string>
/** Notified after a deploy/discard mutated workspace state, so the owner
* (session bar) can refresh its cached draft sources. */
onDataChanged?: () => void
/** Mask maintenance, forwarded to the deploy model (see its args' docs):
* a deploy moves the item's mask entry to its deployed path, a discard
* drops it. */
onItemDeployed?: (item: DeployItem) => void
onItemDiscarded?: (item: DeployItem) => void
} = $props()
const isFork = $derived(!!parentWorkspaceId)
const ws = $derived($userWorkspaces.find((w) => w.id === workspaceId))
const parentWs = $derived(
parentWorkspaceId ? $userWorkspaces.find((w) => w.id === parentWorkspaceId) : undefined
)
let inner: WorkspaceDiffDrawer | undefined = $state(undefined)
const model = useSessionDeployModel(() => ({
workspaceId,
mask: keys,
onDataChanged,
onItemDeployed,
onItemDiscarded
}))
// Editor URL for a row (every item lives in the session workspace).
function editUrlFor(item: DeployItem): string | undefined {
const ws = encodeURIComponent(workspaceId)
const path = item.path
if (item.deployKind === 'flow') return `/flows/edit/${path}?workspace=${ws}`
if (item.deployKind === 'script') return `/scripts/edit/${path}?workspace=${ws}`
if (item.deployKind === 'app') return `/apps/edit/${path}?workspace=${ws}`
if (item.deployKind === 'raw_app') return `/apps_raw/edit/${path}?workspace=${ws}`
return undefined
}
const title = 'Edited during session'
// Compare page = the batch/PR surface. The footer hands off THIS session's
// edits via `from_session`, so the compare page opens preselected.
const compareSessionHref = $derived(
`/forks/compare?workspace_id=${encodeURIComponent(workspaceId)}&mode=${isFork ? 'fork' : 'draft'}` +
(chatId ? `&from_session=${encodeURIComponent(chatId)}` : '')
)
export function open() {
inner?.open()
}
</script>
<WorkspaceDiffDrawer
bind:this={inner}
{model}
{title}
{editUrlFor}
{compareSessionHref}
workspaceLabel={ws?.name ?? workspaceId}
>
{#snippet titleExtra()}
<div class="flex items-center gap-1.5 text-xs text-secondary min-w-0">
{#if isFork}
<GitFork class="w-3.5 h-3.5 shrink-0" />
<span class="font-medium truncate" title={ws?.name ?? workspaceId}>
{ws?.name ?? workspaceId}
</span>
<ArrowRight class="w-3 h-3 shrink-0 text-tertiary" />
<span class="font-medium truncate" title={parentWs?.name ?? parentWorkspaceId}>
{parentWs?.name ?? parentWorkspaceId}
</span>
{:else}
<Pencil class="w-3.5 h-3.5 shrink-0" />
<span class="font-medium truncate" title={ws?.name ?? workspaceId}>
{ws?.name ?? workspaceId}
</span>
{/if}
</div>
{/snippet}
</WorkspaceDiffDrawer>