Skip to content

refactor: replace PayloadDecoder and MetadataDecoder with unified Payload component#3299

Merged
rossedfort merged 36 commits into
mainfrom
refactor-payload-component
May 14, 2026
Merged

refactor: replace PayloadDecoder and MetadataDecoder with unified Payload component#3299
rossedfort merged 36 commits into
mainfrom
refactor-payload-component

Conversation

@rossedfort

@rossedfort rossedfort commented Apr 13, 2026

Copy link
Copy Markdown
Contributor

Description & motivation 💭

The monolithic component handled 3 rendering modes plus a headless decode mode through a single 14-prop interface, where most props were irrelevant to any given call site. This refactor replaces it with four focused components, each with a minimal prop surface.

New components

  • PayloadCodeBlock — renders a , replaces (~24 call sites). Uses decodeEventAttributes + parsePayloadAttributes for full event attribute tree decoding.
  • PayloadSummary — renders a or , replaces . Uses decodeUserMetadata (correct path for single raw Payloads — avoids double-decoding with codec servers).
  • PayloadInline — renders a truncated inline code block, replaces .
  • PayloadDecoder — headless decode-only component that passes decoded value to a child snippet.

Shared decode utility

Extracted the repeated decodeEventAttributes → parsePayloadAttributes → stringifyWithBigInt pipeline from PayloadCodeBlock, PayloadDecoder, and PayloadInline into src/lib/utilities/decode-payload-value.ts. Exports decodePayloadValue and getInitialPayloadValue with a DecodableValue union type.

Correctness fixes

  • PayloadSummary now uses decodeUserMetadata instead of decodeEventAttributes — the previous path would double-decode raw Payloads when a codec server is configured.
  • All four components use $effect instead of onMount, so decoded values update reactively when props change rather than only on mount.
  • Added onDecode callback to PayloadSummary (was missing, needed by timeline graph row).

Screenshots (if applicable) 📸

Design Considerations 🎨

Testing 🧪

How was this tested 👻

Steps for others to test: 🚶🏽‍♂️🚶🏽‍♀️

  • check out this branch, run pnpm dev, pnpm codec-server, and pnpm run-workflows.
  • check the payload-coverage-workflow and user-metadata-workflow to ensure all payloads are decoded

Optionally in addition to the above:

  1. in a separate terminal in the ui repo, run git worktree add /path/to/worktree main, cd /path/to/worktree, cp /path/to/ui/.env /path/to/worktree, pnpm i.
  2. change the VITE_API env variable in .env to "http://localhost:8081"
  3. change preview.port in vite.config.ts to 3031.
  4. Then run pnpm build:local and pnpm preview:local
  5. open http://localhost:3001 to spot-check differences between main and this branch.

Checklists

Draft Checklist

Merge Checklist

Issue(s) closed

Docs

Any docs updates needed?

…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.
@vercel

vercel Bot commented Apr 13, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
holocene Ready Ready Preview, Comment May 13, 2026 5:16pm

Request Review

let success;
let error = $state('');
let loading = $state(false);
let failure = $state<

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • ⚠️ Property 'failure' does not exist on type 'IOutcome | null | undefined'.

let failure = $state<
UpdateWorkflowResponse['outcome']['failure'] | undefined
>(undefined);
let success = $state<

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • ⚠️ Property 'success' does not exist on type 'IOutcome | null | undefined'.

@temporal-cicd

temporal-cicd Bot commented Apr 13, 2026

Copy link
Copy Markdown
Contributor
Warnings
⚠️

📊 Strict Mode: 115 errors in 20 files (12.2% of 941 total)

src/lib/services/data-encoder.ts (5)
  • L32:27: Type 'PotentialPayloads' is not assignable to type 'IPayloads'.
  • L49:8: Element implicitly has an 'any' type because expression of type '"Authorization"' can't be used to index type '{ 'Content-Type': string; 'X-Namespace': string; }'.
  • L52:8: Element implicitly has an 'any' type because expression of type '"Authorization-Extras"' can't be used to index type '{ 'Content-Type': string; 'X-Namespace': string; }'.
  • L56:6: Type 'PotentialPayloads' is not assignable to type 'IPayloads'.
  • L103:2: Type 'PotentialPayloads' is not assignable to type 'IPayloads'.
