Skip to content

Conversation

@luarmr
Copy link
Contributor

@luarmr luarmr commented Nov 5, 2025

Completes the BEM migration for Label Studio application by replacing all legacy <Block/> and <Elem/> components with the modern cn() helper function.

This is the final phase of the organization-wide BEM modernization:

  • ✅ Editor library (libs/editor/) - 70+ files
  • ✅ DataManager library (libs/datamanager/) - 25 files
  • Label Studio app (apps/labelstudio/) - 24 files ← This PR

Changes

Files migrated: 24
Files skipped: 0
Breaking changes: 0
CSS impact: None (same class strings generated)

Migrated Components

Components (13 files):

  • EmptyState, Error (ErrorWrapper), InlineError
  • HeidiTip, VersionNotifier
  • Counter, Form (Form.Indicator), Pagination
  • DropdownComponent

Pages (11 files):

  • DataManager
  • ImportModal, Config
  • ModelsPage, EmptyList
  • PeoplePage, PeopleList, SelectedUser
  • Projects, ProjectsList, EmptyProjectsList
  • AnnotationSettings, GeneralSettings, StorageForm
  • MachineLearningList, PredictionsList

Cleanup

Removed unused exports from apps/labelstudio/src/utils/bem.tsx:

  • ❌ Removed: Block, Elem, BemWithSpecificContext, BlockContext, useBEM, types
  • ✅ Kept: cn (only export needed)

Migration Details

All transformations follow the standard pattern:

Before:

<Block name="component" mod={{ variant }} mix={className}>
  <Elem name="item" mod={{ active }}>Content</Elem>
</Block>

After:

<div className={cn("component").mod({ variant }).mix(className).toClassName()}>
  <div className={cn("component").elem("item").mod({ active }).toClassName()}>Content</div>
</div>

Key Implementation Details

  • Nested Block contexts: Properly handled (e.g., settings-wrapper creates new context for child Elems)
  • Dynamic tags: Converted component tags (NavLink, Button) to use className prop
  • Semantic HTML: Maintained where used (img, ul, li, p, h1, etc.)
  • Refs and handlers: All preserved
  • TypeScript: No as any in .jsx files

Preserved

  • ✅ All BEM class name generation (same CSS classes)
  • ✅ All modifiers and mixins
  • ✅ All refs, handlers, and props
  • ✅ All component behavior
  • ✅ TypeScript types where applicable
  • ✅ Semantic HTML elements

Statistics

  • Commits: 27 total (24 migrations + 1 cleanup + 1 biome fix + 1 initial)
  • Lines changed: 295 insertions, 293 deletions
  • Files modified: 25
  • Migration rate: 100% (0 files skipped)

Notes

  • Each file migrated in separate commit for granular review
  • No CSS file changes required
  • bem.tsx now only exports cn - LSE should import Block/Elem from @humansignal/core if needed
  • Biome linter passes with no errors

luarmr added 17 commits November 5, 2025 15:45
Migrated EmptyState component from Block/Elem to cn() helper.
- Replaced Block/Elem imports with cn import
- Converted Block name="empty-state-default" to cn("empty-state-default")
- Converted all Elem components (icon, title, description, action, footer) to div with cn().elem()
- Preserved all props and conditional rendering
- No behavior change, equivalent class strings

Part of Label Studio BEM migration (Phase 1: Leaf components)
Migrated ErrorWrapper component from Block/Elem to cn() helper.
- Replaced Block/Elem imports with cn import
- Converted Block name="error-message" to cn("error-message")
- Converted all Elem components with various tags (img, div, ul, li, Button)
- Preserved all props, handlers, and conditional rendering
- Maintained semantic HTML (img, ul, li tags)
- No behavior change, equivalent class strings

Part of Label Studio BEM migration (Phase 1: Leaf components)
Migrated HeidiTip and HeidiLink components from Block/Elem to cn() helper.
- Replaced Block/Elem imports with cn import
- Converted Block name="heidy-tip" to cn("heidy-tip")
- Converted all Elem components (content, header, title, text, heidi, link)
- Maintained semantic HTML (a tag for link)
- Preserved all props, handlers, and conditional rendering
- No behavior change, equivalent class strings

Part of Label Studio BEM migration (Phase 1: Leaf components)
Migrated Counter and CounterButton components from Block/Elem to cn() helper.
- Replaced Block/Elem imports with cn import
- Converted Block name="counter" with mod and mix to cn("counter").mod().mix()
- Converted Elem name="input" (input tag) to input with cn().elem()
- Converted Elem name="btn" (a tag) in CounterButton to a with cn().elem().mod()
- Preserved all props, refs, handlers, and state management
- No behavior change, equivalent class strings

