Skip to content

refactor: decouple component ui from preview, compositions and command-bar - #10771

Merged
davidfirst merged 8 commits into
masterfrom
refactor/phase3-ui-runtime-decouple
Oct 9, 2026
Merged

davidfirst merged 8 commits into
masterfrom
refactor/phase3-ui-runtime-decouple

Conversation

@davidfirst

Copy link
Copy Markdown
Member

Phase 3 of the circular-deps cleanup. Breaks the UI-runtime loops that tie component to the rest of the big cycle group: 55 → 48 components in cycles, 271 → 168 edges.

  • pubsub ↔ ui: ui's UI runtime registers pubsub's React context itself (as it already does for graphql); pubsub's UI runtime no longer depends on ui.
  • command-bar ↔ component: command-bar now registers into component's existing slots (key bindings, component searcher, the menu button) and hands it a command runner for the menu items. component no longer knows command-bar. The commandBar config of component still controls the registration.
  • component → preview: ClickInsideAnIframeEvent moves to pubsub, which, as the iframe bridge, re-dispatches it in the host as a mousedown (previously done by the component UI). ComponentPreviewSize moves to the component model, like RegularCompDescriptor.
  • component → compositions: Composition moves to the compositions/model/composition-type leaf.

Old exports stay in place (@teambit/preview, @teambit/compositions).

@qodo-free-for-open-source-projects

Copy link
Copy Markdown

PR Summary by Qodo

Decouple component UI from command bar, preview, and compositions

✨ Enhancement 🐞 Bug fix 🕐 20-40 Minutes

Grey Divider

AI Description

• Reverse UI-runtime dependencies to break cycles between component, command bar, pubsub, and UI.
• Move shared event, composition, and preview-size definitions to lower-level owners while
 preserving exports.
• Reduce the circular-dependency baseline from 55 to 48 components and 271 to 168 edges.
Diagram

graph TD
  CommandBar["Command Bar UI"] -->|registers commands| ComponentUI["Component UI"] --> ComponentModel["Component Model"] --> CompositionLeaf["Composition Type"]
  Preview["Preview"] -->|publishes iframe events| Pubsub["Pubsub UI"]
  UI["UI Runtime"] -->|registers context| Pubsub
  Preview -->|uses size type| ComponentModel
Loading
High-Level Assessment

Keep the registration direction used here: command bar and UI already consume the capabilities supplied by component and pubsub, so moving their integration there removes reverse dependencies without introducing a new coordination layer. Retained exports limit disruption to existing consumers.

Files changed (16) +222 / -279

Refactor (15) +210 / -160
component.ui.runtime.tsxExpose command-bar integration points from component UI +40/-49

Expose command-bar integration points from component UI

• Removes direct command-bar, preview, and pubsub runtime dependencies. Exposes commands and search, accepts a command runner for menu actions, and passes through the command-bar configuration flag.

scopes/component/component/component.ui.runtime.tsx

index.tsExport preview-size type from component +6/-1

Export preview-size type from component

• Makes ComponentPreviewSize available through the component package.

scopes/component/component/index.ts

component-model.tsMove preview-size type into the component model +23/-3

Move preview-size type into the component model

• Defines preview-size types locally and imports Composition from its lower-level type package instead of the compositions aspect.

scopes/component/component/ui/component-model/component-model.ts

index.tsRe-export component-model preview-size type +7/-1

Re-export component-model preview-size type

• Adds ComponentPreviewSize to the component-model barrel exports.

scopes/component/component/ui/component-model/index.ts

component-searcher.tsxRemove command-bar interface dependency from searcher +1/-2

Remove command-bar interface dependency from searcher

• Retains the FuzzySearcher implementation while dropping the direct SearchProvider interface import and declaration.

scopes/component/component/ui/component-searcher/component-searcher.tsx

composition.tsPreserve composition exports through a compatibility shim +6/-60

Preserve composition exports through a compatibility shim

• Re-exports Composition and its types from the new lower-level package instead of defining them here.

scopes/compositions/compositions/composition.ts

composition.tsDefine Composition in the lower-level model package +60/-0

Define Composition in the lower-level model package

• Moves the existing Composition class and metadata types unchanged into the composition-type leaf.

scopes/compositions/model/composition-type/composition.ts

index.tsPublish Composition from the composition-type package +2/-0

Publish Composition from the composition-type package

• Exports the moved class and metadata types for direct use by lower-level consumers.

scopes/compositions/model/composition-type/index.ts

command-bar.ui.runtime.tsxRegister component integrations from command bar +16/-2