src/lib/utilities/decode-payload.ts (24)
  • L81:35: Argument of type '{ [k: string]: Uint8Array; } | null | undefined' is not assignable to parameter of type 'Record<string, unknown>'.
  • L84:9: 'payloadOrPayloads.payloads' is possibly 'null' or 'undefined'.
  • L85:28: Argument of type '{ [k: string]: Uint8Array; } | null | undefined' is not assignable to parameter of type 'Record<string, unknown>'.
  • L111:45: Argument of type '{ [k: string]: Uint8Array; } | null | undefined' is not assignable to parameter of type 'Record<string, unknown>'.
  • L124:45: Argument of type '{ [k: string]: Uint8Array; } | null | undefined' is not assignable to parameter of type 'Record<string, unknown>'.
  • L158:19: No overload matches this call.
  • L159:6: 'searchAttributes' is of type 'unknown'.
  • L159:52: Argument of type 'unknown' is not assignable to parameter of type 'IPayload'.
  • L167:19: No overload matches this call.
  • L168:6: 'memo' is of type 'unknown'.
  • L168:40: Argument of type 'unknown' is not assignable to parameter of type 'IPayload'.
  • L176:19: No overload matches this call.
  • L177:6: 'header' is of type 'unknown'.
  • L177:42: Argument of type 'unknown' is not assignable to parameter of type 'IPayload'.
  • L186:19: No overload matches this call.
  • L187:6: 'queryResult' is of type 'unknown'.
  • L187:47: Argument of type 'unknown' is not assignable to parameter of type 'IPayload'.
  • L281:17: Type 'null' is not assignable to type 'unknown[]'.
  • L282:54: Argument of type 'IPayload[] | null | undefined' is not assignable to parameter of type 'unknown[]'.
  • L328:57: Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{ payloads?: IPayload[] | null | undefined; } | { id: string; attributes: EventAttribute; timestamp: string; classification: "Scheduled" | "Unspecified" | ... 12 more ... | "CancelRequested"; ... 70 more ...; eventId: string; } | ... 61 more ... | { ...; }'.
  • L329:29: Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{ payloads?: IPayload[] | null | undefined; } | { id: string; attributes: EventAttribute; timestamp: string; classification: "Scheduled" | "Unspecified" | ... 12 more ... | "CancelRequested"; ... 70 more ...; eventId: string; } | ... 61 more ... | { ...; }'.
  • L331:8: Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{ payloads?: IPayload[] | null | undefined; } | { id: string; attributes: EventAttribute; timestamp: string; classification: "Scheduled" | "Unspecified" | ... 12 more ... | "CancelRequested"; ... 70 more ...; eventId: string; } | ... 61 more ... | { ...; }'.
  • L333:21: Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{ payloads?: IPayload[] | null | undefined; } | { id: string; attributes: EventAttribute; timestamp: string; classification: "Scheduled" | "Unspecified" | ... 12 more ... | "CancelRequested"; ... 70 more ...; eventId: string; } | ... 61 more ... | { ...; }'.
  • L335:10: Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{ payloads?: IPayload[] | null | undefined; } | { id: string; attributes: EventAttribute; timestamp: string; classification: "Scheduled" | "Unspecified" | ... 12 more ... | "CancelRequested"; ... 70 more ...; eventId: string; } | ... 61 more ... | { ...; }'.
src/lib/utilities/get-single-attribute-for-event.ts (11)
  • L130:13: 'getStackTrace' implicitly has return type 'any' because it does not have a return type annotation and is referenced directly or indirectly in one of its return expressions.
  • L234:35: No overload matches this call.
  • L239:20: Type 'object | null' is not assignable to type 'string | Record<string, unknown> | IPayloads | IPayload'.
  • L241:53: 'value' is possibly 'null'.
  • L241:53: Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{}'.
  • L243:25: 'value' is possibly 'undefined'.
  • L273:19: Function lacks ending return statement and return type does not include 'undefined'.
  • L274:44: No overload matches this call.
  • L312:41: Argument of type 'unknown' is not assignable to parameter of type 'IPayload'.
  • L328:46: No overload matches this call.
  • L341:34: 'event.attempt' is possibly 'null' or 'undefined'.
