DT-3751 - download external payloads#3345
Conversation
…load component Consolidates two divergent payload display components into a single `<Payload />` component at `src/lib/components/payload.svelte`. Previously, `payload-decoder.svelte` (Svelte 5 runes, required a `children` snippet, always decoded full attribute trees) and `metadata-decoder.svelte` (Svelte 4 options API with slot syntax, decoded single payloads to truncated summary strings) served different display purposes but used inconsistent APIs and decoding paths across 65+ call sites. The new component unifies both under a single `mode` prop: - `code-block` (default): renders a CodeBlock with the decoded value, replacing all PayloadDecoder + CodeBlock snippet patterns - `summary`: truncates to 120 chars and renders a Badge, replacing all MetadataDecoder usages - `inline-truncated`: renders the compact .payload pre block used in event detail rows - `children` snippet: escape hatch for callers that need custom rendering (schedule input, timeline SVG text elements) Both old components used a single decoding path internally (cloneAllPotentialPayloadsWithCodec -> decodePayloadAttributes -> stringifyWithBigInt). MetadataDecoder previously used a separate decodeSingleReadablePayloadWithCodec path; the new component uses the same unified path for consistency. The component is written in Svelte 5 runes throughout and imports from $app/state instead of $app/stores, matching the newer direction of the codebase. Migrated files: - src/lib/components/event/event-card.svelte - src/lib/components/event/event-details-row.svelte (+ moved .payload CSS) - src/lib/components/event/event-metadata-expanded.svelte - src/lib/components/event/event-summary-row.svelte - src/lib/components/lines-and-dots/svg/group-details-text.svelte - src/lib/components/lines-and-dots/svg/timeline-graph-row.svelte - src/lib/components/schedule/schedule-form/schedule-input-payload.svelte - src/lib/components/standalone-activities/activity-input-and-outcome.svelte - src/lib/components/workflow/client-actions/update-confirmation-modal.svelte - src/lib/components/workflow/input-and-results-payload.svelte - src/lib/components/workflow/metadata/metadata-events.svelte - src/lib/components/workflow/pending-activity/pending-activity-card.svelte - src/lib/components/workflow/workflow-json-navigator.svelte - src/lib/pages/standalone-activity-details.svelte - src/lib/pages/standalone-activity-search-attributes.svelte - src/lib/pages/workflow-memo.svelte - src/lib/pages/workflow-metadata.svelte - src/lib/pages/workflow-search-attributes.svelte All 1730 tests pass.
Moves payload.svelte into src/lib/components/payload/ and adds a USAGE.md report documenting all 31 call sites grouped by feature area. Updates all 17 import paths accordingly.
… components Replace the 14-prop multi-mode payload.svelte with four single-purpose components: PayloadCodeBlock, PayloadSummary, PayloadDecoder, and PayloadInline. Each component carries only the props relevant to its render mode, eliminating dead props at call sites. Also updates the decode paths to use the renamed decode-payload.ts API (decodeEventAttributes, parsePayloadAttributes) following the #3302 cleanup.
Replace decodeEventAttributes with decodeUserMetadata — the correct decode path for a single raw Payload (Phase 2 codec only, no attribute tree walking). Also add onDecode callback prop, called after a successful decode, consistent with PayloadCodeBlock.
…lue utility Remove 3-way duplication of getInitialValue/onMount decode logic from payload-code-block, payload-decoder, and payload-inline by extracting getInitialPayloadValue and decodePayloadValue into src/lib/utilities/decode-payload-value.ts.
Components now re-decode when value or fieldName props change after mount, fixing the reactivity gap that caused stale decoded content when parent components updated their payload bindings.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
| downloadLoading = true; | ||
| let data: Payloads | undefined = undefined; | ||
| try { | ||
| data = await downloadExternalPayloadWithCodec(payload); |
There was a problem hiding this comment.
⚠️ 'data.payloads' is possibly 'null' or 'undefined'.
| </div> | ||
| <div class="flex w-full flex-col gap-4 xl:w-1/3"> | ||
| <ScheduleInput | ||
| {scheduleId} |
There was a problem hiding this comment.
⚠️ Type 'IPayloads | null | undefined' is not assignable to type 'IPayloads'.
| <ScheduleInput | ||
| {scheduleId} | ||
| input={schedule?.schedule?.action?.startWorkflow?.input} | ||
| /> |
There was a problem hiding this comment.
⚠️ Type 'IScheduleSpec | null | undefined' is not assignable to type 'IScheduleSpec'.
| ): Promise<unknown[]> => { | ||
| if (!payloads) return null; | ||
| returnDataOnly: boolean = true, | ||
| ): Promise<unknown[]> { |
There was a problem hiding this comment.
⚠️ Argument of type 'IPayload[] | null | undefined' is not assignable to parameter of type 'unknown[]'.
| {@render children(decodedValue)} | ||
| {:then decodeResult} | ||
| {@render children(decodeResult)} | ||
| {/await} |
There was a problem hiding this comment.
I feel like we could really easily put the error state in here and pass it a retry snippet as well so we can get per request errors instead of the "last used" errors
| {testId} | ||
| language="json" | ||
| /> | ||
| {/if} |
There was a problem hiding this comment.
I feel like there's a lot of stuff going on with these files that could be weird or error, maybe we should throw down an error boundary somewhere just in case to prevent app crashes
| payload.metadata.messageType === | ||
| 'temporal.api.sdk.v1.ExternalStorageReference' | ||
| ); | ||
| }; |
There was a problem hiding this comment.
Just spitballing on these, I wonder if instead of using typeGuards (which are a typesystem escape hatch) we could use a discriminated union on like a data container so you could easily identify which phase we're in for decoding.
Just thinking out loud don't need to act on it.
* refactor: replace PayloadDecoder and MetadataDecoder with unified Payload component Consolidates two divergent payload display components into a single `<Payload />` component at `src/lib/components/payload.svelte`. Previously, `payload-decoder.svelte` (Svelte 5 runes, required a `children` snippet, always decoded full attribute trees) and `metadata-decoder.svelte` (Svelte 4 options API with slot syntax, decoded single payloads to truncated summary strings) served different display purposes but used inconsistent APIs and decoding paths across 65+ call sites. The new component unifies both under a single `mode` prop: - `code-block` (default): renders a CodeBlock with the decoded value, replacing all PayloadDecoder + CodeBlock snippet patterns - `summary`: truncates to 120 chars and renders a Badge, replacing all MetadataDecoder usages - `inline-truncated`: renders the compact .payload pre block used in event detail rows - `children` snippet: escape hatch for callers that need custom rendering (schedule input, timeline SVG text elements) Both old components used a single decoding path internally (cloneAllPotentialPayloadsWithCodec -> decodePayloadAttributes -> stringifyWithBigInt). MetadataDecoder previously used a separate decodeSingleReadablePayloadWithCodec path; the new component uses the same unified path for consistency. The component is written in Svelte 5 runes throughout and imports from $app/state instead of $app/stores, matching the newer direction of the codebase. Migrated files: - src/lib/components/event/event-card.svelte - src/lib/components/event/event-details-row.svelte (+ moved .payload CSS) - src/lib/components/event/event-metadata-expanded.svelte - src/lib/components/event/event-summary-row.svelte - src/lib/components/lines-and-dots/svg/group-details-text.svelte - src/lib/components/lines-and-dots/svg/timeline-graph-row.svelte - src/lib/components/schedule/schedule-form/schedule-input-payload.svelte - src/lib/components/standalone-activities/activity-input-and-outcome.svelte - src/lib/components/workflow/client-actions/update-confirmation-modal.svelte - src/lib/components/workflow/input-and-results-payload.svelte - src/lib/components/workflow/metadata/metadata-events.svelte - src/lib/components/workflow/pending-activity/pending-activity-card.svelte - src/lib/components/workflow/workflow-json-navigator.svelte - src/lib/pages/standalone-activity-details.svelte - src/lib/pages/standalone-activity-search-attributes.svelte - src/lib/pages/workflow-memo.svelte - src/lib/pages/workflow-metadata.svelte - src/lib/pages/workflow-search-attributes.svelte All 1730 tests pass. * remove unused component, fix css * refactor: move Payload component into payload/ directory Moves payload.svelte into src/lib/components/payload/ and adds a USAGE.md report documenting all 31 call sites grouped by feature area. Updates all 17 import paths accordingly. * docs: add Payload component improvement suggestions * docs: add decode-payload usage report and refactoring recommendations * refactor(payload): split Payload component into focused mode-specific components Replace the 14-prop multi-mode payload.svelte with four single-purpose components: PayloadCodeBlock, PayloadSummary, PayloadDecoder, and PayloadInline. Each component carries only the props relevant to its render mode, eliminating dead props at call sites. Also updates the decode paths to use the renamed decode-payload.ts API (decodeEventAttributes, parsePayloadAttributes) following the #3302 cleanup. * fix(payload-summary): use decodeUserMetadata and add onDecode prop Replace decodeEventAttributes with decodeUserMetadata — the correct decode path for a single raw Payload (Phase 2 codec only, no attribute tree walking). Also add onDecode callback prop, called after a successful decode, consistent with PayloadCodeBlock. * refactor(payload): extract shared decode logic into decode-payload-value utility Remove 3-way duplication of getInitialValue/onMount decode logic from payload-code-block, payload-decoder, and payload-inline by extracting getInitialPayloadValue and decodePayloadValue into src/lib/utilities/decode-payload-value.ts. * fix(payload): replace onMount with \$effect for reactive value updates Components now re-decode when value or fieldName props change after mount, fixing the reactivity gap that caused stale decoded content when parent components updated their payload bindings. * add workflow with user metadata * remove unnecessary props from payload code block * fix summary display * add improvements * fix heading levels * add multi input workflow * get rid of fieldName prop and fix most of the call sites * fix remaining type errors * fix tests * rm inspect * rm console.log * WIP - download external payloads * add support for downloading externally stored payloads * add some error handling to downloads * add support for filename * fix CR feedback * remove bad key * fix export * remove unused type * cleanup unused stuff * fix possibly duplicate key * add language prop to PayloadCodeBlock * add copyIconTitle and copySuccessIconTitle to codeblock instance * refactor(payload): fix reactivity and race condition in payload-summary Replace $effect+$state async pattern with $derived promise + $effect cleanup to ensure all reactive deps (value, fallback, prefix, maxSummaryLength) are tracked synchronously. Use $effect cleanup function to cancel stale promise callbacks on re-run, preventing race conditions when value changes rapidly. Remove unused onDecode prop. * early return if no payloads in decode utils * add type guards * support inline external payloads * fix download error and payload codeblock types * fix schedule input * rm console.log * fix tests and strict mode * fix spec * fix typo * add some optional chaining
Description & motivation 💭
Adds download functionality for externally stored payloads (note: only for payloads that display in a Code Block). Other payload touch points will be addressed in a following PR.
WIP
This PR is a work in progress. Currently the download button shows, but the download functionality doesn't work.
Screenshots (if applicable) 📸
Generic error (when codec server not configured or download fails)
Error when codec server is configured, but no
/downloadendpointDisable download button and show tooltip when codec server not configured
Sample download w/ semantic filename
Event History Row w/ External Payload
Design Considerations 🎨
Testing 🧪
How was this tested 👻
Steps for others to test: 🚶🏽♂️🚶🏽♀️
pnpm iandpnpm build:serverandpnpm devExternal Storage Bug Bash 04/17AWS_PROFILEandS3_BUCKETenvironment variablesgo run demos/worker/main.go, and in a separate shell session, rungo run demos/codecserver/main.gotemporal workflow start --task-queue "echo-task-queue" --type "GenerateDocument" --workflow-id "generate-doc" --input '1000000'to run a workflow with an externally stored payload.Checklists
Draft Checklist
Merge Checklist
Issue(s) closed
Docs
Any docs updates needed?