Uh oh!
There was an error while loading. Please reload this page.
feat: switch to Google Sans and Google Sans Code via google_fonts - #29
Conversation
rahulbisht25
commented
Aug 25, 2026
@cursoragent review |
1 similar comment
rahulbisht25
commented
Aug 25, 2026
@cursoragent review |
Based on my review of the changes, here's my assessment: Code Review SummaryOverviewThis PR cleanly replaces Figtree and Geist Mono with Google Sans and Google Sans Code as the design system's typefaces. The implementation is well-structured and follows the existing patterns. StrengthsCentralized font declarations — The font family constants ( Proper licensing — Both Complete documentation — CHANGELOG documents the breaking change for hosts referencing Clean pubspec comment — The rationale for static instances vs variable fonts is documented inline. ObservationsFile size (noted in PR) — The four Google Sans files total ~7.6MB vs Figtree's ~280KB. The five Google Sans Code files add ~280KB. The subsetting recommendation in the PR notes is worth addressing before release — Playground coupling — fontFamily:'GoogleSans',
package:'flow_ui',This works but creates implicit coupling. Consider importing and using the constants from the library if they're ever exposed, or adding a comment noting this matches the library's bundled face. VerdictThe implementation is solid. The only material decision before shipping is whether to subset the Google Sans files to Latin to avoid the ~7MB payload for every host app. That's a product/release decision rather than a code issue. Approved — ready to merge once the subsetting decision is made. |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 8ce3aae. Configure here.
Uh oh!
There was an error while loading. Please reload this page.
A style whose fontFamily a host replaced via merge or copyWith still carries google_fonts' fallback, so matching on fontFamilyFallback re-cut it in Google Sans; matching the `<family>_<variant>` name lets it pass through. Also tightens the typeface comments and drops the removed fonts/ from CLAUDE.md.
rahulbisht25
commented
Aug 25, 2026
@cursoragent review |
I've reviewed the updated PR. The implementation has been significantly refactored to use Code Review SummaryOverviewThe PR now uses StrengthsSignificant size reduction — Package goes from ~8MB (bundled fonts) to ~383KB compressed. Fonts are fetched and cached on first use. Clean architecture for non-const typography — finalFlowTypography? _typography;
/// The type scale — [FlowTypography.standard] unless the host set one. /// Resolved on read, since the standard scale isn't `const`.FlowTypographyget typography => _typography ??FlowTypography.standard;
final family = style.fontFamily ??'';
if (family.startsWith('${_fontFamily}_')) {
returnGoogleFonts.googleSans(
textStyle: style,
fontWeight: fontWeight,
fontStyle: fontStyle,
);
}Comprehensive usage of Documentation updated — CHANGELOG, README, and CLAUDE.md all document the change to ObservationsRuntime dependency trade-off — First-launch offline shows platform fonts until google_fonts caches the cuts. The README documents the One added dependency — VerdictThe implementation is solid and the Bugbot-reported issue has been addressed. The trade-off between bundled fonts (~8MB payload for every host) versus runtime-fetched fonts (offline-first requires asset workaround) is documented and reasonable. Approved — ready to merge. |
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Its macOS entitlements lacked network.client and its main Android manifest INTERNET, so sandboxed macOS and release Android builds fell back to the platform face.
Uh oh!
There was an error while loading. Please reload this page.
The site loads Google Sans from Google Fonts since #29, which deleted the script that copied these out of the package but not the copies it had already made. Nothing references them; the OFL goes with them.
…#31) * feat: built-in attachment input with picker, web drag-and-drop, and paste The composer picks its own files now: onAttachmentsPicked opens the platform dialog through file_selector (flutter.dev-published, no host configuration except the macOS file-read entitlement), and hands back FlowAttachments already read, decoded, and carrying their bytes and MIME type for upload. FlowChatView.onAttachmentsDropped and FlowComposer.onAttachmentsDropped take web drag-and-drop at two scopes, with a frosted gradient invitation on the surface and a lit card on the composer; onAttachmentsPasted takes Ctrl+V images. All three decode through FlowAttachmentOptions and report refusals through onAttachmentRejected. onAttach stays as the host-picks escape hatch and dropActive as the desktop override, since drop and paste have no SDK path off the web. Closes#25 * feat: image parts with a generating shimmer state FlowImagePart renders an AI-generated picture as large-format content, unlike FlowAttachmentPart's tiles: a null image draws a shimmering block at the part's aspect ratio until the host re-renders with the provider set, and tapping the landed picture opens the full-screen preview. The sweep animation moves out of FlowShimmerText into FlowShimmerSweep so both surfaces share it. Adding a subtype to the sealed FlowMessagePart is source-breaking for exhaustive switches downstream; called out in the changelog. * fix: judge UTI accept groups and give a single slot to the first passing file A group naming only uniform type identifiers used to defer to blanket acceptance, which let any file through an accept list that also had checkable groups. UTIs are now judged through a root-type table (public.image, public.movie, com.adobe.pdf and the like), and deferral is left only for identifiers the table does not know. With allowMultiple false the intake took the first file offered, so a refused one ahead of an acceptable one consumed the slot and, on paste, suppressed the browser's own paste with nothing attached. The slot now goes to the first file that passes, with the refused ones still reported. * feat: lift a user turn's images out of the bubble into tiles above it Attachments in a user turn now sit above the bubble in a row from the trailing edge, each image a 116 square tile, cover-cropped, under an outlineVariant hairline that strengthens to outline on hover; files keep their pill tiles. A picture with no caption draws no bubble at all, and assistant turns are unchanged. FlowMessageStyle gains attachmentCardColor, attachmentCardBorderColor and attachmentCardHoverBorderColor. The playground's Message stage gains a With image variant. * feat: close the attachment preview on a tap outside the picture Two tap targets, one inside the other: a tap on the picture is absorbed and a tap anywhere else on the frosted page closes, as the close button and Escape already did. The image is centred inside the viewer so its box is the painted picture rather than the whole viewport, which is what makes the two places distinct. * feat: generate pictures with Gemini's image model in the example streamReply yields typed deltas, text fragments or a whole picture, and asks for image output only on the image models, since a text model refuses the request. The screen offers Gemini 3.1 Flash Image in the selector and renders the reply through FlowImagePart: a generating block from the first delta, the picture at its measured aspect ratio once the bytes land, the slot dropped if the model answers in words alone. * fix: address review findings on the attachment and image work - public.audiovisual-content is Apple's parent for audio as well as video, so the UTI table maps each identifier to a list of MIME prefixes and that one accepts both; an audio file dropped against a group naming it was refused as unsupportedType. - A lifted sent image drew previewImage, which for the package's own attachments is bounded for the full-screen viewer, so a 116 tile decoded the picture at 2048. It now draws the plain thumbnail wrapped at tile resolution, with the same guard the attachment tiles use. - A disabled composer now refuses drops as it already refused picks, pastes and removals; before, a drag still lit the card and delivered. - FlowImagePart gains bytes and mimeType, the pair FlowAttachment carries. Without them the example dropped generated pictures from the history: a follow-up turn sent only the text, and an image-only turn was skipped entirely. - A measurement landing after the stream failed replaced the turn's parts and dropped the error card's message; it now stands down once the turn has failed. - The install docs claimed nothing outside flutter.dev and no Android permission. google_fonts is a dependency and its fetch needs INTERNET and macOS network.client on every app. * refactor: rest a sent image tile on outline, softening to outlineVariant on hover The rest ink now matches the pending strip's tiles, so a picture keeps the same hairline before and after it is sent. * feat: open the file picker from any control, and fix the attachment rough edges showFlowAttachmentPicker is public: the function behind the composer's attach button, callable from a host's own menu. It never throws — a dialog that cannot open is reported as unreadable under an empty name — and must be called synchronously from the gesture on the web. The playground and the example route 'Add Files or Photos' through it and drop the separate paperclip; the paperclip API stays. Picked, dropped and pasted images now carry their type as kind, so the tile draws the JPG pill the design shows. The composer's pending strip scrolls its tiles under the card's inset, so an overflowing strip cuts the last tile at the edge as the cue that there is more — no counter. The preview's close button sits on an opaque surfaceBright disc with an outlineVariant hairline and the theme's shadow, and grows to 44 on touch platforms; the translucent wash it had vanished over a dark photo in the light theme and a light one in the dark. Refused files are announced by a host-side notice that fades after four seconds, in the playground and the example; the composer stage gets one too. The pinned drop treatment moves from Full Chat to a third Attachments variant, with the docs embed following. * chore: drop the Figtree files the docs site no longer serves The site loads Google Sans from Google Fonts since #29, which deleted the script that copied these out of the package but not the copies it had already made. Nothing references them; the OFL goes with them. * feat: composer error banner, and the drop and preview frost from the design FlowComposer.errorMessage raises the design's tab above the card: the error wash with a warning glyph and the host's line, left-aligned, wrapping when long, growing in from the card's edge, and staying until its cross (onErrorDismiss) or the host clears it. errorIcon swaps the drawn glyph; FlowComposerStyle.errorBackgroundColor and errorForegroundColor recolour it. The playground and the example use it for refused files in place of a host-side notice that faded on a timer. The drop treatment takes the design's values: a vertical wash from surfaceBright at 40% to surface at 80% over a 12 blur, replacing the 72-94% surface frost; dropBlurSigma goes with it, unreleased and with nothing left to control. The attachment preview's backdrop adopts the same frost so the two read as one. Picked, dropped and pasted images no longer carry a kind pill; files without a picture keep theirs. The example seeds an image model's turn with the generating block so the shimmer shows before the bytes land, rather than the thinking indicator. * feat: attachmentsEnabled, to turn every way in off without unwiring it Presence of a callback says a way in is wired; this flag on FlowComposer and FlowChatView says it is available — off for a plan tier, a model that takes no images, a thread that has closed. False stops the attach button, both drops, paste and keyboard media at once, leaves the handlers in place, and keeps pending attachments in the strip and removable, since they are the host's state. * fix: a drop target that is off stays registered and swallows the drop attachmentsEnabled false, and the composer's enabled false, passed a null onDropped into FlowDropTarget, which unregistered the web listener — the only thing calling preventDefault inside the surface. A file dropped on a chat that had merely switched attachments off then let the browser navigate the tab to it. The target now takes an enabled flag of its own: off keeps the zone registered, raises no treatment, delivers nothing, and shows the browser's refusing cursor instead of promising a copy. * fix: a switched-off drop target yields to an enabled one, and light error ink clears AA _targetAt picked the smallest rectangle under the pointer regardless of whether it was on, so a composer with attachments off shadowed a chat view that still took drops: refusing cursor, no treatment, nothing delivered. It now prefers the innermost enabled target and falls back to a disabled one only where nothing enabled contains the point — there, claiming the drop to swallow it still beats the browser navigating. The light preset's onErrorContainer is #B54141, a darker cut of the error accent: #C14A4A on its own 6% wash over surface came to 4.2:1, under WCAG AA at label sizes, so the composer's error banner and a failed user bubble were hard to read. The new ink clears 4.8:1 and still reads as the same red; the dark preset was already clear. * revert: keep the light error ink at the accent Puts onErrorContainer back to #C14A4A in the light preset, as the retune set it, and drops the changelog and CLAUDE.md notes that described the darker cut. The banner's 4.2:1 on its wash stands as a palette decision rather than a code fix.



Summary
Google Sans becomes the prose face and Google Sans Code the code face, replacing Figtree and Geist Mono across the package, the playground, and the docs site. Neither is bundled: the package now depends on
google_fonts, which fetches each cut from Google Fonts on first use and caches it on the device.Package
pubspec.yamldrops theflutter: fonts:block and thefonts/directory; addsgoogle_fonts ^8.2.1— the one dependency outside flutter.dev's own packages (CLAUDE.md's constraint updated to record the exception).FlowTypographybuilds every role throughGoogleFonts.googleSans/googleSansCode. Those calls aren'tconst, soFlowTypography.standardis now astatic final;FlowThemekeeps itsconstconstructor by resolvingtypographythrough a getter. The public surface (typography:parameter,context.flowTypography) is unchanged.FlowTypography.recut(style, fontWeight:, fontStyle:). google_fonts registers each weight as its own font family, socopyWith(fontWeight:)on a token would have the engine fake the weight;recutresolves the real cut. The five in-library sites that bolded or italicised tokens (markdown emphasis, error-state titles, the attachment type pill, the bold-text accessibility merge) go through it. Styles in any other face pass throughcopyWithuntouched.Playground — the chrome's
shellTextusesGoogleFonts.googleSansdirectly.Docs site — the font sync script and its
predev/prebuildhooks are removed;astro.config.mjsloads Google Sans via a Google Fonts<link>, and the@font-faceblocks intheme.cssare gone.README, CHANGELOG (a Typefaces entry under 0.3.0) and the mdx pages updated. The
generated_plugins.cmakefiles and lockfiles inexample/andplayground/changed becausepath_providerarrives transitively.Notes for review
FigtreeorGeistMonoby family name, and hosts thatcopyWith(fontWeight:)on standard tokens get faux bold until they switch torecut— both called out in the changelog.google_fonts/asset folder, which the package checks before the network (documented in the README).pub get.Testing
flutter analyzeclean at the root, inplayground/and inexample/;dart formatclean tree-wide.fonts.gstatic.com(HTTP 200), no console errors, prose in Google Sans and the code panel in Google Sans Code — including the w500/w700 chrome cuts.dart pub publish --dry-runvalidates the archive (383 KB compressed, down from ~8 MB with the font files).Note
Medium Risk
Adds a third-party dependency and changes default rendering to network-fetched fonts, with breaking typography API behavior for hosts that bold tokens via
copyWith; offline-first apps must bundle assets or accept platform fallbacks.Overview
Replaces bundled Figtree and Geist Mono with Google Sans and Google Sans Code loaded through a new
google_fontsdependency. Theflutter: fonts:block and shipped font assets are removed, which shrinks the published package and makes runtime network access (or a host-providedgoogle_fonts/asset folder) required for the design faces on first launch.FlowTypography.standardis now astatic finalbuilt withGoogleFonts.googleSans/googleSansCode;FlowThemekeeps aconstconstructor by resolving typography through a getter.FlowTypography.recutis added so bold/italic use real font cuts instead ofcopyWith(fontWeight:)(which would faux-bold with google_fonts’ per-weight families); in-library markdown, error state, attachment pills, and accessibility bolding call it.Docs and playground follow the same branding: Astro loads Google Sans via a stylesheet link (font sync script removed), playground chrome uses
GoogleFonts.googleSans, and AndroidINTERNET/ macOSnetwork.clientare documented and wired in the playground for release fetches.Breaking for hosts: references to
Figtree/GeistMonoby family name, andcopyWith(fontWeight:)on standard typography tokens, should move torecutor custom faces.Reviewed by Cursor Bugbot for commit fd7d2ae. Bugbot is set up for automated code reviews on this repo. Configure here.