src/lib/services/workflow-service.ts (21)
  • L194:6: Type 'ErrorCallback' is not assignable to type '(error: unknown, toasts?: Toaster, errors?: Writable<NetworkError | null>, isBrowser?: boolean) => void'.
  • L256:4: Type 'ErrorCallback' is not assignable to type '(error: unknown, toasts?: Toaster, errors?: Writable<NetworkError | null>, isBrowser?: boolean) => void'.
  • L295:45: Argument of type 'unknown' is not assignable to parameter of type 'WorkflowExecutionAPIResponse | undefined'.
  • L428:28: No overload matches this call.
  • L648:4: Type 'ErrorCallback' is not assignable to type '(error: unknown, toasts?: Toaster, errors?: Writable<NetworkError | null>, isBrowser?: boolean) => void'.
  • L654:10: Argument of type '(response?: WorkflowExecutionAPIResponse) => WorkflowExecution' is not assignable to parameter of type '(value: unknown) => WorkflowExecution | PromiseLike'.
  • L684:4: Element implicitly has an 'any' type because expression of type 'string' can't be used to index type 'ISearchAttributes'.
  • L760:37: Argument of type 'SearchAttributeInput[]' is not assignable to parameter of type '{ type: "Unspecified" | "Keyword" | "Text" | "Int" | "Double" | "Bool" | "KeywordList" | "Datetime"; label: string; value?: any; }[]'.
  • L823:8: Type 'ErrorCallback' is not assignable to type '(error: unknown, toasts?: Toaster, errors?: Writable<NetworkError | null>, isBrowser?: boolean) => void'.
  • L839:6: 'startEvent.attributes.input.payloads' is possibly 'null' or 'undefined'.
  • L839:6: Argument of type 'IPayload | undefined' is not assignable to parameter of type 'IPayload'.
  • L844:8: 'workflow' is possibly 'undefined'.
  • L846:8: 'workflow' is possibly 'undefined'.
  • L854:8: 'workflow' is possibly 'undefined'.
  • L856:8: 'workflow' is possibly 'undefined'.
  • L1030:20: Argument of type 'WorkflowExecution | undefined' is not assignable to parameter of type 'WorkflowExecution'.
  • L1069:28: Argument of type 'string | undefined' is not assignable to parameter of type 'string'.
  • L1097:10: Property 'executions' does not exist on type 'WithoutNextPageToken'.
  • L1097:41: Argument of type '(token: string) => Promise' is not assignable to parameter of type '(token?: NextPageToken | undefined) => Promise'.
  • L1141:6: Type 'ErrorCallback' is not assignable to type '(error: unknown, toasts?: Toaster, errors?: Writable<NetworkError | null>, isBrowser?: boolean) => void'.
  • L1170:6: Type 'ErrorCallback' is not assignable to type '(error: unknown, toasts?: Toaster, errors?: Writable<NetworkError | null>, isBrowser?: boolean) => void'.
src/lib/utilities/decode-local-activity.ts (2)
  • L48:6: Argument of type 'WorkflowEvent | EventAttribute | IMemo | PotentiallyDecodable' is not assignable to parameter of type 'Optional<WorkflowEvent | EventAttribute | PotentiallyDecodable>'.
  • L53:41: Argument of type 'IPayload | undefined' is not assignable to parameter of type 'IPayload'.