Register component integrations from command bar

• Depends on ComponentAspect and supplies a command runner to component UI. Registers component commands and search when enabled, plus the component menu button.

scopes/explorer/command-bar/command-bar.ui.runtime.tsx

click-inside-an-iframe.tsOwn the iframe-click event in pubsub +17/-0

Own the iframe-click event in pubsub

• Moves the existing ClickInsideAnIframeEvent definition into the iframe-bridge package.

scopes/harmony/pubsub/click-inside-an-iframe.ts

index.tsExport the iframe-click event from pubsub +1/-0

Export the iframe-click event from pubsub

• Adds ClickInsideAnIframeEvent to pubsub's public exports.

scopes/harmony/pubsub/index.ts

pubsub.ui.runtime.tsHandle iframe clicks in the pubsub bridge +16/-12

Handle iframe clicks in the pubsub bridge

• Redispatches iframe-originated click events as host mousedown events after publishing them locally. Removes pubsub's UI runtime dependency and render-hook registration.

scopes/harmony/pubsub/pubsub.ui.runtime.ts

click-inside-an-iframe.tsRetain preview's iframe-click event export +1/-17

Retain preview's iframe-click event export

• Re-exports the event from pubsub so existing preview imports remain available.

scopes/preview/preview/events/click-inside-an-iframe.ts

preview.main.runtime.tsConsume preview-size type from component +9/-11

Consume preview-size type from component

• Imports ComponentPreviewSize from component and re-exports it to preserve preview's public type API.

scopes/preview/preview/preview.main.runtime.ts

ui.ui.runtime.tsxRegister pubsub context from the UI runtime +5/-2

Register pubsub context from the UI runtime

• Adds PubsubAspect as a dependency and installs pubsub's React context as a render hook, reversing the former registration dependency.

scopes/ui-foundation/ui/ui.ui.runtime.tsx

Other (1) +12 / -119
cycles-baseline.jsonRecord reduced circular-dependency baseline +12/-119

Record reduced circular-dependency baseline

• Updates cycle groups and edges after the dependency reversals, reducing cycle membership from 55 to 48 components and edges from 271 to 168.

scripts/circular-deps-check/cycles-baseline.json

@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Oct 6, 2026 •

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (2) 📘 Rule violations (0) 📎 Requirement gaps (0) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)

Grey Divider


Action required

1. Opening the command bar and command search stop working ✓ Resolved
Description
In CommandBarUI.provider, addCommand(...componentUI.keyBindings) and
addSearcher(componentUI.componentSearcher) now run inside the command-bar provider, so they
register into the command and searcher slots under the command-bar's own aspect id.
SlotRegistry.register stores one value per registering extension id, so these calls overwrite the
provider's earlier command-bar.open command and its commandSearcher. Whenever commandBar is
enabled (the default), run('command-bar.open') returns undefined. That breaks the bound mod+k
shortcut and every run('command-bar.open') caller in the component, workspace and scope UIs. The
'>' command search is also replaced by the component searcher.
Code

scopes/explorer/command-bar/command-bar.ui.runtime.tsx[R223-226]

+      if (componentUI.isCommandBarEnabled) {
+        commandBar.addCommand(...componentUI.keyBindings);
+        commandBar.addSearcher(componentUI.componentSearcher);
+      }
Evidence
Earlier in the same provider, addSearcher(commandBar.commandSearcher) and `addCommand({id:
commandBarCommands.open, ...}) call searcherSlot.register and commandSlot.register`, both under
the command-bar's extension id. The repo's own notes on harmony say that SlotRegistry.register
sets the value by the registering extension id, so a later registration replaces the earlier one.
Before this PR, the component keybindings and searcher were registered from the component provider,
under the component id, so nothing was overwritten. Now the second addCommand and addSearcher
calls replace the first ones. getCommand('command-bar.open') then finds nothing, even though the
mousetrap binding for the open shortcut is still in place and calls run.

scopes/explorer/command-bar/command-bar.ui.runtime.tsx[62-91]
scopes/explorer/command-bar/command-bar.ui.runtime.tsx[103-131]
scopes/explorer/command-bar/command-bar.ui.runtime.tsx[201-207]
scopes/harmony/application/select-apps-plugin-defs.ts[14-19]
scopes/workspace/workspace/workspace.ui.runtime.tsx[176-176]
scopes/scope/scope/scope.ui.runtime.tsx[374-374]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Inside `CommandBarUI.provider`, slot registrations are keyed by the registering aspect id, which is command-bar's. The second `addCommand`/`addSearcher` calls (for the component keybindings and component searcher) overwrite the earlier registrations of the `command-bar.open` command and `commandSearcher`. As a result, mod+k, `run('command-bar.open')` and '>' command search stop working.
## Fix Focus Areas
- scopes/explorer/command-bar/command-bar.ui.runtime.tsx[201-231]
## Recommended Fix
Gather everything first, then call `addSearcher` once and `addCommand` once. Pseudocode:

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

