Skip to content

Consolidate duplicated composables across both GUIs into the shared kit #14907

Description

@mrveiss

Part of #14774. Discovery issue — the sweep below is partial; completing it is part of the work.

Problem

Composables are duplicated across the two GUIs with no shared home. Counts:

Package composables
autobot-frontend/src/composables 229
autobot-slm-frontend/src/composables 30
libs/autobot-ui/src/composables 6

The kit exports only useFocusTrap / useFocusRestore / useInitialFocus / useBodyScrollLock / usePagination / useFormValidation. Everything else is per-app.

Confirmed duplicates

Three composables exist under the same filename in both GUIs:

1. useVncControls.ts — byte-identical. 21 lines, 824 bytes, cmp reports no difference. A verbatim copy. Cleanest possible promote-to-kit candidate.

2. useToast.ts — identical public contract, divergent implementations. Both export exactly the same eight symbols:

ToastType · Toast · UseToastReturn · MAX_TOASTS · TOAST_DURATIONS · TOAST_INJECT_KEY · provideToast · useToast

Main is 184 lines, the SLM 93. Same API contract, two implementations — the SLM's reads as a trimmed copy. Two implementations of one contract drift independently, and toast behaviour (max queue, durations, inject key) is exactly the kind of thing that should be identical across both GUIs.

3. usePrometheusMetrics.ts — same name, overlapping types, genuinely different domains. Eight type names are declared in both files:

GPUMetrics · NPUMetrics · ServiceHealth · ServicesSummary · PerformanceAlert · AlertsSummary · OptimizationRecommendation · UsePrometheusMetricsOptions

But the surrounding logic differs legitimately — main carries WorkflowMetrics, GithubMetrics, MultiModalMetrics; the SLM carries NodeMetricsDetailed, FleetMetricsDetailed, NPUFleetMetrics, PerformanceOverview. This one should not be merged wholesale. The shared types are duplicated and belong in one place; the domain logic stays per-app.

Unexamined, flagged

  • Four WebSocket composables across the repo with no shared transport layer: useWebSocket.ts, useGlobalWebSocket.ts, useCanvasWebSocket.ts (main) and useSlmWebSocket.ts (SLM). Not compared — reconnect/backoff/auth handling is the likely overlap and the likely place for divergent bugs.
  • API-client composables — the SLM has useSlmApi, useAutobotApi, useApiKeyApi, useSecretsApi, useSsoApi, useMfaApi, useLlmConfigApi, useSlmUserApi; main has its own set including useSecretsAuditApi, useSecretsInfraApi. Whether they share request/auth/error handling is unchecked.

Scope of this sweep — read before trusting it

This compared exact filename collisions between the two GUIs, plus a targeted concept list. It did not compare 229 composables against 30 behaviourally. Same-concept/different-name duplicates are the likeliest remaining category and are not covered here — a full pass is part of this issue, not a prerequisite for it.

Approach

  1. Promote useVncControls to the kit — byte-identical, no reconciliation needed.
  2. Reconcile useToast into one kit implementation taking the union of both behaviours, not the smaller one. Diff the two bodies first and record what each does that the other does not.
  3. Extract the eight shared usePrometheusMetrics types into one shared location. Leave both composables' domain logic in place — merging them would be a fork collapse of two genuinely different things.
  4. Compare the four WebSocket composables and extract the shared transport concerns if one exists.
  5. Complete the behavioural sweep — same-concept/different-name pairs across all 259 composables. File what it finds rather than folding it in silently.
  6. Add a guard that fails when a composable filename exists in both GUIs without going through the kit.

