Việc chuyển sang Material 3 trong Flutter tạo ra một mô-típ rất dễ đoán. Đội ngũ gọi ColorScheme.fromSeed(seedColor: brandPurple) , nhìn kết quả, kết luận “đây không phải màu thương hiệu của mình”, rồi quay lại truyền màu tường minh cho từng widget. Sáu tháng sau, ứng dụng có hai trăm giá trị hex cứng, dark mode là một danh sách hai trăm giá trị khác, và đổi màu thương hiệu tốn cả tuần. Thứ bị bỏ qua chính là ý tưởng mà Material 3 được xây dựng trên đó: bạn không tô widget bằng màu, bạn gán cho nó một vai trò . Một FilledButton không có nền màu tím — nó có nền primary với nội dung onPrimary . Khi điều đó đúng trên toàn ứng dụng, bảng màu trở thành một object duy nhất mà bạn có thể thay. Các vai trò và mục đích của từng cái ColorScheme có khoảng ba mươi thành viên. Đó không phải ba mươi lựa chọn độc lập; đó là các cặp và các họ. Vai trò Dùng cho Cặp “on” của nó primary Hành động chính, nút filled, trạng thái active onPrimary primaryCon...
Design systems need visual regression more than feature apps. Bài này chuyển quan sát đó thành một mô hình nhỏ để bạn có thể kiểm thử, đo lường và giữ cho code dễ bảo trì khi app lớn lên. Vấn đề cốt lõi Font loading, surface sizes, and review workflow for visual diffs. Câu hỏi hữu ích không phải API hay pattern có đẹp riêng lẻ hay không, mà là state nằm ở đâu, boundary nào chịu trách nhiệm khi lỗi xảy ra, và người dùng phục hồi thế nào khi happy path biến mất. Mô hình thực tế Load real fonts in tests or goldens will lie. Hãy bắt đầu bằng một owner rõ ràng cho behavior. Widget chỉ nên render và phát intent; IO, persistence, permission và retry nên nằm sau một interface nhỏ. Nhờ vậy bạn có seam để fake trong test và một chỗ ghi lại các dữ kiện cần theo dõi ở production. Trong app Flutter, boundary thường có dạng: Widget phát intent như load, submit, refresh hoặc retry. Controller hoặc use case validate intent rồi gọi boundary. Boundary trả về data có kiểu hoặc failure có kiểu,...