Part of Label Studio BEM migration (Phase 1: Leaf components)
Migrated VersionNotifier component from Block/Elem to cn() helper.
- Replaced Block/Elem imports with cn import
- Converted Block tag="li" name="version-notifier" to li with cn("version-notifier")
- Converted Block tag={Link} name="current-version" to Link with cn("current-version")
- Converted all Elem components (icon, content, title, description) to div with cn().elem()
- Preserved all props, handlers, and conditional rendering
- No behavior change, equivalent class strings

Part of Label Studio BEM migration (Phase 1: Leaf components)
Migrated SelectedUser and UserProjectsLinks components from Block/Elem to cn() helper.
- Replaced Block/Elem imports with cn import
- Converted Block name="user-info" to cn("user-info")
- Converted all Elem components (header, info-wrapper, full-name, email, section, section-title, links-list, project-link, last-active)
- Converted Elem tag={NavLink} to NavLink with className in UserProjectsLinks
- Maintained semantic HTML (p tags) where appropriate
- Preserved all props, handlers, and conditional rendering
- No behavior change, equivalent class strings

Part of Label Studio BEM migration (Phase 1: Leaf components)
Migrated EmptyList component from Block/Elem to cn() helper.
- Replaced Block/Elem imports with cn import
- Converted Block name="empty-models-list" to cn("empty-models-list")
- Converted all Elem components (content, heidy, title, caption) to div with cn().elem()
- Preserved all props and structure
- No behavior change, equivalent class strings

Part of Label Studio BEM migration (Phase 1: Leaf components)
Completed migration of MachineLearningList from Block/Elem to cn() helper.
- Removed Block import (file was already partially using cn())
- Converted Block name="backend-card" to div with rootClass.toClassName()
- File already used cn() for all Elem components via rootClass pattern
- Preserved all props, handlers, and structure
- No behavior change, equivalent class strings

Part of Label Studio BEM migration (Phase 1: Leaf components)
Completed migration of PredictionsList from Block/Elem to cn() helper.
- Removed Block import (file was already partially using cn())
- Converted Block name="prediction-card" to div with rootClass.toClassName()
- File already used cn() for all Elem components via rootClass pattern
- Preserved all props, handlers, and structure
- No behavior change, equivalent class strings

Part of Label Studio BEM migration (Phase 1: Leaf components)
Migrated DataManager page from Block/Elem to cn() helper.
- Replaced Block/Elem imports with cn import
- Converted Block name="crash" to div with cn("crash")
- Converted Elem name="info" to div with cn("crash").elem("info")
- Converted Block ref={root} name="datamanager" to div with ref and cn("datamanager")
- Preserved all props, refs, and conditional rendering
- No behavior change, equivalent class strings

Part of Label Studio BEM migration (Phase 1: Leaf components)
Migrated InlineError component from Block/Elem to cn() helper.
- Replaced Block import with cn import
- Converted Block name="inline-error" with mix and style to div with cn().mix()
- Preserved all props, handlers, and ErrorWrapper integration
- No behavior change, equivalent class strings

Part of Label Studio BEM migration (Phase 2: Mid-level components)
Migrated Pagination and NavigationButton components from Block/Elem to cn() helper.
- Replaced Block/Elem imports with cn import
- Converted Block name="pagination-ls" with mod to div with cn().mod()
- Converted all Elem components (label, navigation, input, page-indicator, divider, btn, page-size) to div with cn().elem()
- Preserved all props, handlers, state management, and complex pagination logic
- Maintained TypeScript types and interfaces
- No behavior change, equivalent class strings

Part of Label Studio BEM migration (Phase 2: Mid-level components)
Completed migration of DropdownComponent from Block/Elem to cn() helper.
- Removed Block import (file was already partially using cn())
- Converted Block name="dropdown-ls" with mix to div with rootName.mix()
- File already used cn() for rootName and elem("menu") via rootName pattern
- Preserved all props, refs, handlers, animation logic, and portal rendering
- No behavior change, equivalent class strings

Part of Label Studio BEM migration (Phase 2: Mid-level components - COMPLETE!)
Migrated Form.Indicator subcomponent from Block/Elem to cn() helper.
- Replaced Block/Elem imports with cn import
- Converted Block name="form-indicator" to div with cn("form-indicator")
- Converted Elem tag="span" with mod to span with cn().elem().mod()
- Preserved all props, state context, and Oneof integration
- No behavior change, equivalent class strings

CRITICAL PATH: This component is used by 5 other files (StorageForm, AnnotationSettings, GeneralSettings, Config, etc.)
Part of Label Studio BEM migration (Phase 3: Critical path component - COMPLETE!)
Migrated ImportModal component from Elem to cn() helper.
- Replaced Elem import with cn import
- Converted Elem block="modal" name="title" to div with cn("modal").elem("title")
- Preserved all props, Modal integration, and handlers
- No behavior change, equivalent class strings