src/lib/utilities/get-started-completed-and-task-failed-events.ts (6)
  • L88:6: Variable 'workflowStartedEvent' is used before being assigned.
  • L89:4: Type 'IPayloads | null' is not assignable to type 'IPayloads'.
  • L94:6: Variable 'workflowCompletedEvent' is used before being assigned.
  • L96:4: Type 'IPayloads | (IWorkflowExecutionFailedEventAttributes & { type: "workflowExecutionFailedEventAttributes"; }) | (IWorkflowExecutionTimedOutEventAttributes & { ...; }) | (IWorkflowExecutionTerminatedEventAttributes & { ...; }) | (IWorkflowExecutionCanceledEventAttributes & { ...; }) | null | undefined' is not assignable to type 'IPayloads | (EventAttribute & IWorkflowExecutionCompletedEventAttributes & { type: "workflowExecutionCompletedEventAttributes"; }) | ... 4 more ... | (EventAttribute & ... 1 more ... & { ...; })'.
  • L100:4: Variable 'input' is used before being assigned.
  • L101:4: Variable 'results' is used before being assigned.
src/routes/(app)/nexus/[id]/+layout.ts (1)
  • L24:4: 'endpoint.spec' is possibly 'undefined'.
src/lib/components/workflow/client-actions/update-confirmation-modal.svelte (4)
  • L40:38: Property 'failure' does not exist on type 'IOutcome | null | undefined'.
  • L43:38: Property 'success' does not exist on type 'IOutcome | null | undefined'.
  • L90:6: Type 'string | undefined' is not assignable to type 'string'.
  • L119:9: 'updateDefinitions.length' is possibly 'undefined'.
src/lib/components/event/event-card.svelte (11)
  • L141:37: Type 'string | null | undefined' is not assignable to type 'string'.
  • L141:48: 'link.workflowEvent' is possibly 'null' or 'undefined'.
  • L149:6: Type 'string | null | undefined' is not assignable to type 'string'.
  • L149:15: 'link.workflowEvent' is possibly 'null' or 'undefined'.
  • L152:10: 'link.workflowEvent' is possibly 'null' or 'undefined'.
  • L176:19: Parameter 'key' implicitly has an 'any' type.
  • L176:24: Parameter 'value' implicitly has an 'any' type.
  • L205:15: Parameter 'key' implicitly has an 'any' type.
  • L205:20: Parameter 'value' implicitly has an 'any' type.
  • L225:18: Parameter 'key' implicitly has an 'any' type.
  • L225:23: Parameter 'value' implicitly has an 'any' type.
src/lib/components/workflow/pending-activity/pending-activity-card.svelte (8)
  • L174:24: Type 'IPayloads | null | undefined' is not assignable to type 'object | PotentiallyDecodable'.
  • L224:20: Parameter 'timeDifference' implicitly has an 'any' type.
  • L241:8: Argument of type 'number | null | undefined' is not assignable to parameter of type 'number | null'.
  • L246:54: Argument of type 'number | null | undefined' is not assignable to parameter of type 'number'.
  • L31:26: 'activity.attempt' is possibly 'null' or 'undefined'.
  • L95:12: Argument of type 'number | null | undefined' is not assignable to parameter of type 'number'.
  • L97:14: Argument of type 'Duration | null' is not assignable to parameter of type 'string | Duration'.
  • L140:13: 'totalPending' is possibly 'undefined'.
src/lib/components/event/event-summary-row.svelte (5)
  • L134:6: Argument of type 'string | undefined' is not assignable to parameter of type 'string'.
  • L171:23: Parameter 'event' implicitly has an 'any' type.
  • L183:30: Parameter 'group' implicitly has an 'any' type.
  • L183:37: Parameter 'hoveredEventId' implicitly has an 'any' type.
  • L311:16: Type 'ITimestamp | null | undefined' is not assignable to type 'ITimestamp'.
src/lib/components/lines-and-dots/svg/timeline-graph-row.svelte (5)
  • L95:20: 'distance' is possibly 'null'.
  • L95:31: 'workflowDistance' is possibly 'null'.
  • L106:20: 'distance' is possibly 'null'.
  • L106:31: 'workflowDistance' is possibly 'null'.
  • L241:12: 'pendingActivity.attempt' is possibly 'null' or 'undefined'.
src/lib/components/schedule/schedule-form/schedule-input-payload.svelte (1)
  • L75:18: Type 'IPayloads | undefined' is not assignable to type 'object | PotentiallyDecodable'.
src/lib/components/standalone-activities/activity-input-and-outcome.svelte (1)
  • L18:22: Type 'IPayloads | undefined' is not assignable to type 'object | PotentiallyDecodable'.