Risks / constraints

  • Do not resolve a duplicate by keeping the smaller implementation. Every collapse takes the union of both sides' capabilities; the main GUI's useToast being twice the size is a signal it does more, not that it is bloated.
  • usePrometheusMetrics is a trap: the shared name and shared types make it look like one duplicate, but the domain logic is legitimately per-app. Types only.
  • The kit has no tests (Add test coverage to libs/autobot-ui — the shared kit has zero tests #14786). Promoting composables into an untested package widens blast radius — sequence behind or alongside that issue.
  • Promoted composables must not assume either app's stores, router or token values.

Acceptance criteria

  • useVncControls promoted to the kit; both GUIs import it; no local copies remain
  • One useToast in the kit, its behaviour the union of both prior versions, with the diff recorded in the PR
  • The eight shared Prometheus type names declared once; both composables consume them; domain logic left per-app
  • Four WebSocket composables compared, with a written finding either way
  • Full behavioural sweep completed; same-concept/different-name duplicates filed
  • Guard fails on a composable filename present in both GUIs and absent from the kit

Activity

  1. added this to the v0.10.0 milestone on Sep 12, 2026
  2. mrveiss commented on Sep 20, 2026

    @mrveiss
    OwnerAuthor

    Checked the candidate branch for this issue (`lane-gui-dedup`) before reviving it, per standing discipline (never revive without comparing each touched file's blob against current `origin/main` first).

    Substantial progress already landed on main, via a different path than this branch:

    • AC2 (one `useToast` in the kit, union behavior) — done. `libs/autobot-ui/src/composables/useToast.ts` exists; both `autobot-frontend/src/composables/useToast.ts` and `autobot-slm-frontend/src/composables/useToast.ts` are thin re-export shims citing this issue by number, each with a doc comment naming what capability diff the union preserved (Tier-C persistent-error handling from the main GUI's larger implementation, MAX_TOASTS=5 behavior from the SLM's).
    • AC3 (eight shared Prometheus type names, domain logic per-app) — done. Both `usePrometheusMetrics.ts` files import the shared type names from `@autobot/ui`; domain-specific logic (main's `WorkflowMetrics`/`GithubMetrics`/`MultiModalMetrics`, SLM's `NodeMetricsDetailed`/`FleetMetricsDetailed`/`NPUFleetMetrics`) stays local, matching the issue's own explicit instruction not to merge those wholesale.
    • AC6 (guard fails on a composable filename in both GUIs, absent from the kit) — done. `autobot-infrastructure/shared/scripts/hooks/pre-commit-no-cross-gui-duplicate` exists on main with its own test file.

    `lane-gui-dedup` itself should NOT be revived — beyond being superseded on the above, its own residual diff (direct tip-to-tip against current main, not merge-base) carries two real regressions:

    Still genuinely open, verified directly against main, not assumed:

    • AC1 (`useVncControls` promotion) — open, and worse than the issue's own text implies: the composable is triplicated, not duplicated — `autobot-frontend/src/composables/useVncControls.ts`, `autobot-slm-frontend/src/composables/useVncControls.ts`, and `autobot-plugins/vnc/src/composables/useVncControls.ts` all still exist locally, none promoted to the kit. Per the issue's own text this is the cleanest candidate (byte-identical, no reconciliation) — good next scoped piece of work.
    • AC4 (WebSocket composable comparison) and AC5 (full behavioral sweep across all composables) — not verified either way in this pass; flagging as unchecked rather than assuming either status.

    Not closing anything here — leaving the checklist as-is since AC1/AC4/AC5 are unverified-or-open. Recommend treating remaining scope as fresh work off current main rather than reviving `lane-gui-dedup`.

  3. mrveiss commented on Sep 20, 2026

    @mrveiss
    OwnerAuthor

    Follow-up to the earlier comment: AC1 ("`useVncControls` promoted to the kit; both GUIs import it; no local copies remain") named the wrong target package. The promotion happened, correctly, to `autobot-plugins/vnc` (a VNC-specific plugin, not `@autobot/ui`) via #12931/#12653/#12978 — that's the only reason this AC read as unmet for months. The two app-local files were already dead re-export shims with zero callers, not a live duplicate needing reconciliation.

    Cleanup PR: #17180 — removes both shims plus the one regression test whose entire subject was the shim (a relative import a path-substring grep initially missed; a second, unscoped grep across both apps including tests and dynamic-import forms caught it before it could break the build). `docs/adr/008` updated so its file:line citation of the shims doesn't go stale.

    AC1 can be ticked once #17180 lands.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions