Files
sencho/frontend/src/components/EditorLayout/mobile-surface.ts
T
Anso e8f271f5f6 feat(ui): make the core stack flow usable on mobile (#1327)
* feat(ui): make the core stack flow usable on mobile

Below the md breakpoint the app collapses to a single full-width column:
the stack list is full-screen, tapping a stack opens a full-screen detail
with a Health / Logs / Compose segmented control (Logs first) and a back
button, and a bottom tab bar switches Stacks, Fleet, Schedules, and
Settings. Compose is read-only on a phone with a prompt to edit on desktop.

Desktop (md and up) is unchanged: the mobile shell is gated behind a
useIsMobile hook plus max-md/md variants, and the stack-detail blocks are
shared with the desktop two-pane view so it renders identically.

Also generalizes the unsaved-changes guard so leaving a dirty editor (back,
tab bar, hamburger) prompts before discarding; adds 44px touch targets on
list rows, filter chips, and actions; makes log and shell modals full-screen
on mobile; and offsets toasts and the deploy pill above the bottom tab bar.

* fix(ui): keep mobile nav in sync when opening views from outside the bottom bar

On a phone the sidebar activity actions, the node switcher's Manage Nodes, the
profile Settings entry, and the dashboard configuration links set the active
view without flipping the mobile surface to content, so the user stayed on the
stack list and never saw the destination. Route these through the mobile-aware
navigation and settings helpers (a no-op on desktop).
2026-06-07 01:03:13 -04:00

45 lines
1.6 KiB
TypeScript

import type { ActiveView } from './hooks/useViewNavigationState';
// The top-level mobile surface when no stack detail is open. Kept distinct from
// `activeView` so `dashboard` still maps to HomeDashboard rather than being
// overloaded to mean "the stack list".
export type MobileView = 'list' | 'content';
// The single surface the mobile shell renders at a time.
export type MobileSurface = 'list' | 'content' | 'detail';
export interface MobileSurfaceInput {
activeView: ActiveView;
selectedFile: string | null;
mobileView: MobileView;
/** Set the instant a row is tapped, before loadFile resolves selectedFile. */
pendingDetailStack: string | null;
}
export interface MobileSurfaceState {
surface: MobileSurface;
/** The real EditorView can mount (a stack is selected and editor is active). */
detailReady: boolean;
/** Detail surface should show, including the optimistic pre-fetch window. */
detailOpen: boolean;
}
/**
* Pure derivation of which mobile surface to show. Extracted so the state
* machine can be unit-tested independently of the context-heavy EditorLayout.
*/
export function deriveMobileSurface({
activeView,
selectedFile,
mobileView,
pendingDetailStack,
}: MobileSurfaceInput): MobileSurfaceState {
const detailReady = activeView === 'editor' && !!selectedFile;
const detailOpen = detailReady || !!pendingDetailStack;
let surface: MobileSurface;
if (detailOpen) surface = 'detail';
else if (mobileView === 'list') surface = 'list';
else surface = 'content';
return { surface, detailReady, detailOpen };
}