src/lib/pages/standalone-activity-details.svelte (5)
  • L53:48: Argument of type 'number | undefined' is not assignable to parameter of type 'number | null'.
  • L81:14: Type 'string | undefined' is not assignable to type 'string'.
  • L86:14: Argument of type 'IFailure | undefined' is not assignable to parameter of type 'IFailure'.
  • L106:32: Argument of type 'string | undefined' is not assignable to parameter of type 'string'.
  • L221:30: Type '{ [k: string]: IPayload; } | null | undefined' is not assignable to type 'object | PotentiallyDecodable'.
src/lib/pages/standalone-activity-metadata.svelte (1)
  • L40:23: Argument of type 'IUserMetadata | undefined' is not assignable to parameter of type 'IUserMetadata'.
src/lib/pages/standalone-activity-search-attributes.svelte (1)
  • L10:22: Type 'Record<string, IPayload> | undefined' is not assignable to type 'object | PotentiallyDecodable'.
src/lib/pages/workflow-metadata.svelte (1)
  • L14:24: Type 'Record<string, string> | undefined' is not assignable to type 'object | PotentiallyDecodable'.
src/lib/pages/workflow-search-attributes.svelte (1)
  • L13:22: Type 'Record<string, string> | undefined' is not assignable to type 'object | PotentiallyDecodable'.
temporal/client.ts (1)
  • L38:3: Function lacks ending return statement and return type does not include 'undefined'.

Generated by 🚫 dangerJS against 869b9bc

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.
@rossedfort
rossedfort marked this pull request as ready for review April 21, 2026 15:46
@rossedfort
rossedfort requested a review from a team as a code owner April 21, 2026 15:46
Comment thread src/lib/utilities/export-history.ts Outdated
Comment thread src/lib/components/event/event-details-row.svelte
Comment thread src/lib/components/payload/payload-decoder.svelte Outdated
Comment thread src/lib/components/payload/payload-code-block.svelte Outdated
Comment thread src/lib/utilities/decode-payload.ts
Comment thread src/lib/utilities/decode-payload.ts
Comment thread src/lib/components/payload/IMPROVEMENTS.md Outdated
Comment thread src/lib/components/payload/payload-summary.svelte
Comment thread src/lib/components/payload/payload-decoder.svelte
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.

export const decodePayloadsAndParseDataToJSON = async (
payloads: Payloads | null | undefined,
): Promise<unknown[]> => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • ⚠️ Type 'null' is not assignable to type 'unknown[]'.

export const decodePayloadsAndParseDataToJSON = async (
payloads: Payloads | null | undefined,
): Promise<unknown[]> => {
if (!payloads) return null;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • ⚠️ Argument of type 'IPayload[] | null | undefined' is not assignable to parameter of type 'unknown[]'.

return decoded?.[0] ?? clone;
}

for (const key of Object.keys(clone)) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • ⚠️ Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{ payloads?: IPayload[] | null | undefined; } | { id: string; attributes: EventAttribute; timestamp: string; classification: "Scheduled" | "Unspecified" | ... 12 more ... | "CancelRequested"; ... 70 more ...; eventId: string; } | ... 61 more ... | { ...; }'.

