Skip to content

[deep-review][duplication] D6: DeepLinkActions and AppCapability are the same delegate-per-verb pattern open-coded twice #369

Description

@codemonkeychris

Summary

Two places in the codebase implement the same "delegate-per-verb registry" pattern with no shared abstraction:

  1. DeepLinkActions — a struct in src/OpenClaw.Tray.WinUI/Services/DeepLinkHandler.cs:269-301 with 28 delegate fields (OpenSettings, OpenChat, RunHealthCheck, CopyDebugBundle, ...). Wired in App.xaml.cs:4286-4324 with a ~200-line new DeepLinkActions { ... } initializer block. Dispatched via switch in DeepLinkHandler.Handle (DeepLinkHandler.cs:54-266).

  2. AppCapability — at src/OpenClaw.Shared/Capabilities/AppCapability.cs, with 10 public mutable Func<...>? fields. Wired in App.xaml.cs:WireAppCapabilityHandlers with a similar bulk assignment. Dispatched via 13-arm switch inside AppCapability.ExecuteAsync.

Why it matters

Three problems:

  1. Open-coded ten ten times — adding a new deep-link verb is: add a field to DeepLinkActions, add a case to the switch in DeepLinkHandler, add a => ... in the wiring block in App.xaml.cs. Three sites for one verb. Same for AppCapability.
  2. Public mutable fieldsAppCapability's ten public Func<...>? fields can be reassigned anywhere in the AppDomain. Testable but not safe.
  3. No discoverability for callers — the deep-link dispatch table lives in DeepLinkHandler.Handle, but the contract (what each action does) lives in App.xaml.cs. Splitting the action from its implementation makes the surface harder to read.

Proposed fix

A small IDispatchRegistry<TVerb, TArgs, TResult> abstraction:

public interface IDispatchRegistry<TArgs, TResult>
{
    void Register(string verb, Func<TArgs, TResult> handler);
    TResult Dispatch(string verb, TArgs args);
    bool IsRegistered(string verb);
}

Migrate DeepLinkActions and AppCapability to use it. Side benefit: the dispatch site becomes table-driven, so adding a verb is one Register call instead of three edits.

For AppCapability: this also addresses the public-mutable-field smell. Handlers are registered through Register, not assigned to fields.

Lower-priority alternative: introduce a [DeepLinkAction("settings")] attribute and source-generate the dispatch table.

Severity

Low — code-shape smell, not a bug. Becomes Medium next time someone adds a deep-link verb.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Fields

    Priority

    None yet

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions