port(phase 6): workflows bundle from block/buzz - #799
Merged
Merged
Conversation
(cherry picked from commit 2693e0db1fc4980a551c2492031812dc4bad985f) Signed-off-by: Basheer Phiri <phiribash@gmail.com>
(cherry picked from commit edc4a09aaa41c29e2495a28247c895febaf6587d) Signed-off-by: Basheer Phiri <phiribash@gmail.com>
A subscription is now scoped to a set of channels rather than one optional channel, so `topic_for_subscription` is gone in favour of releasing Global and each channel topic from `removed.scope`. Colony adaptations, all in files where Colony's text differed: - `crates/buzz-db/src/event.rs` is `store/event.rs` here after the Phase 2 split. The `EventQuery::channel_ids` doc takes upstream's wording, which now has to explain `channel_ids_include_global`; the `tag_contains` doc above it is Colony's and stays. - `connection.rs`: upstream's diff and Colony's file disagree around the helper because Colony added `request_rejection_message` and `enforce_ws_admission` next to it. Applied by hand: the per-channel release loop replaces the single `release_topic` call, the helper and its now-unused `EventTopic` import are deleted, and both Colony functions are untouched. - `req.rs`: `apply_access_scope_to_query` becomes upstream's `apply_channel_scope_to_query`, which also takes the filter so an explicit multi-channel `#h` request scopes to exactly those channels. The comment under it keeps Colony's persona example inside upstream's more general wording, since personas are what that pushdown was added for here. Identifier gate against 0d6e6ba reports five removals across five files: `topic_for_subscription` (four copies), `extract_channel_from_filter` and `apply_access_scope_to_query`. All are upstream's own deletions in this commit, and their replacements (`scope.channel_ids()`, `apply_channel_scope_to_query`) are in the tree. Checks: cargo check and clippy -D warnings clean on buzz-relay, buzz-db and buzz-core; 133 buzz-db and 15 buzz-core filter tests pass; 18 desktop src-tauri workflow tests pass; tsc --noEmit clean. buzz-relay's lib suite reports 33 failures on this machine, none of them caused by this commit: they are the NIP-98 replay check finding no local Redis, which CI has. `mint_and_revoke_a_gateway_token` fails with "NIP-98: replay check unavailable", and both it and `owner_archive_rejects_stale_request_after_live_kind0_owner_flip` fail identically in an untouched worktree at e01436b. Signed-off-by: Taylor Ho <taylorkmho@gmail.com> (cherry picked from commit f7a01bda7b1bf95cdbc9dc21bb69970955b14ecc) Signed-off-by: Basheer Phiri <phiribash@gmail.com>
## Summary - retain explicit regression coverage for the exact 128-channel relay request limit - cover the 129-channel split into 128 + 1 filters The workflow-listing implementation originally carried by this PR landed through #6009. This branch is now rebased onto current `main`, so the remaining diff is only the boundary test that #6009 did not include. Fixes #6116 ## Test plan - `cargo test --manifest-path desktop/src-tauri/Cargo.toml workflow_queries_respect_relay_explicit_channel_limit` - pre-push hook: Desktop checks, Desktop tests, Desktop Tauri checks, and path-scoped Rust tests Signed-off-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz> (cherry picked from commit f8692fa9b52ddcfeb4b95fb4862109983509f131) Signed-off-by: Basheer Phiri <phiribash@gmail.com>
**Category:** new-feature **User Impact:** Users can create, edit, duplicate, and deep-link to workflows in a responsive visual editor without losing unsupported YAML or unsaved work. **Problem:** Workflow editing was split across disconnected surfaces and lacked reliable URL state, lifecycle protection, and parity between Form and YAML modes. **Solution:** This adds a route-addressable editor foundation with stable pane identity, guarded dirty exits, lossless Form/YAML transitions, responsive workflow and channel controls, and matching reaction-filter execution support. <details> <summary>File changes</summary> **crates/buzz-workflow/src/lib.rs** Apply reaction trigger filters during workflow execution and cover target-message gating. **crates/buzz-workflow/src/schema.rs** Extend the reaction trigger schema with the editor-owned filter field. **desktop/src/app/navigation/useAppNavigation.ts** Add navigation helpers for explicit workflow create, edit, and duplicate editor modes. **desktop/src/app/routes/WorkflowsRouteScreen.tsx** Coordinate route state with the shared workflow library and editor dialog. **desktop/src/app/routes/lazyWorkflowsRouteScreen.ts** Share one lazy route component across workflow route entry points to avoid loading flashes. **desktop/src/app/routes/workflows.$workflowId.tsx** Parse workflow editor modes and pane deep links for workflow-specific URLs. **desktop/src/app/routes/workflows.tsx** Parse library-level create state and render the shared workflow route screen. **desktop/src/app/AppWorkflowEditorOverlayProvider.tsx** Host the shared workflow editor at the app-shell level so channel-originated workflow dialogs stay above the active channel instead of replacing it. **desktop/src/shared/context/WorkflowEditorOverlayContext.tsx** Expose route-independent open-existing and create-new workflow actions to channel settings. **desktop/src/features/workflows/ui/WorkflowEditorHost.tsx** Share editor loading, unavailable, and dialog lifecycle wiring between canonical workflow routes and the channel overlay. **desktop/src/features/channels/ui/ChannelManagementSheet.tsx** Add an experiment-gated, Canvas-style Workflows ingress below Canvas, including channel-scoped loading, error, empty, and list states plus open/create actions; disabled users issue no workflow query. **desktop/src/features/channels/ui/ChannelWorkflowsSection.tsx** Render the channel workflow list and New workflow action without pushing the existing settings sheet past its file-size ceiling. **desktop/src/features/workflows/ui/ChannelCombobox.tsx** Adopt the final channel presentation, portalled scrolling, and one-shot create-flow opening behavior. **desktop/src/features/workflows/ui/CreateWorkflowDialog.tsx** Remove the superseded create-only dialog in favor of the unified workflow editor. **desktop/src/features/workflows/ui/WorkflowCard.tsx** Open workflow cards in the detail and run-history modal while preserving explicit edit and duplicate actions. **desktop/src/features/workflows/ui/WorkflowDetailDialog.tsx** Present workflow Trigger/Steps in the shared modal chrome, with top-chrome ingress to a responsive right-side run-history inspector and an explicit edit action. **desktop/src/features/workflows/ui/WorkflowDialog.tsx** Unify create, edit, and duplicate lifecycle handling with URL panes, generated-name synchronization, dirty-exit guards, stale-write preservation, and protected webhook-secret handoff. **desktop/src/features/workflows/ui/WorkflowFormBuilder.tsx** Build the responsive Form/YAML editor shell, stable step selection, insertion and removal behavior, and lossless canonical-YAML synchronization. **desktop/src/features/workflows/ui/WorkflowStepCard.tsx** Align workflow step controls and presentation with the final editor interaction model. **desktop/src/features/workflows/ui/WorkflowUnavailableDialog.tsx** Show a non-disclosing loading or unavailable state for missing and inaccessible workflow links, with retry and close actions. **desktop/src/features/workflows/ui/WorkflowWebhookSecretDialog.tsx** Obscure one-time webhook secrets by default and require explicit confirmation before any close or navigation discards them. **desktop/src/features/workflows/ui/WorkflowsScreen.tsx** Connect library state and workflow actions to the route-addressable editor. **desktop/src/features/workflows/ui/WorkflowsView.tsx** Restore the responsive workflow library, create tile, cards, loading states, and shared action menu. **desktop/src/features/workflows/ui/workflowEditorPane.test.mjs** Cover pane parsing, serialization, and stable step-ID reconciliation. **desktop/src/features/workflows/ui/workflowEditorPane.ts** Define explicit trigger and stable step pane URL state. **desktop/src/features/workflows/ui/workflowYamlDocument.ts** Read and update header fields independently of full form validation so incomplete steps cannot clear or disable the workflow title. **desktop/src/features/workflows/ui/workflowYamlDocument.test.mjs** Cover document-level workflow header reads and writes for incomplete definitions. **desktop/tests/e2e/workflow-title-stability.spec.ts** Verify generated, renamed, saved, and duplicated titles remain stable while moving between trigger and step panes. **desktop/src/features/workflows/ui/workflowFormTypes.test.mjs** Cover lossless Form/YAML round trips and actionable fallback for unsupported fields. **desktop/src/features/workflows/ui/workflowFormTypes.ts** Own canonical workflow YAML conversion while preserving supported trigger and step fields. **desktop/src/shared/ui/PortalledScrollArea.tsx** Provide bounded scrolling for popovers rendered outside their dialog container. **desktop/src/shared/ui/popover.tsx** Allow workflow popovers to use the shared portalled scroll container. **desktop/tests/e2e/channels.spec.ts** Cover disabled and enabled Workflows experiment states, including suppressed queries while disabled, placement beneath Canvas, channel workflow listing and opening, channel-preselected workflow creation, and direct return to the channel Workflows panel after close, discard, or cancel. **desktop/tests/e2e/workflows.spec.ts** Exercise library actions, deep links, create/edit/duplicate lifecycle, dirty exits, responsive editor behavior, YAML safety, stale updates, and one-shot channel selection. </details> 1. Open **Workflows** and confirm the responsive card library, create tile, card action menu, and card-to-detail/run-history modal navigation. 2. Open `?view=create`; confirm the channel chooser opens once, the trigger inspector stays hidden until a channel is selected, and closing the chooser does not make it reopen after unrelated edits. 3. Create a workflow, switch between Form and YAML, add and remove steps, refresh a pane deep link, and confirm the selected trigger or stable step remains addressable. 4. Edit or duplicate a workflow, make an unsaved change, and confirm close, Escape, browser navigation, and route target changes require discard confirmation while pane-only navigation does not. 5. Enter unsupported YAML and confirm Form mode gives an actionable fallback without rewriting the definition; verify reaction triggers preserve and execute their filter. 6. Open a channel’s settings, select **Workflows** below **Canvas**, and open or create a workflow; confirm the shared modal stays over the channel, the channel URL does not change, New workflow preselects that channel, and closing or discarding returns directly to the channel’s **Workflows** panel. 7. In create, edit, and duplicate modes, move between the trigger and incomplete step panes and confirm the generated or edited title remains visible and editable. Fresh captures from product head `c5c3abc91a71fe511d43e6cc9168b1626d0c217c`; the later review-guidance fix does not alter these pictured states.     - Corrected Diff Posted condition guidance to use the executor-supported `str_contains(trigger_text, "deploy")` syntax and added a visible Playwright regression assertion. - Scoped direct workflow-detail navigation coverage to the stable `Edit workflow` dialog name, asserted the workflow title separately, and retained the trigger-node assertion. - Routed dirty channel-overlay Duplicate/Edit transitions through the existing discard confirmation, preserving the original YAML draft when the user keeps editing. - Made workflow deletion await relay success, remain non-dismissible while pending, and retain the confirmation/editor/draft with an actionable inline error on rejection. - Gated the channel-settings Workflows ingress and channel workflow query behind the `workflows` experiment, with defensive rendering if the flag changes while that view is active. - Matched the Reaction Added trigger to the prototype reaction picker, preserving native/custom/legacy values in canonical YAML and providing an explicit clear action. - Kept a channel’s Workflows panel mounted beneath channel-origin editors so clean close, dirty discard, and create cancel return directly to that panel without changing the channel URL. Verified at exact pushed head `76ebba7b7ace1e13445744200da70ca9da231b7e`: - Push hooks passed: destination-org policy, branch-skew, differential file-size, Desktop checks, TypeScript typecheck, and **5,115/5,115** Desktop unit tests - Focused channel lifecycle E2E passed: dirty edit discard returns directly to the channel Workflows panel; create cancel does the same while preserving the channel URL and preselected channel - E2E production build, standalone TypeScript typecheck, and Biome checks on both touched files passed - Reaction-picker regression spec remains recorded at `dc28ffa98ec34b4e0656757fb2dd93e60a84674d`: **5/5 passed** (picker interaction, canonical YAML persistence/clear, legacy-value preservation, save/reopen round trip, narrow viewport containment) - Blox existing workflow E2E regression set at `dc28ffa98ec34b4e0656757fb2dd93e60a84674d`: **36/36 passed** - Earlier blocker and review regressions remain recorded at `3760c3d657a525f5af98e8d0f98bdd03999d8e61`: **5,114/5,114** Desktop unit tests and the dirty-overlay/deletion focused checks - Working tree clean; local branch, remote branch, and PR head all match the exact SHA above None found. Closest prior work: #231. --------- Signed-off-by: Taylor Ho <taylorkmho@gmail.com> Co-authored-by: Codex <noreply@openai.com> Colony adaptations: - `AppShell` wraps the profile-panel subtree in `AppWorkflowEditorOverlayProvider`. Upstream's diff also re-indents the whole block, and Colony's shell has a CommunityRail and SidebarProvider upstream does not, so the wrapper was placed by hand and nothing else in that file moved. - `commitNavigation` gains `state?: Record<string, unknown>`, matching upstream's own signature. The editor marks navigations it opened so closing it knows whether there is an origin to return to. - `ChannelManagementSheet` is on the size ratchet and this commit pushes it to 1009 lines. Its workflow wiring moves to `useChannelSheetWorkflows`, taking the file to 980. Behaviour is upstream's unchanged; the limit is untouched. - Colony's sidebar keeps Workflows inside the More group, so the three new specs reach it through `openSidebarDestination` rather than clicking `open-workflows-view` directly. Without that they time out on a nav item that is not on screen yet. - Playwright's smoke list keeps Colony's `workflow-run-recovery.spec.ts` alongside upstream's three new specs. One deliberate divergence, and the reason: Upstream comments out the Run history entry point inside the editor, pending a NIP-11 capability gate, and two of its specs pin the button's absence. Colony already ships run history and the Run-again affordance (the authoritative reads landed with #5780), so leaving the popover with no trigger would delete a working Colony feature rather than defer an unbuilt one. The trigger stays, and the two upstream assertions now expect it. Colony's own recovery spec moves to the new surface: opening a card opens the editor, and run history is behind that button. Identifier gate against 6557114 reports fourteen removals across eight files. Every one is upstream's own deletion in this commit, confirmed against its diff. Checks: 156 buzz-workflow tests pass and clippy -D warnings is clean; tsc --noEmit clean; biome zero errors over src and tests; the file-size ratchet passes. E2E on PLAYWRIGHT_PORT=4193: 136 specs across workflows, channels, workflow-run-recovery, workflow-local-controls, workflow-reaction-picker and sidebar-more-nav, plus 20 across workflow-title-stability and navigation. The editor route itself renders under E2E, not only in unit tests. (cherry picked from commit 1934e83bf5a5d8cd00f0cf28b558547b8d0dffb0) Signed-off-by: Basheer Phiri <phiribash@gmail.com>
The `send_message` workflow action could only post new top-level channel
messages. This adds a `reply_in_thread` option so a `message_posted`
workflow can reply in the triggering message's thread, plus a
`trigger_is_reply` filter variable so a workflow can fire only on
top-level messages.
Every NIP-10 thread-marker reader in the tree now routes through one
shared parser and one collapse rule in `buzz-core`, deleting four
hand-rolled copies that had drifted on id-validity and marker semantics.
- **Schema** (`crates/buzz-workflow/src/schema.rs`): new
`reply_in_thread: bool` (serde default `false`) on
`ActionDef::SendMessage`. `validate()` rejects `reply_in_thread: true`
on `schedule`/`webhook` triggers — they carry no message to reply to.
- **Executor** (`crates/buzz-workflow/src/executor.rs`): threads
`reply_to: Option<&str>` (the trigger `message_id`) through
`ActionSink::send_message` when `reply_in_thread` is set; errors clearly
if the trigger has no `message_id`. `resolve_step_templates` carries the
new field. Adds `trigger_is_reply` (boolean) to the eval context.
- **Relay sink** (`crates/buzz-relay/src/workflow_sink.rs`): when
`reply_to` is set, resolves parent/root/depth from the known trigger
event, persists real thread metadata instead of the hardcoded top-level
`depth: 0`, and pushes the live kind:39005 thread-summary overlay after
insert so subscribed desktops update the root's reply badge without
refetching — matching the human ingest path. Emits the same NIP-10
`e`-tag shape as `buzz_sdk::builders::thread_tags`: a single `["e", id,
"", "reply"]` tag for a direct reply (parent == root), and the `root` +
`reply` pair only when nested — so every writer produces one wire shape
per reply kind.
- **Ingest resolver** (`crates/buzz-relay/src/handlers/ingest.rs`): new
`resolve_relay_reply_thread_meta` + `ReplyAncestry`. Unlike the
client-facing `resolve_nip10_thread_meta` (which validates
client-supplied tags), this computes root/depth from the known parent
and enforces the same same-channel and depth-limit invariants. When the
parent has no `thread_metadata` row, both resolvers share
`derive_ancestry_from_parent_tags`, which applies
`ThreadMarkers::resolve()`: a marked nested parent remains depth 2,
while a root-only, malformed, or unmarked parent is top-level and starts
its reply thread at itself. Both the client resolver and the
parent-ancestor fallback now read markers via the shared parser instead
of hand-scanning `e`-tags.
- **Shared NIP-10 parser** (`crates/buzz-core/src/nip10.rs`):
`parse_thread_markers` reads an event's `root`/`reply` markers, honoring
a marker only when its event id is exactly 64 ASCII-hex characters.
`ThreadMarkers::resolve()` is the single definition of the (root, reply)
→ (root_id, parent_id) collapse: `root`+`reply` as-is, `reply`-only →
`(reply, reply)`, a lone `root` or neither → top-level. A slice-based
`parse_thread_markers_from_parts` entry point serves consumers holding
raw JSON tag arrays. Relay ingest, the workflow `trigger_is_reply`
predicate, ACP anchoring, and the CLI reply resolver all call these, so
they cannot drift on marker, id-validity, or collapse semantics.
- **ACP** (`crates/buzz-acp/src/queue.rs`): `parse_thread_tags`
delegates marker parsing and collapse to `buzz_core::nip10`, keeping
only its local `p`-tag mention collection. This fixes a parity gap with
ingest: a malformed non-64-hex marker id is no longer counted as a
thread link, and a lone `root` marker is now top-level rather than being
treated as root == parent.
- **CLI** (`crates/buzz-cli/src/commands/messages.rs`):
`find_root_from_tags` routes its JSON tags through the shared slice
parser and collapse rule. CLI replies to root-only, malformed, or
unmarked parents now correctly start at that immediate parent;
reply-only and root+reply parent ancestry remain intact.
- **Trigger context** (`crates/buzz-workflow/src/lib.rs`):
`build_trigger_context` derives `is_reply` solely from a valid NIP-10
`reply` marker (no DB hit). A lone `root` marker is top-level to ingest,
so it does not count; neither does a `reply` marker whose event id is
malformed, nor a bare/unmarked `e`-tag (mentions, quotes).
- **Desktop** (`workflowFormTypes.ts`, `WorkflowStepCard.tsx`):
`replyInThread` form field with YAML round-trip, and a "Reply to
triggering message in thread" checkbox shown only for message-based
triggers. Switching to `schedule` or `webhook` clears the option on
every step before serialization, so a value hidden behind an action
change cannot resurrect when the action is switched back. The form
parser also requires `reply_in_thread` to be a boolean and refuses
`reply_in_thread: true` under an ineligible trigger, falling back to
YAML mode rather than silently normalizing a backend-invalid definition.
```yaml
trigger:
on: message_posted
filter: trigger_is_reply == false
steps:
- id: auto_reply
action: send_message
text: "pre-written reply text"
reply_in_thread: true
```
---------
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Co-authored-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
Colony adaptations:
- `workflow_sink.rs` here already carries Phase 3's owner-contact hierarchy and
the workflow-mention provenance from #6953, so it is the base. Upstream's
thread-ancestry block is inserted into it, and upstream's shorter
"Resolve `@Name` mentions" comment is dropped in favour of Colony's fuller
one that documents Option C routing.
- `send_message` now carries BOTH Colony's `authored_text` and upstream's
`reply_to`. Colony's five existing sink tests pass `None` for the new
parameter; upstream's five new ones pass their own literal as
`authored_text`.
- Upstream's new PG-gated tests were placed in the `write_path_guard` module,
which has neither `test_state` nor `RelayActionSink` in scope. They move to
`integration_tests`, which owns both.
- `ingest.rs` keeps Colony's event-write fence: the shared
`derive_ancestry_from_parent_tags` helper and the new
`resolve_relay_reply_thread_meta` read through
`get_event_by_id_for_event_write`, not `get_event_by_id`, so the workflow
path sees the same snapshot the ingest path does.
One deliberate divergence, and the reason:
`ThreadMarkers::resolve()` reads a lone `root` marker with no `reply` as
top-level. That is right for relay ingest and for the CLI, and both use it
unchanged. On the ACP side it is not: a client that writes into an existing
thread with only a `root` marker would get a NEW session rather than the
thread's own, which is the behaviour
`scope::tests::direct_reply_to_root_scopes_to_that_root` exists to prevent.
`queue::parse_thread_tags` therefore anchors that case to the named root, and
upstream's new `test_parse_thread_tags_root_only_is_top_level` becomes
`..._anchors_to_that_root` with the reason written out. Threads are the unit of
work here, so an ACP session must not fork away from one.
Identifier gate against 41938a8 reports three removals, all upstream's own:
`valid_event_id` folded into `buzz_core::nip10`, and two renamed tests.
Checks: 1135 buzz-acp, 169 buzz-workflow, 759 buzz-cli, 854 buzz-core and 22
workflow_sink tests pass; clippy -D warnings clean across all five crates;
tsc --noEmit clean and 61 desktop workflow specs pass. The 33 buzz-relay lib
failures are the same local-Redis NIP-98 replay check as before.
(cherry picked from commit c6e3fe7dfa751096d8c4083aaf15d6f6af296572)
Signed-off-by: Basheer Phiri <phiribash@gmail.com>
#5780's relay reader and #6248's routes and overlay providers all change what the inventory records, and CI's check fails on a stale file. Signed-off-by: Basheer Phiri <phiribash@gmail.com>
nocodeafrica
force-pushed
the
port/phase6-workflows
branch
from
September 14, 2026 09:55
d9af98e to
823f7d1
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Phase 6 workflows bundle from block/buzz
Six upstream commits in upstream's first-parent order (#6470 and #6712 split out, see below), hand-merged with Colony's files as the base, identifier-gated (only upstream's own removals) and trailer-verified with
check-trailers.sh.What is in it
0075_workflow_run_error_codeswith itsschema/schema.sqlmirror; the Db facade hunk lives instore/workflow.rs; the regression sits inruntime/migration.rs(looked up by version).parse_expected_workflow_revision;update_workflownow requires the revision.removed.scope;topic_for_subscriptiongone.AppShellwrapped by hand around Colony's community rail and sidebar providers (now composed inAppShellProvidersfor the size ratchet); the three new specs open Workflows through Colony's More group. Divergence kept on purpose: Colony's shipped Run history and Run-again stay inside the editor where upstream commented the entry out pending a capability gate; the two assertions pinning its absence were flipped.send_message: Colony'sworkflow_sink.rsis the base, carryingauthored_textandreply_to; a lone NIP-10rootmarker anchors to that thread inqueue::parse_thread_tagsso an ACP session is not forked away from the thread.Split out
workflows.spec.tswhen more than one workflow exists: the activation confirmation, anAlertDialoginside #6248's modal editor, dismisses itself right after opening. Three diagnostic rounds (focus-outside prevention, deferred open) did not settle it; next lead is RadixDismissableLayer. #6712 (trigger filter discovery) is welded to #6470's files. Both live onport/phase6-workflows-6470for a dedicated fix.Also:
ActionCenterWorkflowDetailno longer renders Approve and Deny buttons fromapproval.token(the relay stores a hash, so they could never work); it carries the same line as the approval card, which is #5780's own change.Report:
epics/42f611d5-ba62-464d-b797-5a3ebb0ba700/artifacts/upstream-port-2026-09/phase-6-workflows/bundle-report-2026-09-14/index.md.Commits
64c3ada71Make workflow run history authoritative in Desktop (#5780)8379a23dffeat(workflows): add responsive library card actions (#6008)66e4b35d4fix(workflows): preserve multi-channel listing semantics (#6009)940655b91test(desktop): cover exact workflow batch limit (#6168)a5b095d42feat(workflows): add workflow editor (#6248)c84092c56feat(workflows): reply in-thread from send_message action (#6178)823f7d1dfchore(desktop): regenerate the native inventory for the workflow bundle🤖 Generated with Claude Code
https://claude.ai/code/session_01FD8THgkKDssuVYgumfTHJM