diff --git a/src/features/workbench/operational-timeline/operational-timeline-model.test.ts b/src/features/workbench/operational-timeline/operational-timeline-model.test.ts
index ca0c165..69c1d8a 100644
--- a/src/features/workbench/operational-timeline/operational-timeline-model.test.ts
+++ b/src/features/workbench/operational-timeline/operational-timeline-model.test.ts
@@ -1,6 +1,10 @@
import { describe, expect, it } from "vitest";
import type { ScheduledConditionItem } from "../types";
-import { clusterOperationalEvents, createOperationalEvents } from "./operational-timeline-model";
+import {
+ canDockMapScaleBesideTimeline,
+ clusterOperationalEvents,
+ createOperationalEvents
+} from "./operational-timeline-model";
const workOrder: ScheduledConditionItem = {
id: "work-1", kind: "work_order", code: "WO-1", scheduledAt: "2026-08-19T10:00:00.000Z",
@@ -39,4 +43,10 @@ describe("operational timeline model", () => {
expect(clusters).toHaveLength(2);
expect(clusters.find((cluster) => cluster.lane === "plan")?.events).toHaveLength(2);
});
+
+ it("docks scale info beside a floating timeline only when the business area is wide enough", () => {
+ expect(canDockMapScaleBesideTimeline(2048, "expanded", true)).toBe(true);
+ expect(canDockMapScaleBesideTimeline(1380, "expanded", true)).toBe(false);
+ expect(canDockMapScaleBesideTimeline(1380, "expanded", false)).toBe(true);
+ });
});
diff --git a/src/features/workbench/operational-timeline/operational-timeline-model.ts b/src/features/workbench/operational-timeline/operational-timeline-model.ts
index b7d8882..c6e3dab 100644
--- a/src/features/workbench/operational-timeline/operational-timeline-model.ts
+++ b/src/features/workbench/operational-timeline/operational-timeline-model.ts
@@ -30,6 +30,28 @@ export type OperationalEventCluster = {
events: OperationalEvent[];
};
+export const OPERATIONAL_TIMELINE_LAYOUT = {
+ compact: { height: 72, maxWidth: 980 },
+ expanded: { height: 240, maxWidth: 1180 },
+ summary: { height: 52, maxWidth: 760 },
+ mobileSummaryHeight: 48,
+ mapScaleSafeWidth: 520,
+ floatingGap: 48
+} as const;
+
+export function canDockMapScaleBesideTimeline(
+ businessWorkspaceWidth: number,
+ mode: OperationalTimelineMode,
+ visible: boolean
+) {
+ if (!visible) return true;
+ const layout = OPERATIONAL_TIMELINE_LAYOUT[mode];
+ return businessWorkspaceWidth >=
+ layout.maxWidth +
+ OPERATIONAL_TIMELINE_LAYOUT.mapScaleSafeWidth +
+ OPERATIONAL_TIMELINE_LAYOUT.floatingGap;
+}
+
export function createOperationalEvents(items: ScheduledConditionItem[]): OperationalEvent[] {
return items.flatMap((item) =>
item.kind === "work_order" ? createWorkOrderEvents(item) : createConditionEvents(item)
diff --git a/src/features/workbench/operational-timeline/operational-timeline.tsx b/src/features/workbench/operational-timeline/operational-timeline.tsx
index 0f6f321..3238863 100644
--- a/src/features/workbench/operational-timeline/operational-timeline.tsx
+++ b/src/features/workbench/operational-timeline/operational-timeline.tsx
@@ -13,6 +13,7 @@ import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/shar
import {
clusterOperationalEvents,
getOperationalEventSummary,
+ OPERATIONAL_TIMELINE_LAYOUT,
type OperationalEvent,
type OperationalEventCluster,
type OperationalEventLane,
@@ -54,7 +55,7 @@ export function OperationalTimeline({
const clusters = clusterOperationalEvents(events, mobile ? 60 : 10);
const selectedEvent = events.find((event) => event.id === selectedEventId) ?? null;
const cursorPosition = getDayPosition(cursorTime);
- const height = mobile ? "100%" : mode === "expanded" ? 240 : mode === "summary" ? 52 : 72;
+ const height = mobile ? "100%" : OPERATIONAL_TIMELINE_LAYOUT[mode].height;
if (mode === "summary" && !mobile) {
return (
diff --git a/src/features/workbench/workspace/workbench-main-frame.tsx b/src/features/workbench/workspace/workbench-main-frame.tsx
index f2fb886..6704a6d 100644
--- a/src/features/workbench/workspace/workbench-main-frame.tsx
+++ b/src/features/workbench/workspace/workbench-main-frame.tsx
@@ -1,6 +1,7 @@
import { ArrowLeft, FileChartColumnIncreasing } from "lucide-react";
import type { ReactNode } from "react";
import { cn } from "@/shared/ui/cn";
+import { OPERATIONAL_TIMELINE_LAYOUT } from "../operational-timeline/operational-timeline-model";
import { AnalysisArtifactWorkspace } from "./analysis-document-view";
import { ArtifactPeekBar } from "./artifact-peek-bar";
import type {
@@ -14,6 +15,7 @@ type WorkbenchMainFrameProps = {
mapOverlay?: ReactNode;
timelineContent: ReactNode;
timelineMode: "compact" | "expanded" | "summary";
+ reserveMapScaleSpace: boolean;
activeArtifact: AnalysisArtifact | null;
pendingArtifact: AnalysisArtifact | null;
surfaceMode: WorkbenchSurfaceMode;
@@ -30,6 +32,7 @@ export function WorkbenchMainFrame({
mapOverlay,
timelineContent,
timelineMode,
+ reserveMapScaleSpace,
activeArtifact,
pendingArtifact,
surfaceMode,
@@ -111,12 +114,14 @@ export function WorkbenchMainFrame({