Part of Label Studio BEM migration (Phase 4: Mid-level pages)
Migrated PeopleList component from Block/Elem to cn() helper.
- Replaced Block/Elem imports with cn import
- Converted Block name="people-list" to div with cn("people-list")
- Converted all Elem components (wrapper, users, header, column, body, user, field, loading) to div with cn().elem()
- Preserved all props, handlers, Pagination integration, and table-like structure
- Maintained mix modifiers for avatar, email, name, last-activity
- No behavior change, equivalent class strings

Part of Label Studio BEM migration (Phase 4: Mid-level pages)
@luarmr luarmr requested a review from a team as a code owner November 5, 2025 23:46
@netlify
Copy link

netlify bot commented Nov 5, 2025

Deploy Preview for heartex-docs canceled.

Name Link
🔨 Latest commit 76d7653
🔍 Latest deploy log https://app.netlify.com/projects/heartex-docs/deploys/6914c584c0b735000897e30d

@netlify
Copy link

netlify bot commented Nov 5, 2025

Deploy Preview for label-studio-storybook ready!

Name Link
🔨 Latest commit 76d7653
🔍 Latest deploy log https://app.netlify.com/projects/label-studio-storybook/deploys/6914c5842aa2c30008fc49df
😎 Deploy Preview https://deploy-preview-8765--label-studio-storybook.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@netlify
Copy link

netlify bot commented Nov 5, 2025

Deploy Preview for label-studio-docs-new-theme canceled.

Name Link
🔨 Latest commit 76d7653
🔍 Latest deploy log https://app.netlify.com/projects/label-studio-docs-new-theme/deploys/6914c58457c0110007edfb1d

@netlify
Copy link

netlify bot commented Nov 5, 2025

Deploy Preview for label-studio-playground ready!

Name Link
🔨 Latest commit 76d7653
🔍 Latest deploy log https://app.netlify.com/projects/label-studio-playground/deploys/6914c5843012b10008426004
😎 Deploy Preview https://deploy-preview-8765--label-studio-playground.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@luarmr luarmr changed the title Fb echo 421/remove bem from lso refactor: ECHO-421: remove bem from lso Nov 5, 2025
Migrated ModelsPage from Block to cn() helper.
- Replaced Block import with cn import
- Converted Block name="prompter" to div with cn("prompter")
- Preserved all props and EmptyList integration
- No behavior change, equivalent class strings

Part of Label Studio BEM migration (Phase 4: Mid-level pages)
Migrated ProjectsList, EmptyProjectsList, and ProjectCard components from Block/Elem to cn() helper.
- Replaced Block/Elem imports with cn import
- Converted ProjectsList Elems to use parent Block "projects-page" (list, pages)
- Converted EmptyProjectsList Block "empty-projects-page" to div with cn()
- Converted ProjectCard nested structure:
  - Elem tag={NavLink} to NavLink with cn("projects-page").elem("link")
  - Block "project-card" with all nested Elems (header, title, menu, summary, annotation, etc.)
- Converted Icon Elem tags to Icon components with className
- Preserved all props, handlers, styles, mods, and complex nested structure
- No behavior change, equivalent class strings

Part of Label Studio BEM migration (Phase 4: Mid-level pages - COMPLETE!)
Migrated PeoplePage from Block/Elem to cn() helper.
- Replaced Block/Elem imports with cn import
- Converted Block name="people" to div with cn("people")
- Converted Elem components (controls, content) to div with cn().elem()
- Preserved all props, handlers, PeopleList/SelectedUser integration, and feature flags
- No behavior change, equivalent class strings

Part of Label Studio BEM migration (Phase 5: Top-level pages)
Migrated ProjectsPage from Block/Elem to cn() helper.
- Replaced Block/Elem imports with cn import
- Converted Block name="projects-page" to div with cn("projects-page")
- Converted Elem components (loading, content) to div with cn().elem()
- Preserved all props, handlers, Oneof integration, and ProjectsList/EmptyProjectsList rendering
- No behavior change, equivalent class strings

Part of Label Studio BEM migration (Phase 5: Top-level pages)
@codecov
Copy link

codecov bot commented Nov 5, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 59.45%. Comparing base (ffad427) to head (76d7653).
⚠️ Report is 4 commits behind head on develop.

❗ There is a different number of reports uploaded between BASE (ffad427) and HEAD (76d7653). Click for more details.

HEAD has 1 upload less than BASE
Flag BASE (ffad427) HEAD (76d7653)
pytests 1 0
Additional details and impacted files
@@             Coverage Diff             @@
##           develop    #8765      +/-   ##
===========================================
- Coverage    67.33%   59.45%   -7.89%     
===========================================
  Files          806      553     -253     
  Lines        62467    39360   -23107     
  Branches     10419    10419              
===========================================
- Hits         42060    23400   -18660     
+ Misses       20404    15957    -4447     
  Partials         3        3              