2. Two component menu actions do nothing 🐞 Bug ≡ Correctness
Description
ComponentUI.runCommand silently returns when no command runner has been registered, but the
component menu still includes “Open command bar” and “Toggle component list”. This occurs when the
component UI loads without the command-bar aspect, which is now possible because that aspect is no
longer a dependency; both actions remain visible in the main dropdown.
Code

scopes/component/component/component.ui.runtime.tsx[310]

+  static dependencies = [ReactRouterAspect];
Evidence
The component UI no longer requires the command-bar aspect, and only the command-bar provider
registers its runner. The component provider still registers the menu unconditionally, and the menu
renders those items in the main dropdown.

scopes/component/component/component.ui.runtime.tsx[129-171]
scopes/component/component/component.ui.runtime.tsx[310-310]
scopes/component/component/component.ui.runtime.tsx[370-378]
scopes/explorer/command-bar/command-bar.ui.runtime.tsx[221-222]
scopes/component/component/ui/menu/menu.tsx[141-158]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The component UI can now load without the command bar, but its menu continues to display two actions that require a command runner and silently do nothing without one.
## Fix Focus Areas
- scopes/component/component/component.ui.runtime.tsx[129-185]
- scopes/component/component/component.ui.runtime.tsx[370-378]
## Recommended Fix
Only offer command-bar-dependent menu actions when a command runner is available, while keeping the independent copy actions available.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Isolated composition builds miss imports 🐞 Bug ☼ Reliability
Description
The new Composition implementation imports @teambit/compositions.model.composition-id, but the
composition-type component's lockfile importer does not declare it; the component model and
compatibility wrapper likewise import composition-type without declaring it. An isolated install
using the current lockfile therefore has no direct dependency links for these new imports, so
compilation or loading those components can fail.
Code

scopes/compositions/model/composition-type/composition.ts[1]

+import { humanizeCompositionId } from '@teambit/compositions.model.composition-id';
Evidence
The new model calls the imported helper, while its lockfile importer lists no composition-id
dependency. Both new consumers import composition-type, which is likewise absent from their
respective importer dependency lists.

scopes/compositions/model/composition-type/composition.ts[1-1]
scopes/compositions/model/composition-type/composition.ts[36-37]
scopes/compositions/compositions/composition.ts[1-6]
scopes/component/component/ui/component-model/component-model.ts[1-2]
pnpm-lock.yaml[17420-17457]
pnpm-lock.yaml[17238-17367]
pnpm-lock.yaml[15177-15251]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The relocated composition implementation and its consumers introduce package imports absent from their component dependency declarations in the current lockfile. Isolated installs may not resolve them.
## Fix Focus Areas
- scopes/compositions/model/composition-type/composition.ts[1-1]
- scopes/compositions/compositions/composition.ts[1-6]
- scopes/component/component/ui/component-model/component-model.ts[1-2]
- pnpm-lock.yaml[17420-17457]
## Recommended Fix
Update the Bit component dependencies for composition-type, compositions, and component, then regenerate and commit the workspace lockfile so each new import has a direct dependency link.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Tip of the day
💡 Did you know, you can show, collapse, or hide each part of a finding: code, evidence, and all

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment thread scopes/explorer/command-bar/command-bar.ui.runtime.tsx Outdated
Comment thread scopes/compositions/model/composition-type/composition.ts
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit e7d6aea

Comment thread scopes/component/component/component.ui.runtime.tsx
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit fed923b

@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 641ce99

@davidfirst
davidfirst enabled auto-merge (squash) October 9, 2026 14:16
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 86b9e93

@davidfirst
davidfirst merged commit a9816f4 into master Oct 9, 2026
14 checks passed
@davidfirst
davidfirst deleted the refactor/phase3-ui-runtime-decouple branch October 9, 2026 16:42
zkochan added a commit to zkochan/bit that referenced this pull request Oct 9, 2026
12.11.2 ships pnpm/pnpm#16797: an install with enableModulesDir: false
also fetches the packages it reuses from an existing lockfile, not only
the ones it resolves.

The lockfile also records command-bar's chai dependency, which teambit#10771
added without updating it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.

2 participants