Skip to content

Commit 081845e

Browse files
committed
chore: lint
1 parent 732326e commit 081845e

File tree

5 files changed

+82
-8
lines changed

5 files changed

+82
-8
lines changed

src/application/services/js-services/http/http_api.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -998,7 +998,7 @@ export async function getAppOutline(workspaceId: string): Promise<AppOutlineResp
998998
}));
999999
}
10001000

1001-
export async function getView(workspaceId: string, viewId: string, depth: number = 2) {
1001+
export async function getView(workspaceId: string, viewId: string, depth: number = 1) {
10021002
const url = `/api/workspace/${workspaceId}/view/${viewId}?depth=${depth}`;
10031003

10041004
return executeAPIRequest<View>(() =>
@@ -2171,7 +2171,7 @@ export async function turnIntoMember(workspaceId: string, email: string) {
21712171

21722172

21732173
export async function getShareWithMe(workspaceId: string): Promise<View> {
2174-
const url = `/api/sharing/workspace/${workspaceId}/view/${workspaceId}?depth=1`;
2174+
const url = `/api/sharing/workspace/${workspaceId}/view/${workspaceId}?depth=50`;
21752175

21762176
return executeAPIRequest<View>(() =>
21772177
axiosInstance?.get<APIResponse<View>>(url)

src/components/_shared/outline/mergeOutline.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,16 @@ export function addViewToOutline(
107107

108108
const next = outline.map((view) => {
109109
if (view.view_id === parentId) {
110+
// In lazy outline mode, an unloaded parent can have has_children=true
111+
// with empty children. Don't materialize a partial child list in that case.
112+
const parentChildrenLoaded = view.children.length > 0 || view.has_children === false;
113+
114+
if (!parentChildrenLoaded) {
115+
if (view.has_children) return view;
116+
changed = true;
117+
return { ...view, has_children: true };
118+
}
119+
110120
// Don't add duplicates
111121
if (view.children.some((c) => c.view_id === newView.view_id)) {
112122
return view;

src/components/app/hooks/usePageOperations.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,14 +43,15 @@ export function usePageOperations({
4343
try {
4444
const response = await service?.addAppPage(currentWorkspaceId, parentViewId, payload);
4545

46-
// Keep current expanded/loaded subtree state. New page is synced via
47-
// WebSocket notifications (FolderViewChanged/FolderChanged).
46+
// Keep a resilient fallback when realtime delivery is unavailable.
47+
// This guarantees sidebar eventual consistency after creation.
48+
void loadOutline?.(currentWorkspaceId, false);
4849
return response;
4950
} catch (e) {
5051
return Promise.reject(e);
5152
}
5253
},
53-
[currentWorkspaceId, service, outline, role]
54+
[currentWorkspaceId, service, outline, role, loadOutline]
5455
);
5556

5657
// Delete a page (move to trash)

src/components/app/layers/AppBusinessLayer.tsx

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ export const AppBusinessLayer: React.FC<AppBusinessLayerProps> = ({ children })
7979
const { loadView, createRow, toView, awarenessMap, getViewIdFromDatabaseId, bindViewSync } = useViewOperations();
8080

8181
// Initialize page operations
82-
const pageOperations = usePageOperations({ outline });
82+
const pageOperations = usePageOperations({ outline, loadOutline });
8383

8484
// Check if current view has been deleted
8585
const viewHasBeenDeleted = useMemo(() => {
@@ -130,8 +130,17 @@ export const AppBusinessLayer: React.FC<AppBusinessLayerProps> = ({ children })
130130
const cacheKey = `${currentWorkspaceId}:${viewId}`;
131131
const cached = routeViewExistsCacheRef.current.get(cacheKey);
132132

133-
if (cached !== undefined) {
134-
setRouteViewExists(cached);
133+
// Cache policy:
134+
// - false can be trusted (confirmed not-found)
135+
// - true is trusted only while the view is still present in current outline
136+
// to avoid stale positive cache after realtime deletes/moves.
137+
if (cached === false) {
138+
setRouteViewExists(false);
139+
return;
140+
}
141+
142+
if (cached === true && findView(outline ?? [], viewId)) {
143+
setRouteViewExists(true);
135144
return;
136145
}
137146

src/components/app/outline/Outline.tsx

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ export function Outline({ width }: { width: number }) {
4848
>(undefined);
4949
const loadingViewIdsRef = useRef<Set<string>>(new Set());
5050
const autoLoadRetryAfterRef = useRef<Map<string, number>>(new Map());
51+
const validatingRestoreIdsRef = useRef<Set<string>>(new Set());
52+
const validatedExistingRestoreIdsRef = useRef<Set<string>>(new Set());
5153
const [loadingRevision, setLoadingRevision] = useState(0);
5254
const [nowMs, setNowMs] = useState(() => Date.now());
5355
const loadingViewIds = useMemo(() => loadingViewIdsRef.current, [loadingRevision]); // eslint-disable-line react-hooks/exhaustive-deps
@@ -61,9 +63,61 @@ export function Outline({ width }: { width: number }) {
6163
setPendingAutoLoadIds(restoredExpandedIds);
6264
loadingViewIdsRef.current = new Set();
6365
autoLoadRetryAfterRef.current = new Map();
66+
validatingRestoreIdsRef.current = new Set();
67+
validatedExistingRestoreIdsRef.current = new Set();
6468
setLoadingRevision((r) => r + 1);
6569
}, [currentWorkspaceId]);
6670

71+
// Validate restored expanded IDs that are not in the current tree and prune only truly stale IDs.
72+
// This avoids keeping deleted/moved IDs forever, while preserving valid deep IDs.
73+
useEffect(() => {
74+
if (!outline || outline.length === 0 || !loadViewChildrenBatch || pendingAutoLoadIds.length === 0) return;
75+
76+
const unknownIds = pendingAutoLoadIds.filter((id) => {
77+
if (findView(outline, id)) return false;
78+
if (validatedExistingRestoreIdsRef.current.has(id)) return false;
79+
if (validatingRestoreIdsRef.current.has(id)) return false;
80+
return true;
81+
});
82+
83+
if (unknownIds.length === 0) return;
84+
85+
unknownIds.forEach((id) => validatingRestoreIdsRef.current.add(id));
86+
87+
void loadViewChildrenBatch(unknownIds)
88+
.then((views) => {
89+
const existingIds = new Set((views || []).map((view) => view.view_id));
90+
const staleIds = unknownIds.filter((id) => !existingIds.has(id));
91+
92+
existingIds.forEach((id) => validatedExistingRestoreIdsRef.current.add(id));
93+
94+
if (staleIds.length === 0) return;
95+
96+
const staleSet = new Set(staleIds);
97+
98+
staleIds.forEach((id) => {
99+
setOutlineExpands(id, false);
100+
loadingViewIdsRef.current.delete(id);
101+
autoLoadRetryAfterRef.current.delete(id);
102+
});
103+
104+
setPendingAutoLoadIds((prev) => {
105+
const next = prev.filter((id) => !staleSet.has(id));
106+
107+
return next.length === prev.length ? prev : next;
108+
});
109+
setExpandViewIds((prev) => {
110+
const next = prev.filter((id) => !staleSet.has(id));
111+
112+
return next.length === prev.length ? prev : next;
113+
});
114+
setLoadingRevision((r) => r + 1);
115+
})
116+
.finally(() => {
117+
unknownIds.forEach((id) => validatingRestoreIdsRef.current.delete(id));
118+
});
119+
}, [outline, pendingAutoLoadIds, loadViewChildrenBatch]);
120+
67121
// Drop startup pending ids as soon as they are confirmed loaded.
68122
useEffect(() => {
69123
setPendingAutoLoadIds((prev) => {

0 commit comments

Comments
 (0)