Flag Coverage Δ
lsf-e2e 53.78% <ø> (ø)
lsf-integration 50.19% <ø> (-0.05%) ⬇️
lsf-unit 8.27% <ø> (ø)
pytests ?

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Migrated Config component's form indicator from Block/Elem to cn() helper.
- Replaced Block/Elem imports with cn only
- Converted inline Block name="form-indicator" to div with cn("form-indicator")
- Converted Elem tag="span" with mod to span with cn().elem().mod()
- Preserved all props and saved state indicator
- No behavior change, equivalent class strings

Part of Label Studio BEM migration (Phase 5: Top-level pages)
Migrated StorageForm component's form indicator from Block/Elem to cn() helper.
- Replaced Block/Elem imports with cn import
- Converted Block name="form-indicator" to div with cn("form-indicator")
- Converted Elem tag="span" with mod to span with cn().elem().mod()
- Preserved all props, Oneof integration, and connection validation states
- No behavior change, equivalent class strings

Part of Label Studio BEM migration (Phase 5: Top-level pages)
@luarmr luarmr force-pushed the fb-echo-421/remove-bem-from-lso branch from 6d14168 to c1a4821 Compare November 6, 2025 01:07
Migrated AnnotationSettings from Block/Elem to cn() helper.
- Replaced Block/Elem imports with cn import
- Converted Block name="annotation-settings" to div with cn("annotation-settings")
- Converted Block name="settings-wrapper" to div with cn("settings-wrapper")
- Converted Elem name="wrapper" and name="header" to div with cn().elem()
- Preserved all props, handlers, Form integration, and nested structure
- No behavior change, equivalent class strings

Part of Label Studio BEM migration (Phase 5: Top-level pages)
Migrated GeneralSettings from Block/Elem to cn() helper.
- Replaced Block/Elem imports with cn import
- Converted Block name="general-settings" to div with cn("general-settings")
- Converted Block name="settings-wrapper" to div with cn("settings-wrapper")
- Converted Block name="workspace-placeholder" with nested Elems to div structure
- Converted Block name="color" to div with cn("color")
- Converted all Elem components (wrapper, badge-wrapper, title) to div with cn().elem()
- Preserved all props, handlers, Form integration, RadioGroup, and feature flags
- No behavior change, equivalent class strings

Part of Label Studio BEM migration (Phase 5: Top-level pages - COMPLETE!)

🎉 ALL 24 FILES MIGRATED! Label Studio is now 100% using cn() helper!
Label Studio has been fully migrated to use the modern cn() helper.
Removed unused exports from bem.tsx:
- Block (no longer used in LSO)
- Elem (no longer used in LSO)
- BemWithSpecificContext (no longer used)
- BlockContext (no longer used)
- useBEM (no longer used)
- type CN (not used)
- type CNTagName (not used)
- type BemComponent (not used)

Only cn() is now exported for Label Studio.

Note: LSE may still use Block/Elem and should import directly from
@HumanSignal/core if needed.

Part of Label Studio BEM migration cleanup.
@luarmr luarmr force-pushed the fb-echo-421/remove-bem-from-lso branch from c1a4821 to 44b5ec2 Compare November 6, 2025 05:41
@luarmr luarmr requested a review from a team November 6, 2025 16:07
return (
<Block name="backend-card">
<div className={rootClass.toClassName()}>
<div className={rootClass.elem("title-container")}>
Copy link
Contributor

Choose a reason for hiding this comment

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

could we add .toClassName() here - this'll clean up the output by not dropping all the object params in the DOM

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I will definitely check if I can implement a more holistic solution for this. Since these aren't the only instances and this involves preexisting code, I might run a script to localize this element or even use a plugin in Biome.

I will create a pr fixing those if you are ok with that.

Copy link
Contributor

Choose a reason for hiding this comment

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

sounds good to me

<Block name="prediction-card">
<div className={rootClass.toClassName()}>
<div>
<div className={rootClass.elem("title")}>
Copy link
Contributor

Choose a reason for hiding this comment

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

same here

type CNTagName,
type BemComponent,
} from "@humansignal/core/lib/utils/bem";
export { cnb as cn } from "@humansignal/core/lib/utils/bem";
Copy link
Contributor

Choose a reason for hiding this comment

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

much cleaner 😎

@luarmr
Copy link
Contributor Author

luarmr commented Nov 12, 2025

/git merge

Workflow run
Successfully merged: create mode 100644 label_studio/users/tests/test_admin_async_migrations_action.py

@robot-ci-heartex robot-ci-heartex merged commit 400a58c into develop Nov 12, 2025
45 of 46 checks passed
@robot-ci-heartex robot-ci-heartex deleted the fb-echo-421/remove-bem-from-lso branch November 12, 2025 20:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants