Skip to content

Commit 4310b15

Browse files
committed
chore: fix chat
1 parent 4a63359 commit 4310b15

File tree

3 files changed

+63
-9
lines changed

3 files changed

+63
-9
lines changed

cypress/e2e/chat/selection-mode.cy.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { AuthTestUtils } from '../../support/auth-utils';
22
import { TestTool } from '../../support/page-utils';
3-
import { AddPageSelectors, PageSelectors, SidebarSelectors } from '../../support/selectors';
3+
import { AddPageSelectors, HeaderSelectors, PageSelectors, SidebarSelectors } from '../../support/selectors';
44
import { generateRandomEmail, logAppFlowyEnvironment } from '../../support/test-config';
55

66
const STUBBED_MESSAGE_ID = 101;
@@ -147,7 +147,8 @@ describe('Chat Selection Mode Tests', () => {
147147

148148
cy.contains(STUBBED_MESSAGE_CONTENT).should('be.visible');
149149

150-
PageSelectors.moreActionsButton().first().click({ force: true });
150+
// Click the header's more actions button (not the sidebar's)
151+
HeaderSelectors.moreActionsButton().click({ force: true });
151152

152153
cy.get('[role="menu"]').should('exist');
153154

cypress/support/selectors.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -305,6 +305,18 @@ export const SidebarSelectors = {
305305
pageHeader: (options?: CypressGetOptions) => cy.get(byTestId('sidebar-page-header'), options),
306306
};
307307

308+
/**
309+
* App Header selectors (top bar)
310+
*/
311+
export const HeaderSelectors = {
312+
// Header container
313+
container: (options?: CypressGetOptions) => cy.get('.appflowy-top-bar', options),
314+
315+
// More actions button in the header (not sidebar)
316+
moreActionsButton: (options?: CypressGetOptions) =>
317+
cy.get('.appflowy-top-bar').find(byTestId('page-more-actions'), options),
318+
};
319+
308320
/**
309321
* Trash view selectors
310322
*/

src/pages/AppPage.tsx

Lines changed: 48 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -48,10 +48,51 @@ function AppPage() {
4848
const { getViewReadOnlyStatus } = useViewOperations();
4949

5050
const currentUser = useCurrentUser();
51-
const view = useMemo(() => {
51+
const service = useService();
52+
53+
// View from outline (may be undefined if outline hasn't updated yet)
54+
const outlineView = useMemo(() => {
5255
if (!outline || !viewId) return;
5356
return findView(outline, viewId);
5457
}, [outline, viewId]);
58+
59+
// Fallback view fetched from server when not in outline
60+
const [fallbackView, setFallbackView] = React.useState<{ view_id: string; layout: ViewLayout } | null>(null);
61+
62+
// Fetch view metadata when not found in outline (handles race condition after creating new view)
63+
useEffect(() => {
64+
if (outlineView || !viewId || !workspaceId || !service) {
65+
// Clear fallback when outline has the view
66+
if (outlineView && fallbackView?.view_id === viewId) {
67+
setFallbackView(null);
68+
}
69+
70+
return;
71+
}
72+
73+
// View not in outline - fetch from server directly
74+
let cancelled = false;
75+
76+
service
77+
.getAppView(workspaceId, viewId)
78+
.then((fetchedView) => {
79+
if (!cancelled && fetchedView) {
80+
setFallbackView({ view_id: fetchedView.view_id, layout: fetchedView.layout });
81+
}
82+
})
83+
.catch((e) => {
84+
console.warn('[AppPage] Failed to fetch view metadata for', viewId, e);
85+
});
86+
87+
return () => {
88+
cancelled = true;
89+
};
90+
}, [outlineView, viewId, workspaceId, service, fallbackView?.view_id]);
91+
92+
// Use outline view if available, otherwise use fallback
93+
const view = outlineView;
94+
const layout = outlineView?.layout ?? fallbackView?.layout;
95+
5596
const rendered = useContext(AppContext)?.rendered;
5697

5798
const helmet = useMemo(() => {
@@ -61,8 +102,6 @@ function AppPage() {
61102
</Suspense>
62103
) : null;
63104
}, [rendered, view]);
64-
65-
const layout = view?.layout;
66105
const [doc, setDoc] = React.useState<YDoc | undefined>(undefined);
67106
const [error, setError] = React.useState<AppError | null>(null);
68107
const loadPageDoc = useCallback(
@@ -141,7 +180,6 @@ function AppPage() {
141180
[uploadFile, view]
142181
);
143182

144-
const service = useService();
145183
const requestInstance = service?.getAxiosInstance();
146184

147185
// Check if view is in shareWithMe and determine readonly status
@@ -151,7 +189,10 @@ function AppPage() {
151189
}, [getViewReadOnlyStatus, viewId, outline]);
152190

153191
const viewDom = useMemo(() => {
154-
if (!doc && layout === ViewLayout.AIChat && viewId) {
192+
// Check if doc belongs to current viewId (handles race condition when doc from old view arrives after navigation)
193+
const docForCurrentView = doc && doc.id === viewId ? doc : undefined;
194+
195+
if (!docForCurrentView && layout === ViewLayout.AIChat && viewId) {
155196
return (
156197
<Suspense>
157198
<AIChat chatId={viewId} onRendered={onRendered} />
@@ -161,11 +202,11 @@ function AppPage() {
161202

162203
const View = layout === ViewLayout.Document ? Document : DatabaseView;
163204

164-
return doc && viewMeta && workspaceId && View ? (
205+
return docForCurrentView && viewMeta && workspaceId && View ? (
165206
<View
166207
requestInstance={requestInstance}
167208
workspaceId={workspaceId}
168-
doc={doc}
209+
doc={docForCurrentView}
169210
readOnly={isReadOnly}
170211
viewMeta={viewMeta}
171212
navigateToView={toView}

0 commit comments

Comments
 (0)