fix(studio): improve font picker and text property controls#736
Conversation
- Line height and letter-spacing: convert from free-text to select with presets - Font style: remove oblique (browser falls back to italic), keep normal/italic - Font weight: detect available weights via document.fonts.check(), add labels - Font source: local fonts matching Google catalog tagged as Google - Font list: balanced per-source caps prevent any source from being cut off - Sort order: Google fonts rank before Local so curated fonts appear first Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
jrusso1020
left a comment
There was a problem hiding this comment.
Verdict: APPROVE — six small fixes, all coherent
Read the full PropertyPanel.tsx (~3000 LoC) end-to-end for the relevant pieces. All six PR-description claims verified against source.
Backward-compat check — the only thing that could regress
My main concern walking in was: converting free-text MetricField → preset-only SelectField for line-height / letter-spacing, plus dropping oblique from font-style, could leave existing compositions displaying wrong values in the picker.
Confirmed clean — SelectField at PropertyPanel.tsx:2177 defensively prepends out-of-list values:
const renderedOptions = value && !options.includes(value) ? [value, ...options] : options;Same pattern in the new FontWeightField (displayOptions). So:
- Saved
letter-spacing: 0.07em→ shows0.07emin the dropdown, user can re-select it. - Saved
font-style: oblique→ still picker-displayable, even thoughobliqueis removed from the new options list. - Saved
font-weight: 350(rare, but valid) → preserved.
The free-text → preset trade-off does mean users lose the ability to re-enter a non-preset value once committed — they're snapped to the nearest preset on the next edit. Intentional per the PR description; flagging for awareness, not a blocker.
detectAvailableWeights semantics
Worth knowing: document.fonts.check("400 16px \"Roboto\"") returns true for any weight the browser will render — including via faux-bold / faux-italic synthesis. So for a system font like Arial (which only ships 400 + 700 as real glyphs), check() will report all 9 weights as available. The dropdown then offers all 9 weights even though only two are real.
In practice this is fine — synthesis produces acceptable output for most users. But the PR description's "filters dropdown to supported weights" is slightly more precise than what's delivered ("filters to renderable weights, including synthesized"). Non-blocking observation.
Also: fonts.check() returns false for unloaded fonts. If a user has Roboto selected but the stylesheet hasn't injected yet, the picker shows all 9 weights via the available.length > 0 ? available : ALL_WEIGHTS fallback, then re-renders to the filtered list once the font loads. Brief flicker possible; tolerable.
taggedLocalFonts interaction with the existing dedup
uniqueFontOptions at 754 dedupes by family name (lowercase), first-write-wins. The new options array orders Google catalog before taggedLocalFonts, so when a font is in both googleFonts AND localFonts, the Google entry wins regardless of the tag.
The tagging logic in taggedLocalFonts is therefore load-bearing only when a font is in localFonts AND in the Google catalog but not in the googleFonts array passed as prop. If googleFonts is the full curated catalog, the tagging is redundant. If googleFonts is a subset, the tagging correctly surfaces Google-catalog-matched locals as "Google". Either way, no bug — just worth knowing the tagging is sometimes redundant.
Per-source caps in filteredOptions
Verified: 100 Google / 80 Local / unlimited System + Current + Document + Imported. Sensible — prevents Google or Local from monopolizing the dropdown.
Note: the cap only applies to the unfiltered list. When the user types a query, all matches up to 200 are returned without per-source balancing. Acceptable because search is typically narrow.
font-style removed oblique value
The PR description says "falls back to italic in most fonts." Technically, CSS font-style: oblique and italic are distinct — oblique is mechanically slanted glyphs, italic is true italic glyph variants. For most variable/web fonts, the browser DOES fall back oblique → italic if no oblique variant exists. The simplification is reasonable for a property panel; advanced users can still set oblique via CSS directly, and the SelectField prepend preserves it on display.
Praise
- Sticky-value handling via
displayOptions/renderedOptionsis the right primitive for both Weight and Style. Without it, the dropdown changes would have been disruptive for existing content. detectAvailableWeightscorrectly handles quoted/unquoted family names and the multi-familyfont-familydeclaration viasplit(",")[0]?.trim().replace(/['"]/g, "").- Per-source caps are a clean fix for the "Google fonts pushed off by Local" usability bug — much better than a single hard 160-item cap.
- Sort-rank swap (Google before Local) matches user expectation since Google fonts are the more recognizable / curated set.
mergeable_state: "unstable" — some non-required checks failing
Worth a glance at CI before merge, but doesn't block on its own.
Review by Rames Jusso (pr-review)
miguel-heygen
left a comment
There was a problem hiding this comment.
Clean, well-scoped PR. Six fixes in one file, all backward-compatible.
Verified the key risk — SelectField defensively prepends out-of-list values (:2177), so existing compositions with arbitrary letter-spacing: 0.07em or font-weight: 350 still display correctly in the picker.
Two minor observations (non-blocking):
-
detectAvailableWeightsruns 9document.fonts.check()calls on every render ofFontWeightField. Fast in practice, but if we ever notice jank in the property panel, memoizing onfontFamilywould be the first thing to try. -
Line-height/letter-spacing presets:
0px(legacy MetricField default) and0emboth appear in the tracking list — functionally identical. Tiny UX papercut, not worth blocking on.
CI green, targets next. Good to merge.
— Magi
Summary
Six improvements to Studio's text property panel and font picker.
document.fonts.check()for the current font family, filters dropdown to supported values, adds human-readable labels (Thin, Light, Regular, Bold, etc.)Test plan
🤖 Generated with Claude Code