From ece9e67b67b4fabaa71fc2f24148ab2bf8f74d87 Mon Sep 17 00:00:00 2001 From: Guilhem Lemouel Date: Tue, 6 Oct 2026 10:26:32 -0700 Subject: [PATCH] fix: the badge solves its lightness against the colour the browser paints MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three nits from the review round. The contrast bisection measured the hue at full float precision, but a browser paints 8-bit channels: rounding them moved the result up to 0.025 off the target, which took #993388 to 4.478 against white — under the AA floor the comment claims. Quantizing the channels inside `paintedLuminance` puts the worst case across 2880 hue and saturation combinations at 4.500, and your own fork's badge measures 4.53 as painted. An agent nobody can write still offered the rename behind its path segment: an editor with two read-only fields, in place of the click that copies the path. It is gated on `readOnly` now, the way a detail page is gated on `onSaved`. The script and flow editors take the same gate from the `customUi` flags that already decide whether their summary offers one — unreachable today, since the editors own the band only on routes that pass no `customUi`, but the four editors now answer the question the same way. And the registry's doc named an `onEditName` field that has never existed. Co-Authored-By: Claude Opus 5 (1M context) --- frontend/src/lib/components/FlowBuilder.svelte | 8 +++++++- frontend/src/lib/components/ScriptBuilder.svelte | 7 ++++++- .../lib/components/flows/content/AgentEditorModal.svelte | 4 +++- frontend/src/lib/components/pageHeaderRegistry.svelte.ts | 3 ++- frontend/src/lib/utils/forkColor.ts | 9 ++++++--- 5 files changed, 24 insertions(+), 7 deletions(-) diff --git a/frontend/src/lib/components/FlowBuilder.svelte b/frontend/src/lib/components/FlowBuilder.svelte index 7f3e96de0d..57cd8b2953 100644 --- a/frontend/src/lib/components/FlowBuilder.svelte +++ b/frontend/src/lib/components/FlowBuilder.svelte @@ -1582,7 +1582,13 @@ kind: 'flow', path: pathSnapshot ?? $pathStore, summaryContent: flowSummary, - pathTrigger: flowPathTrigger + // Gated like the summary's own editor: a host that allows neither field keeps the + // band's segment, which copies the path. + pathTrigger: + customUi?.topBar?.editablePath != false || + customUi?.topBar?.editableSummary != false + ? flowPathTrigger + : undefined }} actions={flowHeaderActions} contexts={headerContexts} diff --git a/frontend/src/lib/components/ScriptBuilder.svelte b/frontend/src/lib/components/ScriptBuilder.svelte index ad57b192bb..ad869ed492 100644 --- a/frontend/src/lib/components/ScriptBuilder.svelte +++ b/frontend/src/lib/components/ScriptBuilder.svelte @@ -2299,7 +2299,12 @@ // invalidates the band before this component is torn down. path: pathSnapshot ?? script?.path, summaryContent: scriptSummary, - pathTrigger: scriptPathTrigger + // Gated like the summary's own editor: a host that allows neither field keeps the + // band's segment, which copies the path. + pathTrigger: + customUi?.topBar?.editablePath != false || customUi?.topBar?.editableSummary != false + ? scriptPathTrigger + : undefined }} afterName={scriptMarks} actions={scriptHeaderActions} diff --git a/frontend/src/lib/components/flows/content/AgentEditorModal.svelte b/frontend/src/lib/components/flows/content/AgentEditorModal.svelte index 08e1906708..0280455e77 100644 --- a/frontend/src/lib/components/flows/content/AgentEditorModal.svelte +++ b/frontend/src/lib/components/flows/content/AgentEditorModal.svelte @@ -319,7 +319,9 @@ item={{ path: pathSnapshot ?? shownPath, summaryContent: agentSummary, - pathTrigger: agentPathTrigger + // Only where the rename behind it can be saved: a reader who cannot write this agent + // keeps the segment that copies the path, rather than an editor with nothing to edit. + pathTrigger: readOnly ? undefined : agentPathTrigger }} afterName={agentHint} actions={settings} diff --git a/frontend/src/lib/components/pageHeaderRegistry.svelte.ts b/frontend/src/lib/components/pageHeaderRegistry.svelte.ts index e499c77025..a68cda5401 100644 --- a/frontend/src/lib/components/pageHeaderRegistry.svelte.ts +++ b/frontend/src/lib/components/pageHeaderRegistry.svelte.ts @@ -6,7 +6,8 @@ export type PageHeaderItem = { /** Absent for an item the picker does not file under a kind — an agent's detail page. The * breadcrumb then names the item without claiming it is one of the three. */ kind?: WorkspaceItemKind - /** Shown whole. Clicking it opens `onEditName` where a page offers one, and copies otherwise. */ + /** Shown whole. Clicking it opens whatever the page hangs off it with `pathTrigger`, and copies + * the path where there is none. */ path: string | undefined /** Wraps the band's own path segment in whatever the page wants behind it — its rename * popover, anchored there rather than beside the summary, so the editor opens under the half diff --git a/frontend/src/lib/utils/forkColor.ts b/frontend/src/lib/utils/forkColor.ts index ca44541696..eb9af17b32 100644 --- a/frontend/src/lib/utils/forkColor.ts +++ b/frontend/src/lib/utils/forkColor.ts @@ -78,11 +78,14 @@ function contrast(a: number, b: number): number { return (Math.max(a, b) + 0.05) / (Math.min(a, b) + 0.05) } -/** Luminance of what `hslFloorL` paints, not of the exact numbers: a percent of lightness is - * worth more than the margin being solved for. */ +/** Luminance of what the browser paints, which is neither the exact numbers nor the `hsl()` string + * alone: the string carries whole percents, and the channels it resolves to are 8-bit. Solving + * against the unrounded channels leaves the chosen lightness up to 0.025 short of the target once + * painted, which is enough to miss AA on a hue like #993388. */ function paintedLuminance(h: number, s: number, l: number): number { const [r, g, b] = hslToRgb(Math.round(h), Math.round(s * 100) / 100, Math.floor(l * 100) / 100) - return relativeLuminance(r, g, b) + const q = (x: number) => Math.round(x * 255) / 255 + return relativeLuminance(q(r), q(g), q(b)) } /**