@rossedfort
rossedfort merged commit ef03dfc into main May 14, 2026
23 checks passed
@rossedfort
rossedfort deleted the refactor-payload-component branch May 14, 2026 16:28
@rossedfort
rossedfort restored the refactor-payload-component branch May 14, 2026 17:35
@rossedfort
rossedfort deleted the refactor-payload-component branch May 14, 2026 17:36
tegan-temporal pushed a commit that referenced this pull request May 18, 2026
…load component (#3299)

* 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

* 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
laurakwhit added a commit that referenced this pull request May 21, 2026
Auto-generated version bump from 2.49.1 to 2.50.0

Bump type: minor

Changes included:
- [`29832bec`](29832be) Use initiatedEvent for startChildFailed event grouping (#3342)
- [`91a01560`](91a0156) rm slash (#3343)
- [`2e7b88d0`](2e7b88d) feat: Set Current Version action for worker deployment versions (#3319)
- [`b2685f3b`](b2685f3) Add relative path prefix support to routeFor utilities (#3292)
- [`9c888c0c`](9c888c0) fix: validate connection modal status, retry button, and copy nits (#3347)
- [`38989c48`](38989c4) revert: restore original create worker deployment copy (#3348)
- [`90e1fe58`](90e1fe5) Common Errors for Event History (#3306)
- [`f479e4e2`](f479e4e) Show current duration for pending timeline events (#3346)
- [`65a7ff0d`](65a7ff0) Add refresh button to workers list view (#3349)
- [`219cfee4`](219cfee) Fix null conditionals in search attribute filter (#3351)
- [`08bd2f01`](08bd2f0) Add support for adding caller Namespace even if it's not in list of allowed Namespace options for Nexus endpoint (#3167)
- [`d424a78a`](d424a78) refactor(DT-3906): Add knip (#3350)
- [`e828b14f`](e828b14) Remove icon (#3357)
- [`252a755c`](252a755) Add Java to list of support versions for worker heartbeats (#3362)
- [`0f446c7a`](0f446c7) refactor(DT-3906): Simple Svelte 5 migrations (#3359)
- [`63db5b72`](63db5b7) feat(DT-3657): Support shift click for bulk selection in workflow table (#3344)
- [`389d57bf`](389d57b) Enable Svelte 5 runes on files not using legacy features (#3363)
- [`f4b87e1d`](f4b87e1) Update components/workflow to Svelte 5 syntax (#3361)
- [`3e2cce43`](3e2cce4) refactor(DT-3906): More Svelte 5 Migrations (trivial ones) (#3364)
- [`15adcd73`](15adcd7) refactor(DT-3906): UI svelte 5 migrate components/events (#3365)
- [`031fb867`](031fb86) refactor(DT-3906): Migrate stories to Svelte 5 syntax (#3366)
- [`4eac9d2a`](4eac9d2) Scope group hover to tooltip component only (#3379)
- [`eeace5c3`](eeace5c) chore: upgrade TypeScript to v6.0.3 (#3371)
- [`cb80efdd`](cb80efd) refactor(DT-3906): More trivial migrations (#3367)
- [`6cc395bf`](6cc395b) refactor(DT-3906): migrate holocene primitives + layout components to Svelte 5 runes (#3377)
- [`40a029ea`](40a029e) refactor(DT-3906): UI Svelte 5 Migrations medium (#3368)
- [`7dc29ba6`](7dc29ba) refactor(DT-3906): Delete unused Svelte 4 scaffolding (carved from #3370 - Part 1) (#3372)
- [`34a8547e`](34a8547) refactor(DT-3906): Migrate workflow client-action modals to runes (carved from #3370 - Part 4) (#3375)
- [`61090d7e`](61090d7) refactor(DT-3906): Migrate schedule view components to runes (carved from #3370 - Part 2) (#3373)
- [`988e0479`](988e047) refactor(DT-3906): Migrate filter and input components to runes (carved from #3370 - Part 3) (#3374)
- [`d8b78496`](d8b7849) refactor(DT-3906): Migrate workflows-summary table + relationships to runes (carved from #3370 - Part 5) (#3376)
- [`ff86c004`](ff86c00) fix(DT-3968): Make Workflow Table Tooltips render in portal (#3383)
- [`5e02642a`](5e02642) fix(DT-3967): Visibly toggle the view children button even with a parent has 0 (#3382)
- [`1f2b1031`](1f2b103) Remove capability guard from Set Current Version menu item (#3386)
- [`0d54dcb4`](0d54dcb) fix: portal maximizable to body to escape stacking context (#3385)
- [`abf45438`](abf4543) chore(security): patch Dependabot alerts for axios, protobufjs, fast-uri, uuid, postcss, gomarkdown (#3388)
- [`4500f5ea`](4500f5e) Upgrade GitHub actions (#3389)
- [`4a8c0c5b`](4a8c0c5) fix(timeline): stabilize child workflow timeline width with scrollbar-gutter (#3329)
- [`ead14c61`](ead14c6) Move Create Schedule button to header row (#3390)
- [`e8638a2b`](e8638a2) Add top margin (#3392)
- [`ef03dfce`](ef03dfc) refactor: replace PayloadDecoder and MetadataDecoder with unified Payload component (#3299)
- [`89b9eafa`](89b9eaf) add loading state to payload code block (#3397)
- [`ee3ae138`](ee3ae13) Update Schedules search attributes filter (#3396)
- [`41fd4f1f`](41fd4f1) DT-3751 - download external payloads (#3345)
- [`e4fee0b6`](e4fee0b) Fix codec server request URL (#3400)
- [`966b3d0a`](966b3d0) Make input from schedule result actually a string and update tests (#3403)
- [`48a014db`](48a014d) feat(history): show Nexus operation name in compact view  (#3394)
- [`9127c768`](9127c76) Refactor status counts and refresh button (#3402)
- [`747ec109`](747ec10) Fix double loader button (#3407)
- [`6ae961fc`](6ae961f) Payload rendering and error optimizations (#3401)
- [`32c74a48`](32c74a4) add codec server error banner back to workflow layout (#3408)
- [`f73c7ec9`](f73c7ec) Nxs operation/kt (#3406)
- [`672bb04a`](672bb04) Bump Go 1.26.2→1.26.3, x/net v0.54.0, remove curl from runtime image (#3409)
- [`1ee09fe3`](1ee09fe) chore(deps-dev): bump svelte from 5.55.1 to 5.55.7 (#3395)
- [`565bb071`](565bb07) VLN-1352: remediate missing-dependency-cooldown (#3398)
- [`25794ed8`](25794ed) Bump devalue to 5.8.1 (#3410)
- [`b18d0060`](b18d006) Set color-scheme explicitly (#3358)
- [`e7995b81`](e7995b8) chore: ensure the empty app.html respects the user's ligh/dark settings (#3353)
- [`01d64038`](01d6403) fix: enable external payload download button with namespace level codec endpoint (#3420)
- [`4cc44311`](4cc4431) Fix publicPath URL duplication when prefix appears as substring (#3393)

Co-authored-by: laurakwhit <[email protected]>
edwardzhu0211 added a commit to edwardzhu0211/ui that referenced this pull request Jun 6, 2026
…ity detail

The Standalone Activity detail page rendered failures with a plain CodeBlock
and JSON.stringify, bypassing the codec entirely, in two places:

- the Result panel (activity-input-and-outcome.svelte) for `outcome.failure`
- the Last Failure panel (standalone-activity-details.svelte) for `info.lastFailure`

As a result a failure's `encodedAttributes` payload (where the SDK's failure
converter stores the error message and stack trace when configured with
EncodeCommonAttributes) was shown as raw base64 instead of being decoded — even
when a codec server was configured and the Input/Result decoded correctly.

Render both with PayloadCodeBlock, which routes the object through
decodeEventAttributes and recursively decodes both `payloads` and
`encodedAttributes` via the codec server — matching how the workflow
event-history view already renders failures, and completing the temporalio#3299 refactor
that moved Input/Result to PayloadCodeBlock but missed the failure branch.
Alex-Tideman pushed a commit that referenced this pull request Jun 9, 2026
…ity detail (#3507)

The Standalone Activity detail page rendered failures with a plain CodeBlock
and JSON.stringify, bypassing the codec entirely, in two places:

- the Result panel (activity-input-and-outcome.svelte) for `outcome.failure`
- the Last Failure panel (standalone-activity-details.svelte) for `info.lastFailure`

As a result a failure's `encodedAttributes` payload (where the SDK's failure
converter stores the error message and stack trace when configured with
EncodeCommonAttributes) was shown as raw base64 instead of being decoded — even
when a codec server was configured and the Input/Result decoded correctly.

Render both with PayloadCodeBlock, which routes the object through
decodeEventAttributes and recursively decodes both `payloads` and
`encodedAttributes` via the codec server — matching how the workflow
event-history view already renders failures, and completing the #3299 refactor
that moved Input/Result to PayloadCodeBlock but missed the failure branch.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants