Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 10 additions & 3 deletions workspaces/lightspeed/.changeset/bright-waves-flow.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
---
'@red-hat-developer-hub/backstage-plugin-lightspeed': minor
'@red-hat-developer-hub/backstage-plugin-lightspeed': major
---

Graduate the New Frontend System (NFS) plugin from the `./alpha` export to the primary `./` entry point. Legacy (OFS) component exports are still available from the main path (deprecated, will be removed in a future release) and also accessible at `./legacy`. Translations remain at `./alpha`.
**Breaking:** Legacy (OFS) component exports have been removed from the main `./` entry point and are now exclusively available at the `./legacy` subpath. OFS consumers must update their imports:

Existing OFS dynamic plugin configurations continue to work without changes — `module: Legacy` is advised but not mandatory.
```diff
- import { LightspeedDrawerProvider } from '@red-hat-developer-hub/backstage-plugin-lightspeed';
+ import { LightspeedDrawerProvider } from '@red-hat-developer-hub/backstage-plugin-lightspeed/legacy';
```

**New:** Graduate the New Frontend System (NFS) plugin from `./alpha` to the primary `./` entry point. The drawer state management has been refactored from a React Context (`globalThis` singleton) to a proper global store using `@backstage/version-bridge` + `useSyncExternalStore`, eliminating the Provider dependency for consumers.

Translations remain at `./alpha`. Existing OFS dynamic plugin configurations using `module: Legacy` continue to work unchanged.
1 change: 1 addition & 0 deletions workspaces/lightspeed/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@
"react": "^18.3.1"
},
"resolutions": {
"@backstage/plugin-catalog-node": "2.1.0",
"@testing-library/react": "15.0.7",
"@types/react": "^18",
"@types/react-dom": "^18",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ import {
LightspeedChatContainer,
LightspeedDrawerStateExposer,
LightspeedFAB,
} from '@red-hat-developer-hub/backstage-plugin-lightspeed';
} from '@red-hat-developer-hub/backstage-plugin-lightspeed/legacy';
import { Administration } from '@backstage-community/plugin-rbac';
import { ApplicationDrawer } from './ApplicationDrawer';
import LogoFull from './LogoFull';
Expand Down
2 changes: 1 addition & 1 deletion workspaces/lightspeed/packages/backend/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ backend.add(import('@backstage/plugin-search-backend/alpha'));

// search engine
// See https://backstage.io/docs/features/search/search-engines
backend.add(import('@backstage/plugin-search-backend-module-pg/alpha'));
// backend.add(import('@backstage/plugin-search-backend-module-pg/alpha'));

// search collators
backend.add(import('@backstage/plugin-search-backend-module-catalog'));
Expand Down
18 changes: 9 additions & 9 deletions workspaces/lightspeed/plugins/lightspeed/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,7 @@ Expected: entries for `LightspeedFABModule` and `LightspeedTranslationsModule`.

### OFS Mode (Scalprum / Legacy)

OFS mode uses Scalprum for dynamic plugin loading. Legacy exports are also available from the default module (PluginRoot), but it is advisable to add `module: Legacy` for clarity. Translation resources require `module: Alpha`:
OFS mode uses Scalprum for dynamic plugin loading. Legacy exports require `module: Legacy`. Translation resources require `module: Alpha`:

```yaml
plugins:
Expand Down Expand Up @@ -335,32 +335,32 @@ import { lightspeedFABModule } from '@red-hat-developer-hub/backstage-plugin-lig

### Legacy / OFS consumers

Legacy component imports from the main package path still work for backwards compatibility, but are deprecated and will be removed in a future release. New OFS consumers should use the `./legacy` subpath:
Legacy component imports have been **removed from the main package path**. OFS consumers must update imports to use the `./legacy` subpath:

```ts
// Still works (deprecated — will be removed in a future release)
// Before (no longer works)
import { LightspeedPage, LightspeedDrawerProvider } from '@red-hat-developer-hub/backstage-plugin-lightspeed';

