From d61693529635cbd9319a93cc3122ed48e1448b74 Mon Sep 17 00:00:00 2001 From: Alan Wizemann Date: Thu, 23 Apr 2026 21:43:36 +0200 Subject: [PATCH] fix(config-sheet): wrap wide schema descriptions instead of clipping MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Configuration sheet rendered field labels chopped on the left and description URLs spilling off the right whenever a schema description contained a raw `https://…` URL. Root cause is layout: SwiftUI's inline-markdown renderer turns the URL into an unbreakable AttributedString link token, and without an explicit maxWidth constraint on the sheet's inner VStack, width resolution went bottom-up — the description's ideal width became the URL's character length, the VStack matched it, the ScrollView's content exceeded the sheet's `.frame(minWidth: 560)` viewport, the window clipped the grown sheet, and the center-aligned result cut off both sides. Added `.frame(maxWidth: .infinity, alignment: .leading)` in two places: - TemplateConfigSheet's inner VStack inside the ScrollView + the fieldRow VStack. - TemplateInstallSheet's main-preview VStack inside its ScrollView — same pattern, same failure mode for raw URLs in cron prompts or README blocks (the disclosure-group inner ScrollViews already had the modifier). With the constraint, the description's `.fixedSize(horizontal: false, vertical: true)` wraps at whitespace boundaries as intended. The URL stays on its own line, still clickable, still showing the full href. Long paths and other unbreakable tokens render the same way. Found while rendering a user-authored schema with two raw URLs in descriptions. SKILL.md gets a paired update (separate commit) teaching authors to prefer `[link text](https://…)` markdown syntax so the visible description stays short even when the href is long. 58/58 Swift tests pass. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../Templates/Views/TemplateConfigSheet.swift | 27 ++++++++++++++++++- .../Views/TemplateInstallSheet.swift | 11 ++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/scarf/scarf/Features/Templates/Views/TemplateConfigSheet.swift b/scarf/scarf/Features/Templates/Views/TemplateConfigSheet.swift index 81700ef..c9d4875 100644 --- a/scarf/scarf/Features/Templates/Views/TemplateConfigSheet.swift +++ b/scarf/scarf/Features/Templates/Views/TemplateConfigSheet.swift @@ -23,6 +23,20 @@ struct TemplateConfigSheet: View { header Divider() ScrollView { + // `.frame(maxWidth: .infinity, alignment: .leading)` is + // load-bearing: without it, SwiftUI resolves width + // bottom-up and an unbreakable token in a child (e.g. a + // raw URL inside a field description rendered via + // AttributedString markdown) sets the whole VStack's + // ideal width to that token's length. ScrollView's + // content then exceeds the sheet's viewport, the outer + // `.frame(minWidth: 560)` grows to content width, and + // the window clips the result with labels cut off on + // the left + URL spilling off the right. With the + // explicit maxWidth, the ScrollView's offered width + // propagates down and the description Text's + // `.fixedSize(horizontal: false, vertical: true)` + // wraps at whitespace boundaries as intended. VStack(alignment: .leading, spacing: 18) { if viewModel.schema.fields.isEmpty { ContentUnavailableView( @@ -40,6 +54,7 @@ struct TemplateConfigSheet: View { modelRecommendation(rec) } } + .frame(maxWidth: .infinity, alignment: .leading) .padding(20) } Divider() @@ -116,7 +131,11 @@ struct TemplateConfigSheet: View { // Inline markdown so descriptions can include // `[Create one](https://…)`-style links to token // generation pages, **bold** emphasis on important - // prerequisites, etc. + // prerequisites, etc. Raw URLs (not wrapped in + // markdown link syntax) will still render but can't + // word-break mid-token — keep the parent maxWidth + // constraint below so a rogue raw URL wraps cleanly + // instead of expanding the entire sheet. TemplateMarkdown.inlineText(description) .font(.caption) .foregroundStyle(.secondary) @@ -129,6 +148,12 @@ struct TemplateConfigSheet: View { .foregroundStyle(.red) } } + // maxWidth: .infinity forces this row to span the column's + // full width so its internal description Text wraps instead + // of expanding the outer VStack when a description contains + // a long unbreakable token (raw URL, path, etc.). See the + // comment on the parent ScrollView's inner VStack. + .frame(maxWidth: .infinity, alignment: .leading) .padding(12) .background( RoundedRectangle(cornerRadius: 8) diff --git a/scarf/scarf/Features/Templates/Views/TemplateInstallSheet.swift b/scarf/scarf/Features/Templates/Views/TemplateInstallSheet.swift index e697050..c1e01c1 100644 --- a/scarf/scarf/Features/Templates/Views/TemplateInstallSheet.swift +++ b/scarf/scarf/Features/Templates/Views/TemplateInstallSheet.swift @@ -126,6 +126,16 @@ struct TemplateInstallSheet: View { .padding(.bottom, 8) Divider() ScrollView { + // `.frame(maxWidth: .infinity, alignment: .leading)` — + // without it, a subsection containing an unbreakable + // token (raw URL in a cron prompt or README block, a + // long file path in the project-files list, a schema + // description with a bare URL, etc.) sets the VStack's + // ideal width to that token's length; the sheet grows + // past its `.frame(minWidth: 620)` and gets clipped by + // the window. Same fix as `TemplateConfigSheet`'s + // inner VStack — propagate the ScrollView's width down + // so inner Text wraps instead of expanding outward. VStack(alignment: .leading, spacing: 16) { projectFilesSection(plan: plan) if plan.skillsNamespaceDir != nil { @@ -142,6 +152,7 @@ struct TemplateInstallSheet: View { } readmeSection } + .frame(maxWidth: .infinity, alignment: .leading) .padding(.vertical) } Divider()