diff --git a/organized-ideas.md b/organized-ideas.md index 60fd124..e967c5c 100644 --- a/organized-ideas.md +++ b/organized-ideas.md @@ -82,6 +82,7 @@ Stated explicitly during planning: this needs to run **entirely locally** during - **Recommended:** whatever visual grouping happens in compact view must be purely a canvas rendering concern — the BOM math (cable counts, lengths, totals) always operates on the real underlying connections, never on the aggregated display. Otherwise compact view could silently produce a wrong shopping list. - **Implementation detail, still loose:** exactly what "connection-type group" means for aggregation purposes isn't fully pinned down — grouped by cable type, by port family, by direction, some combination? Low-stakes to leave open since this is frontend-only and easy to iterate on visually once it's being built. - **Decided: toggle scope.** Per-device (each device node expands/collapses independently), defaulting to **compact** for a newly-placed device — consistent with the palette categories already defaulting to collapsed. Plus **"Expand all" / "Collapse all"** buttons on the canvas for quickly toggling every device at once, rather than clicking through each one individually. +- **Future TODO, not yet built: an "Auto-arrange" layout action.** Surfaced while building mobile view-only support (2026-09-28) — nothing stops two devices' saved positions from overlapping (only quick-add's own placement got a bigger anti-stack grid), and expanding a device grows its node size, which can turn a fine compact layout into an overlapping expanded one. On desktop this was always self-correctable (whoever's looking has edit access and can just drag nodes apart), but a mobile/view-only visitor now has no way to fix it themselves — they're stuck with whatever layout the diagram's owner left. Needs a real graph-layout pass (something like `dagre`), a decision on when it runs (probably a manual button rather than automatic-on-load, so a diagram doesn't silently rearrange itself), and to account for compact vs. expanded node sizing. Deliberately deferred rather than built speculatively — it's a standalone feature, not something to fold into the mobile branch. ## 5. Device Menus diff --git a/src/App.tsx b/src/App.tsx index 9d31d4d..b4feecd 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -32,7 +32,7 @@ export default function App() { }, [session]) if (session === undefined) { - return
Loading…
+ return
Loading…
} if (!session) { @@ -40,7 +40,7 @@ export default function App() { } if (needsUsername === undefined) { - return
Loading…
+ return
Loading…
} if (needsUsername) { diff --git a/src/components/auth/CompleteProfileScreen.tsx b/src/components/auth/CompleteProfileScreen.tsx index 5643319..5700297 100644 --- a/src/components/auth/CompleteProfileScreen.tsx +++ b/src/components/auth/CompleteProfileScreen.tsx @@ -39,7 +39,7 @@ export default function CompleteProfileScreen({ onDone }: { onDone: () => void } } return ( -
+

One more thing

Choose a username to finish setting up your account.

diff --git a/src/components/auth/LoginScreen.tsx b/src/components/auth/LoginScreen.tsx index 5f218b8..fbbe1a8 100644 --- a/src/components/auth/LoginScreen.tsx +++ b/src/components/auth/LoginScreen.tsx @@ -60,7 +60,7 @@ export default function LoginScreen() { if (confirmSent) { return ( -
+

Check your email

@@ -91,7 +91,7 @@ export default function LoginScreen() { } return ( -

+

Diagrav

{mode === 'sign-in' ? 'Sign in' : 'Create an account'}

diff --git a/src/components/canvas/FlowCanvas.tsx b/src/components/canvas/FlowCanvas.tsx index 370e416..36adcca 100644 --- a/src/components/canvas/FlowCanvas.tsx +++ b/src/components/canvas/FlowCanvas.tsx @@ -26,6 +26,8 @@ import { } from '../../domain/diagram' import { useCatalogStore } from '../../state/catalogStore' import { useDiagramStore } from '../../state/diagramStore' +import { useCanEdit } from '../../hooks/useCanEdit' +import { useIsMobile } from '../../hooks/useIsMobile' import CableEdge, { type CableFlowEdge } from './CableEdge' import DeviceNode, { type DeviceFlowNode, type PortInfo } from './DeviceNode' import { buildGroupHandleId, buildHandleId, parsePortId } from './handleIds' @@ -65,13 +67,11 @@ function FlowCanvasInner() { const selectConnection = useDiagramStore((s) => s.selectConnection) const { screenToFlowPosition } = useReactFlow() const catalog = useCatalogStore((s) => s.catalog) - const access = useDiagramStore((s) => s.access) // Owner/edit-collaborator/(a Super Admin, who getAccess always reports as - // 'edit') can edit; a view-only collaborator can look but not touch — - // organized-ideas.md §8's per-collaborator permissions. `access` is only - // null in the brief window before the first diagram finishes loading, at - // which point nothing is rendered yet anyway (AppShell's isLoaded gate). - const canEdit = access?.myPermission !== 'view' + // 'edit') can edit; a view-only collaborator, or anyone on a mobile-width + // viewport, can look but not touch — see useCanEdit. + const canEdit = useCanEdit() + const isMobile = useIsMobile() // Per-device compact/expanded toggle (organized-ideas.md §4) — purely a // canvas display preference, so it's local component state rather than @@ -398,8 +398,17 @@ function FlowCanvasInner() { colorMode="light" > - - + {/* showInteractive is React Flow's own lock-toggle button, and it's + misleading whenever canEdit is already false (shared view-only, + or mobile) — dragging/connecting are gated by nodesDraggable/ + nodesConnectable above regardless of this button's state, so + "unlocking" it wouldn't actually grant editing, just click-to- + select. Hiding it avoids implying otherwise. */} + + {/* Takes a meaningful chunk of a phone-sized viewport for not much + payoff — pan/pinch-zoom plus fitView already gets you oriented + without it. */} + {!isMobile && } + + + ) +} diff --git a/src/components/layout/RightPanel.tsx b/src/components/layout/RightPanel.tsx index c5ed399..dfc4ea3 100644 --- a/src/components/layout/RightPanel.tsx +++ b/src/components/layout/RightPanel.tsx @@ -12,7 +12,7 @@ export default function RightPanel() { const [tab, setTab] = useState('inspector') return ( -