From 75686502e3914c131b0d26138048f9451f5e69b8 Mon Sep 17 00:00:00 2001 From: Colby McHenry Date: Sat, 29 Aug 2026 13:53:50 -0500 Subject: [PATCH] feat(steps): an early exit reads as a guard clause, not as a branch MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A fork with nothing on one side is `if (!user) return` — a reader takes it as a guard, not as a decision with two sides. Drawn as a branch it costs a column and a step right, and a handler with four guards (every server handler) read as four nested branches with three-quarters of the width holding the words "returns here". It is now one line — the condition and where the code leaves — with everything below it running because it did not, and the rail stays on its own hairline. next-saas-starter's `signIn` goes from not fitting the screen to fitting it. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01REFyW9hmNrxhwN5wxRoAkC --- docs/design/codegraph-ui-design-spec.md | 7 ++- ui/src/components/steps/RailBlock.svelte | 58 ++++++++++++++++++------ 2 files changed, 50 insertions(+), 15 deletions(-) diff --git a/docs/design/codegraph-ui-design-spec.md b/docs/design/codegraph-ui-design-spec.md index 7baaa90..e07145f 100644 --- a/docs/design/codegraph-ui-design-spec.md +++ b/docs/design/codegraph-ui-design-spec.md @@ -623,8 +623,11 @@ layout engine, a column of boxes with a hairline down its left and a fork as a r `StepBox.svelte`, the canvas's box exactly (the canvas wraps it in handles; the rail lets it size to its words), and so are the click, the double-click-to-start-here and the panel. The fork's head says the decision once and its arms say only which side they are — **WHEN** / **WHEN NOT** — except a `switch`, whose arms each have a case to say, and a -`try`, which says `on error` once. `StepsKey.svelte` is the key: floating over the canvas, last in the document on the -rail, which scrolls and cannot have things sitting on it. +`try`, which says `on error` once. **An early exit is drawn as a guard clause, not as a branch** — a fork with nothing on +one side (`if (!user) return`) is one line, the condition and where the code leaves (`userWithTeam.length === 0 +· returns here`), with everything below it running because it did not; a rail of guard clauses would otherwise step +right once per guard and a handler with four of them would read as four nested branches. `StepsKey.svelte` is the key: +floating over the canvas, last in the document on the rail, which scrolls and cannot have things sitting on it. **Which reading opens** travels in the URL (`&view=order` / `&view=tree`) and the summary offers both; without one the answer's own `defaultView` decides — the code's order for a handler, an endpoint or any function, the tree for a diff --git a/ui/src/components/steps/RailBlock.svelte b/ui/src/components/steps/RailBlock.svelte index ae58e34..01bd599 100644 --- a/ui/src/components/steps/RailBlock.svelte +++ b/ui/src/components/steps/RailBlock.svelte @@ -58,20 +58,38 @@ {/if} {:else if item.kind === 'fork'} -
-
{@render words(item.words)}
-
- {#each item.arms as arm, a (a)} -
-
{@render words(arm.words)}
- {#if arm.body.length > 0} - - {/if} - {#if arm.ends}
{arm.ends}
{/if} -
- {/each} + {@const guard = item.arms.length <= 2 && item.arms[0]?.body.length === 0 && item.arms[0]?.ends !== null} + {#if guard} + +
+ {@render words(item.words)} + {item.arms[0]!.ends}
-
+ {#if item.arms[1] && item.arms[1].body.length > 0} + + {/if} + {#if item.arms[1]?.ends}
{item.arms[1].ends}
{/if} + {:else} +
+
{@render words(item.words)}
+
+ {#each item.arms as arm, a (a)} +
+
{@render words(arm.words)}
+ {#if arm.body.length > 0} + + {/if} + {#if arm.ends}
{arm.ends}
{/if} +
+ {/each} +
+
+ {/if} {:else if item.kind === 'group'}
@@ -178,6 +196,20 @@ padding-top: 4px; align-self: stretch; } + /* An early exit: the condition and where it leaves, on one line. */ + .guard { + display: flex; + align-items: baseline; + gap: 8px; + max-width: 100%; + min-width: 0; + } + .ends.inline { + border-top: 0; + padding-top: 0; + align-self: auto; + white-space: nowrap; + } .kw { font-weight: 600; }