// Recommended for new OFS consumers
// After (required)
import { LightspeedPage, LightspeedDrawerProvider } from '@red-hat-developer-hub/backstage-plugin-lightspeed/legacy';
```

### Dynamic plugin configuration (OFS)

Legacy exports are available from the default module (PluginRoot), but it is advisable to add `module: Legacy` for clarity:
Legacy exports are available exclusively from the `Legacy` module. Use `module: Legacy` in your dynamic plugin configuration:

```yaml
dynamicRoutes:
- path: /intelligent-assistant
importName: LightspeedPage
module: Legacy # advisable for clarity, but not mandatory
module: Legacy
```

### Dynamic plugin module name change

The `LightspeedPlugin` scalprum module has been removed. Configurations that previously used `module: LightspeedPlugin` should be updated:

- **OFS/Legacy** — omit `module` (defaults to PluginRoot which has legacy exports) or use `module: Legacy` for clarity
- **OFS/Legacy** — use `module: Legacy` (required since legacy exports are no longer on the default module)
- **NFS** — use the default `module: PluginRoot` (or omit `module` entirely)

```yaml
Expand All @@ -370,9 +370,9 @@ dynamicRoutes:
importName: LightspeedPage
module: LightspeedPlugin

# After (OFS — either works)
# After (OFS)
dynamicRoutes:
- path: /intelligent-assistant
importName: LightspeedPage
module: Legacy # or omit module entirely
module: Legacy
```
Original file line number Diff line number Diff line change
Expand Up @@ -15,25 +15,28 @@ dynamicPlugins:
# ------------------------------------------------------------

# OFS (Old Frontend System) deployment configuration:

# Note: Legacy exports are also available from the default module (PluginRoot),
# but it is advisable to add `module: Legacy` for clarity.
# Legacy exports require `module: Legacy` — they are not available on the default module.
translationResources:
- importName: lightspeedTranslations
module: Alpha
ref: lightspeedTranslationRef
dynamicRoutes:
- path: /intelligent-assistant
importName: LightspeedPage
module: Legacy
mountPoints:
- mountPoint: application/listener
importName: LightspeedFAB
module: Legacy
- mountPoint: application/provider
importName: LightspeedDrawerProvider
module: Legacy
- mountPoint: application/internal/drawer-state
importName: LightspeedDrawerStateExposer
module: Legacy
- mountPoint: application/internal/drawer-content
importName: LightspeedChatContainer
module: Legacy
config:
id: lightspeed
priority: 100
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,11 @@ import { PropsWithChildren } from 'react';

import { ChatbotDisplayMode } from '@patternfly/chatbot';

import { useLightspeedDrawerContext } from '../src/hooks/useLightspeedDrawerContext';
import { useLightspeedDrawer } from '../src/hooks/useLightspeedDrawer';
import { CustomDrawer } from './CustomDrawer';

export const DrawerComponent = ({ children }: PropsWithChildren) => {
const { displayMode, drawerWidth, setDrawerWidth } =
useLightspeedDrawerContext();
const { displayMode, drawerWidth, setDrawerWidth } = useLightspeedDrawer();
return (
<CustomDrawer
isDrawerOpen={displayMode === ChatbotDisplayMode.docked}
Expand Down
1 change: 1 addition & 0 deletions workspaces/lightspeed/plugins/lightspeed/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@
"@backstage/plugin-app-react": "^0.2.1",
"@backstage/plugin-permission-react": "^0.4.41",
"@backstage/theme": "^0.7.2",
"@backstage/version-bridge": "^1.0.12",
"@material-ui/core": "^4.9.13",
"@material-ui/lab": "^4.0.0-alpha.61",
"@monaco-editor/react": "^4.7.0",
Expand Down
54 changes: 0 additions & 54 deletions workspaces/lightspeed/plugins/lightspeed/report.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,17 @@ import { AnyApiFactory } from '@backstage/frontend-plugin-api';
import { AnyRouteRefParams } from '@backstage/frontend-plugin-api';
import { ApiFactory } from '@backstage/frontend-plugin-api';
import { AppDrawerContent } from '@red-hat-developer-hub/backstage-plugin-app-react';
import { BackstagePlugin } from '@backstage/core-plugin-api';
import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api';
import { ExtensionBlueprintParams } from '@backstage/frontend-plugin-api';
import { ExtensionDataRef } from '@backstage/frontend-plugin-api';
import { ExtensionInput } from '@backstage/frontend-plugin-api';
import { FrontendModule } from '@backstage/frontend-plugin-api';
import { IconElement } from '@backstage/frontend-plugin-api';
import { JSX as JSX_2 } from 'react';
import { JSX as JSX_3 } from 'react/jsx-runtime';
import { OverridableExtensionDefinition } from '@backstage/frontend-plugin-api';
import { OverridableFrontendPlugin } from '@backstage/frontend-plugin-api';
import { PathParams } from '@backstage/core-plugin-api';
import { PropsWithChildren } from 'react';
import { RouteRef } from '@backstage/frontend-plugin-api';
import { RouteRef as RouteRef_2 } from '@backstage/core-plugin-api';
import { SubRouteRef } from '@backstage/frontend-plugin-api';
import { SubRouteRef as SubRouteRef_2 } from '@backstage/core-plugin-api';

// @public
const _default: OverridableFrontendPlugin<
Expand Down Expand Up @@ -168,57 +162,9 @@ const _default: OverridableFrontendPlugin<
>;
export default _default;

// @public
export type DrawerState = {
id: string;
isDrawerOpen: boolean;
drawerWidth: number;
setDrawerWidth: (width: number) => void;
closeDrawer: () => void;
};

// @public
export type DrawerStateExposerProps = {
onStateChange: (state: DrawerState) => void;
};

// @public (undocumented)
export const LightspeedChatContainer: () => JSX_3.Element;

// @public
export const LightspeedDrawerProvider: React.ComponentType<PropsWithChildren>;

// @public
export const LightspeedDrawerStateExposer: (
input: DrawerStateExposerProps,
) => null;

// @public
export const LightspeedFAB: () => JSX_3.Element | null;

// @public (undocumented)
export const lightspeedFABModule: FrontendModule;

// @public
export const LightspeedIcon: () => JSX_3.Element;

// @public
export const LightspeedPage: () => JSX_3.Element;

// @public
export const lightspeedPlugin: BackstagePlugin<
{
root: RouteRef_2<undefined>;
lightspeedConversation: SubRouteRef_2<
PathParams<'/conversation/:conversationId'>
>;
lightspeedNotebooks: SubRouteRef_2<undefined>;
lightspeedNotebookView: SubRouteRef_2<PathParams<'/notebooks/:notebookId'>>;
},
{},
{}
>;

// @public (undocumented)
export const lightspeedRedirectModule: FrontendModule;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import {
useState,
} from 'react';

import { useLightspeedDrawerContext } from '../hooks/useLightspeedDrawerContext';
import { useLightspeedDrawer } from '../hooks/useLightspeedDrawer';
import { useTranslation } from '../hooks/useTranslation';
import { FileContent } from '../types';
import { isSupportedFileType, readFileAsText } from '../utils/attachment-utils';
Expand Down Expand Up @@ -59,8 +59,7 @@ const FileAttachmentContextProvider: FC<{
children: ReactNode;
}> = ({ children }) => {
const { t } = useTranslation();
const { draftFileContents, setDraftFileContents } =
useLightspeedDrawerContext();
const { draftFileContents, setDraftFileContents } = useLightspeedDrawer();
const [currentFileContent, setCurrentFileContent] = useState<
FileContent | undefined
>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ import {
} from '../hooks';
import { useCreateNotebook } from '../hooks/notebooks/useCreateNotebook';
import { useNotebookDocuments } from '../hooks/notebooks/useNotebookDocuments';
import { useLightspeedDrawerContext } from '../hooks/useLightspeedDrawerContext';
import { useLightspeedDrawer } from '../hooks/useLightspeedDrawer';
import { useLightspeedUpdatePermission } from '../hooks/useLightspeedUpdatePermission';
import { useTranslation } from '../hooks/useTranslation';
import { useWelcomePrompts } from '../hooks/useWelcomePrompts';
Expand Down Expand Up @@ -666,7 +666,7 @@ export const LightspeedChat = ({
consumePendingOverlayThreadHandoff,
shellViewTab,
setShellViewTab,
} = useLightspeedDrawerContext();
} = useLightspeedDrawer();
const isFullscreenMode = displayMode === ChatbotDisplayMode.embedded;
const location = useLocation();
const isNotebooksFullscreenPath =
Expand Down Expand Up @@ -972,6 +972,8 @@ export const LightspeedChat = ({
if (lastOpenedId === TEMP_CONVERSATION_ID || lastOpenedId === null) {
if (onOverlayLikeSurface && stillOnProvisionalThread) {
setNewChatCreated(true);
} else if (!stillOnProvisionalThread) {
setNewChatCreated(false);
}
}
}, [
Expand Down Expand Up @@ -1768,6 +1770,7 @@ export const LightspeedChat = ({
onSendMessage={sendMessage}
isSendButtonDisabled={isSendButtonDisabled}
hasAttachButton
attachButtonPosition="start"
handleAttach={handleAttach}
hasMicrophoneButton
value={draftMessage}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@
* limitations under the License.
*/

import React, { createContext } from 'react';

import { ChatbotDisplayMode } from '@patternfly/chatbot';

import { FileContent } from '../types';
Expand All @@ -30,7 +28,7 @@ export type LightspeedEmbeddedNotebooksTarget =
| { notebookSessionId: string };

/**
* Type for LightspeedDrawerContext
* Type for Lightspeed drawer state and actions.
*
* @public
*/
Expand Down Expand Up @@ -66,7 +64,7 @@ export interface LightspeedDrawerContextType {
/**
* The function for setting the drawer width
*/
setDrawerWidth: React.Dispatch<React.SetStateAction<number>>;
setDrawerWidth: (width: number) => void;
/**
* The current conversation ID
*/
Expand Down Expand Up @@ -107,19 +105,3 @@ export interface LightspeedDrawerContextType {
shellViewTab: number;
setShellViewTab: (tab: number) => void;
}

const CONTEXT_KEY = '__lightspeed_drawer_context__' as keyof typeof globalThis;

function getOrCreateContext() {
const existing = (globalThis as any)[CONTEXT_KEY];
if (existing)
return existing as React.Context<LightspeedDrawerContextType | undefined>;
const ctx = createContext<LightspeedDrawerContextType | undefined>(undefined);
(globalThis as any)[CONTEXT_KEY] = ctx;
return ctx;
}

/**
* @public
*/
export const LightspeedDrawerContext = getOrCreateContext();
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@ import { makeStyles } from '@mui/styles';
import { ChatbotModal } from '@patternfly/chatbot';

import { DOCKED_CONTENT_OFFSET } from '../const';
import { useLightspeedProviderState } from '../hooks/useLightspeedProviderState';
import { useLightspeedDrawer } from '../hooks/useLightspeedDrawer';
import { useLightspeedShellState } from '../hooks/useLightspeedProviderState';
import { LightspeedChatContainer } from './LightspeedChatContainer';
import { LightspeedDrawerContext } from './LightspeedDrawerContext';

const useStyles = makeStyles(theme => ({
chatbotModal: {
Expand All @@ -40,20 +40,24 @@ const useStyles = makeStyles(theme => ({
}));

/**
* Router-bridge shell: syncs React Router / AppDrawer state with the
* global lightspeedDrawerStore and renders the overlay ChatbotModal.
* No Context.Provider — state flows through the singleton store.
*
* @public
*/
export const LightspeedDrawerProvider = ({ children }: PropsWithChildren) => {
const classes = useStyles();
const { contextValue, shouldRenderOverlayModal, closeChatbot } =
useLightspeedProviderState();
const { shouldRenderOverlayModal, closeChatbot } = useLightspeedShellState();
const { displayMode } = useLightspeedDrawer();

return (
<LightspeedDrawerContext.Provider value={contextValue}>
<>
{children}
{shouldRenderOverlayModal && (
<ChatbotModal
isOpen
displayMode={contextValue.displayMode}
displayMode={displayMode}
disableFocusTrap
onEscapePress={() => closeChatbot()}
ouiaId="LightspeedChatbotModal"
Expand All @@ -63,6 +67,6 @@ export const LightspeedDrawerProvider = ({ children }: PropsWithChildren) => {
<LightspeedChatContainer />
</ChatbotModal>
)}
</LightspeedDrawerContext.Provider>
</>
);
};
Loading
Loading