diff --git a/CLAUDE.md b/CLAUDE.md index 8e9e83e..4167d50 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -100,8 +100,9 @@ migrate → Settings → Import). ## Testing -- **The suite must stay quick and fully green.** It is currently **238 tests at - 100% line coverage**, running in 1–2min on this machine. Don't regress either. +- **The suite must stay quick and fully green.** It is currently **313 tests at + 100% line coverage** (2005 lines), running well under a minute on this + machine. Don't regress either. (The "~5s" figure this file used to quote had drifted well before the port guard was added; measure before quoting a new one.) - Widget tests use `test/fake_note_repository.dart` — a `FakeNoteRepository` @@ -133,793 +134,32 @@ migrate → Settings → Import). - Run `dart format` + `flutter analyze` (clean) before every commit. - Comments explain intent/trade-offs, not syntax. - Keep the app buttery-smooth and low on CPU/RAM — it's a quick-capture tool. +- **Every file is at most 250 lines**, source and prose alike. Enforced by a + pre-commit hook (`scripts/check_file_length.sh`, delegating to the shared + gate in `~/utils`) and by `.github/workflows/file-length.yml`, so it fails + the commit rather than being a note anyone can ignore. Install the hook on + a fresh clone with `scripts/install_hooks.sh`. There is deliberately no + baseline and no allowlist — split the file instead. Exemptions (generated, + vendored, data files) live with the checker so the gate and the survey + cannot disagree. +- Where a split would otherwise break callers, keep the original path as a + barrel that re-exports the parts (`note_template.dart`, + `firebase_backend.dart`, `note_repository.dart` all do this). Where a + widget and its private `State` must stay one library, use `part` files + (`note_editor_*.dart`, `capture_screen_*.dart`). ## Flutter/Dart AI rules -_Appended from [flutter/flutter docs/rules/rules.md](https://github.com/flutter/flutter/blob/main/docs/rules/rules.md) via `/home/kuhy/.claude/CLAUDE.md` Flutter AI tooling setup. Re-fetch periodically to stay current._ - -## AI rules for Flutter - -You are an expert in Flutter and Dart development. Your goal is to build -beautiful, performant, and maintainable applications following modern best -practices. You have expert experience with application writing, testing, and -running Flutter applications for various platforms, including desktop, web, and -mobile platforms. - -### Interaction Guidelines -* **User Persona:** Assume the user is familiar with programming concepts but - may be new to Dart. -* **Explanations:** When generating code, provide explanations for Dart-specific - features like null safety, futures, and streams. -* **Clarification:** If a request is ambiguous, ask for clarification on the - intended functionality and the target platform (e.g., command-line, web, - server). -* **Dependencies:** When suggesting new dependencies from `pub.dev`, explain - their benefits. -* **Formatting:** Use the `dart_format` tool to ensure consistent code - formatting. -* **Fixes:** Use the `dart_fix` tool to automatically fix many common errors, - and to help code conform to configured analysis options. -* **Linting:** Use the Dart linter with a recommended set of rules to catch - common issues. Use the `analyze_files` tool to run the linter. - -### Project Structure -* **Standard Structure:** Assumes a standard Flutter project structure with - `lib/main.dart` as the primary application entry point. - -### Flutter style guide -* **SOLID Principles:** Apply SOLID principles throughout the codebase. -* **Concise and Declarative:** Write concise, modern, technical Dart code. - Prefer functional and declarative patterns. -* **Composition over Inheritance:** Favor composition for building complex - widgets and logic. -* **Immutability:** Prefer immutable data structures. Widgets (especially - `StatelessWidget`) should be immutable. -* **State Management:** Separate ephemeral state and app state. Use a state - management solution for app state to handle the separation of concerns. -* **Widgets are for UI:** Everything in Flutter's UI is a widget. Compose - complex UIs from smaller, reusable widgets. -* **Navigation:** Use a modern routing package like `auto_route` or `go_router`. - For more guidelines around navigation, see the section on [routing](#routing). - -### Package Management -* **Pub Tool:** To manage packages, use the `pub` tool, if available. -* **External Packages:** If a new feature requires an external package, use the - `pub_dev_search` tool, if it is available. Otherwise, identify the most - suitable and stable package from pub.dev. -* **Adding Dependencies:** To add a regular dependency, use the `pub` tool, if - it is available. Otherwise, run `flutter pub add `. -* **Adding Dev Dependencies:** To add a development dependency, use the `pub` - tool, if it is available, with `dev:`. Otherwise, run `flutter - pub add dev:`. -* **Dependency Overrides:** To add a dependency override, use the `pub` tool, if - it is available, with `override::1.0.0`. Otherwise, run `flutter - pub add override::1.0.0`. -* **Removing Dependencies:** To remove a dependency, use the `pub` tool, if it - is available. Otherwise, run `dart pub remove `. - -### Code Quality -* **Code structure:** Adhere to maintainable code structure and separation of - concerns (e.g., UI logic separate from business logic). -* **Naming conventions:** Avoid abbreviations and use meaningful, consistent, - descriptive names for variables, functions, and classes. -* **Conciseness:** Write code that is as short as it can be while remaining - clear. -* **Simplicity:** Write straightforward code. Code that is clever or - obscure is difficult to maintain. -* **Error Handling:** Anticipate and handle potential errors. Don't let your - code fail silently. -* **Styling:** - * Line length: Lines should be 80 characters or fewer. - * Use `PascalCase` for classes, `camelCase` for - members/variables/functions/enums, and `snake_case` for files. -* **Functions:** - * Keep functions short and with a single purpose. - Strive for less than 20 lines. -* **Testing:** Write code with testing in mind. Use the `file`, `process`, and - `platform` packages, if appropriate, so you can inject in-memory and fake - versions of the objects. -* **Logging:** Use the `logging` package instead of `print`. - -### Dart Best Practices -* **Effective Dart:** Follow the official Effective Dart guidelines - (https://dart.dev/effective-dart) -* **Class Organization:** Define related classes within the same library file. - For large libraries, export smaller, private libraries from a single top-level - library. -* **Library Organization:** Group related libraries in the same folder. -* **API Documentation:** Add documentation comments to all public APIs, - including classes, constructors, methods, and top-level functions. -* **Comments:** Write clear comments for complex or non-obvious code. Avoid - over-commenting. -* **Trailing Comments:** Don't add trailing comments. -* **Async/Await:** Ensure proper use of `async`/`await` for asynchronous - operations with robust error handling. - * Use `Future`s, `async`, and `await` for asynchronous operations. - * Use `Stream`s for sequences of asynchronous events. -* **Null Safety:** Write code that is soundly null-safe. Leverage Dart's null - safety features. Avoid `!` unless the value is guaranteed to be non-null. -* **Pattern Matching:** Use pattern matching features where they simplify the - code. -* **Records:** Use records to return multiple types in situations where defining - an entire class is cumbersome. -* **Switch Statements:** Prefer using exhaustive `switch` statements or - expressions, which don't require `break` statements. -* **Exception Handling:** Use `try-catch` blocks for handling exceptions, and - use exceptions appropriate for the type of exception. Use custom exceptions - for situations specific to your code. -* **Arrow Functions:** Use arrow syntax for simple one-line functions. - -### Flutter Best Practices -* **Immutability:** Widgets (especially `StatelessWidget`) are immutable; when - the UI needs to change, Flutter rebuilds the widget tree. -* **Composition:** Prefer composing smaller widgets over extending existing - ones. Use this to avoid deep widget nesting. -* **Private Widgets:** Use small, private `Widget` classes instead of private - helper methods that return a `Widget`. -* **Build Methods:** Break down large `build()` methods into smaller, reusable - private Widget classes. -* **List Performance:** Use `ListView.builder` or `SliverList` for long lists to - create lazy-loaded lists for performance. -* **Isolates:** Use `compute()` to run expensive calculations in a separate - isolate to avoid blocking the UI thread, such as JSON parsing. -* **Const Constructors:** Use `const` constructors for widgets and in `build()` - methods whenever possible to reduce rebuilds. -* **Build Method Performance:** Avoid performing expensive operations, like - network calls or complex computations, directly within `build()` methods. - -### API Design Principles -When building reusable APIs, such as a library, follow these principles. - -* **Consider the User:** Design APIs from the perspective of the person who will - be using them. The API should be intuitive and easy to use correctly. -* **Documentation is Essential:** Good documentation is a part of good API - design. It should be clear, concise, and provide examples. - -### Application Architecture -* **Separation of Concerns:** Aim for separation of concerns similar to MVC/MVVM, with defined Model, - View, and ViewModel/Controller roles. -* **Logical Layers:** Organize the project into logical layers: - * Presentation (widgets, screens) - * Domain (business logic classes) - * Data (model classes, API clients) - * Core (shared classes, utilities, and extension types) -* **Feature-based Organization:** For larger projects, organize code by feature, - where each feature has its own presentation, domain, and data subfolders. This - improves navigability and scalability. - -### Lint Rules - -Include the package in the `analysis_options.yaml` file. Use the following -`analysis_options.yaml` file as a starting point: - -```yaml -include: package:flutter_lints/flutter.yaml - -linter: - rules: - # Add additional lint rules here: - # avoid_print: false - # prefer_single_quotes: true -``` - -#### State Management -* **Built-in Solutions:** Prefer Flutter's built-in state management solutions. - Do not use a third-party package unless explicitly requested. -* **Streams:** Use `Streams` and `StreamBuilder` for handling a sequence of - asynchronous events. -* **Futures:** Use `Futures` and `FutureBuilder` for handling a single - asynchronous operation that will complete in the future. -* **ValueNotifier:** Use `ValueNotifier` with `ValueListenableBuilder` for - simple, local state that involves a single value. - - ```dart - // Define a ValueNotifier to hold the state. - final ValueNotifier _counter = ValueNotifier(0); - - // Use ValueListenableBuilder to listen and rebuild. - ValueListenableBuilder( - valueListenable: _counter, - builder: (context, value, child) { - return Text('Count: $value'); - }, - ); - ``` - -* **ChangeNotifier:** For state that is more complex or shared across multiple - widgets, use `ChangeNotifier`. -* **ListenableBuilder:** Use `ListenableBuilder` to listen to changes from a - `ChangeNotifier` or other `Listenable`. -* **MVVM:** When a more robust solution is needed, structure the app using the - Model-View-ViewModel (MVVM) pattern. -* **Dependency Injection:** Use simple manual constructor dependency injection - to make a class's dependencies explicit in its API, and to manage dependencies - between different layers of the application. -* **Provider:** If a dependency injection solution beyond manual constructor - injection is explicitly requested, `provider` can be used to make services, - repositories, or complex state objects available to the UI layer without tight - coupling (note: this document generally defaults against third-party packages - for state management unless explicitly requested). - -#### Data Flow -* **Data Structures:** Define data structures (classes) to represent the data - used in the application. -* **Data Abstraction:** Abstract data sources (e.g., API calls, database - operations) using Repositories/Services to promote testability. - -#### Routing -* **GoRouter:** Use the `go_router` package for declarative navigation, deep - linking, and web support. -* **GoRouter Setup:** To use `go_router`, first add it to your `pubspec.yaml` - using the `pub` tool's `add` command. - - ```dart - // 1. Add the dependency - // flutter pub add go_router - - // 2. Configure the router - final GoRouter _router = GoRouter( - routes: [ - GoRoute( - path: '/', - builder: (context, state) => const HomeScreen(), - routes: [ - GoRoute( - path: 'details/:id', // Route with a path parameter - builder: (context, state) { - final String id = state.pathParameters['id']!; - return DetailScreen(id: id); - }, - ), - ], - ), - ], - ); - - // 3. Use it in your MaterialApp - MaterialApp.router( - routerConfig: _router, - ); - ``` -* **Authentication Redirects:** Configure `go_router`'s `redirect` property to - handle authentication flows, ensuring users are redirected to the login screen - when unauthorized, and back to their intended destination after successful - login. - -* **Navigator:** Use the built-in `Navigator` for short-lived screens that do - not need to be deep-linkable, such as dialogs or temporary views. - - ```dart - // Push a new screen onto the stack - Navigator.push( - context, - MaterialPageRoute(builder: (context) => const DetailsScreen()), - ); - - // Pop the current screen to go back - Navigator.pop(context); - ``` - -#### Data Handling & Serialization -* **JSON Serialization:** Use `json_serializable` and `json_annotation` for - parsing and encoding JSON data. -* **Field Renaming:** When encoding data, use `fieldRename: FieldRename.snake` - to convert Dart's camelCase fields to snake_case JSON keys. - - ```dart - // In your model file - import 'package:json_annotation/json_annotation.dart'; - - part 'user.g.dart'; - - @JsonSerializable(fieldRename: FieldRename.snake) - class User { - final String firstName; - final String lastName; - - User({required this.firstName, required this.lastName}); - - factory User.fromJson(Map json) => _$UserFromJson(json); - Map toJson() => _$UserToJson(this); - } - ``` - - -#### Logging -* **Structured Logging:** Use the `log` function from `dart:developer` for - structured logging that integrates with Dart DevTools. - - ```dart - import 'dart:developer' as developer; - - // For simple messages - developer.log('User logged in successfully.'); - - // For structured error logging - try { - // ... code that might fail - } catch (e, s) { - developer.log( - 'Failed to fetch data', - name: 'myapp.network', - level: 1000, // SEVERE - error: e, - stackTrace: s, - ); - } - ``` - -### Code Generation -* **Build Runner:** If the project uses code generation, ensure that - `build_runner` is listed as a dev dependency in `pubspec.yaml`. -* **Code Generation Tasks:** Use `build_runner` for all code generation tasks, - such as for `json_serializable`. -* **Running Build Runner:** After modifying files that require code generation, - run the build command: - - ```shell - dart run build_runner build --delete-conflicting-outputs - ``` - -### Testing -* **Running Tests:** To run tests, use the `run_tests` tool if it is available, - otherwise use `flutter test`. -* **Unit Tests:** Use `package:test` for unit tests. -* **Widget Tests:** Use `package:flutter_test` for widget tests. -* **Integration Tests:** Use `package:integration_test` for integration tests. -* **Assertions:** Prefer using `package:checks` for more expressive and readable - assertions over the default `matchers`. - -#### Testing Best practices -* **Convention:** Follow the Arrange-Act-Assert (or Given-When-Then) pattern. -* **Unit Tests:** Write unit tests for domain logic, data layer, and state - management. -* **Widget Tests:** Write widget tests for UI components. -* **Integration Tests:** For broader application validation, use integration - tests to verify end-to-end user flows. -* **integration_test package:** Use the `integration_test` package from the - Flutter SDK for integration tests. Add it as a `dev_dependency` in - `pubspec.yaml` by specifying `sdk: flutter`. -* **Mocks:** Prefer fakes or stubs over mocks. If mocks are absolutely - necessary, use `mockito` or `mocktail` to create mocks for dependencies. While - code generation is common for state management (e.g., with `freezed`), try to - avoid it for mocks. -* **Coverage:** Aim for high test coverage. - -### Visual Design & Theming -* **UI Design:** Build beautiful and intuitive user interfaces that follow - modern design guidelines. -* **Responsiveness:** Ensure the app is mobile responsive and adapts to - different screen sizes, working perfectly on mobile and web. -* **Navigation:** If there are multiple pages for the user to interact with, - provide an intuitive and easy navigation bar or controls. -* **Typography:** Stress and emphasize font sizes to ease understanding, e.g., - hero text, section headlines, list headlines, keywords in paragraphs. -* **Background:** Keep backgrounds flat and simple — no noise texture or - gradients. Follow the `unified-design-system` skill (safe-design-rules - rule 17: simple-on-complex); let content and typography carry visual - interest instead of background texture. -* **Shadows:** Follow the `unified-design-system` skill's shadow policy: no - shadows at all in dark UI (differentiate elevation with lighter surface - fills, not shadows); in light UI, shadows are for floating/overlay - elements only (menus, dialogs), never for inline cards, and never mixed - with a border on the same surface. -* **Icons:** Incorporate icons to enhance the user’s understanding and the - logical navigation of the app. -* **Interactive Elements:** Buttons/controls signal state via color and fill - changes, not a "glow" shadow — one depth technique per surface, per - `unified-design-system`. - -#### Theming -* **Centralized Theme:** Define a centralized `ThemeData` object to ensure a - consistent application-wide style. -* **Light and Dark Themes:** Implement support for both light and dark themes, - ideal for a user-facing theme toggle (`ThemeMode.light`, `ThemeMode.dark`, - `ThemeMode.system`). -* **Color Scheme Generation:** Do NOT use `ColorScheme.fromSeed` for this - app's palette — it can't reproduce hand-picked values exactly. Build - `ThemeData` from explicit `ColorScheme.dark(...)`/`.light(...)` using the - frozen, identical-everywhere tokens in the `unified-design-system` skill - (`~/.claude/skills/unified-design-system/references/tokens.md`). - - ```dart - final ThemeData lightTheme = ThemeData( - colorScheme: ColorScheme.fromSeed( - seedColor: Colors.deepPurple, - brightness: Brightness.light, - ), - // ... other theme properties - ); - ``` -* **Color Palette:** Include a wide range of color concentrations and hues in - the palette to create a vibrant and energetic look and feel. -* **Component Themes:** Use specific theme properties (e.g., `appBarTheme`, - `elevatedButtonTheme`) to customize the appearance of individual Material - components. -* **Custom Fonts:** For custom fonts, use the `google_fonts` package. Define a - `TextTheme` to apply fonts consistently. - - ```dart - // 1. Add the dependency - // flutter pub add google_fonts - - // 2. Define a TextTheme with a custom font - final TextTheme appTextTheme = TextTheme( - displayLarge: GoogleFonts.oswald(fontSize: 57, fontWeight: FontWeight.bold), - titleLarge: GoogleFonts.roboto(fontSize: 22, fontWeight: FontWeight.w500), - bodyMedium: GoogleFonts.openSans(fontSize: 14), - ); - ``` - -#### Assets and Images -* **Image Guidelines:** If images are needed, make them relevant and meaningful, - with appropriate size, layout, and licensing (e.g., freely available). Provide - placeholder images if real ones are not available. -* **Asset Declaration:** Declare all asset paths in your `pubspec.yaml` file. - - ```yaml - flutter: - uses-material-design: true - assets: - - assets/images/ - ``` - -* **Local Images:** Use `Image.asset` for local images from your asset - bundle. - - ```dart - Image.asset('assets/images/placeholder.png') - ``` -* **Network images:** Use NetworkImage for images loaded from the network. -* **Cached images:** For cached images, use NetworkImage a package like - `cached_network_image`. -* **Custom Icons:** Use `ImageIcon` to display an icon from an `ImageProvider`, - useful for custom icons not in the `Icons` class. -* **Network Images:** Use `Image.network` to display images from a URL, and - always include `loadingBuilder` and `errorBuilder` for a better user - experience. - - ```dart - // When using network images, always provide an errorBuilder. - Image.network( - 'https://picsum.photos/200/300', - loadingBuilder: (context, child, progress) { - if (progress == null) return child; - return const Center(child: CircularProgressIndicator()); - }, - errorBuilder: (context, error, stackTrace) { - return const Icon(Icons.error); - }, - ) - ``` - -### UI Theming and Styling Code - -* **Responsiveness:** Use `LayoutBuilder` or `MediaQuery` to create responsive - UIs. -* **Text:** Use `Theme.of(context).textTheme` for text styles. -* **Text Fields:** Configure `textCapitalization`, `keyboardType`, and - `placeholder`. - -### Material Theming Best Practices - -#### Embrace `ThemeData` and Material 3 - -* **Use `ColorScheme.fromSeed()`:** Use this to generate a complete, harmonious - color palette for both light and dark modes from a single seed color. -* **Define Light and Dark Themes:** Provide both `theme` and `darkTheme` to your - `MaterialApp` to support system brightness settings seamlessly. -* **Centralize Component Styles:** Customize specific component themes (e.g., - `elevatedButtonTheme`, `cardTheme`, `appBarTheme`) within `ThemeData` to - ensure consistency. -* **Dark/Light Mode and Theme Toggle:** Implement support for both light and - dark themes using `theme` and `darkTheme` properties of `MaterialApp`. The - `themeMode` property can be dynamically controlled (e.g., via a - `ChangeNotifierProvider`) to allow for toggling between `ThemeMode.light`, - `ThemeMode.dark`, or `ThemeMode.system`. - -```dart -// main.dart -MaterialApp( - theme: ThemeData( - colorScheme: ColorScheme.fromSeed( - seedColor: Colors.deepPurple, - brightness: Brightness.light, - ), - textTheme: const TextTheme( - displayLarge: TextStyle(fontSize: 57.0, fontWeight: FontWeight.bold), - bodyMedium: TextStyle(fontSize: 14.0, height: 1.4), - ), - ), - darkTheme: ThemeData( - colorScheme: ColorScheme.fromSeed( - seedColor: Colors.deepPurple, - brightness: Brightness.dark, - ), - ), - home: const MyHomePage(), -); -``` - -#### Implement Design Tokens with `ThemeExtension` - -For custom styles that aren't part of the standard `ThemeData`, use -`ThemeExtension` to define reusable design tokens. - -* **Create a Custom Theme Extension:** Define a class that extends - `ThemeExtension` and include your custom properties. -* **Implement `copyWith` and `lerp`:** These methods are required for the - extension to work correctly with theme transitions. -* **Register in `ThemeData`:** Add your custom extension to the `extensions` - list in your `ThemeData`. -* **Access Tokens in Widgets:** Use `Theme.of(context).extension()!` - to access your custom tokens. - -```dart -// 1. Define the extension -@immutable -class MyColors extends ThemeExtension { - const MyColors({required this.success, required this.danger}); - - final Color? success; - final Color? danger; - - @override - ThemeExtension copyWith({Color? success, Color? danger}) { - return MyColors(success: success ?? this.success, danger: danger ?? this.danger); - } - - @override - ThemeExtension lerp(ThemeExtension? other, double t) { - if (other is! MyColors) return this; - return MyColors( - success: Color.lerp(success, other.success, t), - danger: Color.lerp(danger, other.danger, t), - ); - } -} - -// 2. Register it in ThemeData -theme: ThemeData( - extensions: const >[ - MyColors(success: Colors.green, danger: Colors.red), - ], -), - -// 3. Use it in a widget -Container( - color: Theme.of(context).extension()!.success, -) -``` - -#### Styling with `WidgetStateProperty` - -* **`WidgetStateProperty.resolveWith`:** Provide a function that receives a - `Set` and returns the appropriate value for the current state. -* **`WidgetStateProperty.all`:** A shorthand for when the value is the same for - all states. - -```dart -// Example: Creating a button style that changes color when pressed. -final ButtonStyle myButtonStyle = ButtonStyle( - backgroundColor: WidgetStateProperty.resolveWith( - (Set states) { - if (states.contains(WidgetState.pressed)) { - return Colors.green; // Color when pressed - } - return Colors.red; // Default color - }, - ), -); -``` - -### Layout Best Practices - -#### Building Flexible and Overflow-Safe Layouts - -##### For Rows and Columns - -* **`Expanded`:** Use to make a child widget fill the remaining available space - along the main axis. -* **`Flexible`:** Use when you want a widget to shrink to fit, but not - necessarily grow. Don't combine `Flexible` and `Expanded` in the same `Row` or - `Column`. -* **`Wrap`:** Use when you have a series of widgets that would overflow a `Row` - or `Column`, and you want them to move to the next line. - -##### For General Content - -* **`SingleChildScrollView`:** Use when your content is intrinsically larger - than the viewport, but is a fixed size. -* **`ListView` / `GridView`:** For long lists or grids of content, always use a - builder constructor (`.builder`). -* **`FittedBox`:** Use to scale or fit a single child widget within its parent. -* **`LayoutBuilder`:** Use for complex, responsive layouts to make decisions - based on the available space. - -#### Layering Widgets with Stack - -* **`Positioned`:** Use to precisely place a child within a `Stack` by anchoring it to the edges. -* **`Align`:** Use to position a child within a `Stack` using alignments like `Alignment.center`. - -#### Advanced Layout with Overlays - -* **`OverlayPortal`:** Use this widget to show UI elements (like custom - dropdowns or tooltips) "on top" of everything else. It manages the - `OverlayEntry` for you. - - ```dart - class MyDropdown extends StatefulWidget { - const MyDropdown({super.key}); - - @override - State createState() => _MyDropdownState(); - } - - class _MyDropdownState extends State { - final _controller = OverlayPortalController(); - - @override - Widget build(BuildContext context) { - return OverlayPortal( - controller: _controller, - overlayChildBuilder: (BuildContext context) { - return const Positioned( - top: 50, - left: 10, - child: Card( - child: Padding( - padding: EdgeInsets.all(8.0), - child: Text('I am an overlay!'), - ), - ), - ); - }, - child: ElevatedButton( - onPressed: _controller.toggle, - child: const Text('Toggle Overlay'), - ), - ); - } - } - ``` - -### Color Scheme Best Practices - -#### Contrast Ratios - -* **WCAG Guidelines:** Aim to meet the Web Content Accessibility Guidelines - (WCAG) 2.1 standards. -* **Minimum Contrast:** - * **Normal Text:** A contrast ratio of at least **4.5:1**. - * **Large Text:** (18pt or 14pt bold) A contrast ratio of at least **3:1**. - -#### Palette Selection - -* **Primary, Secondary, and Accent:** Define a clear color hierarchy. -* **The 60-30-10 Rule:** A classic design rule for creating a balanced color scheme. - * **60%** Primary/Neutral Color (Dominant) - * **30%** Secondary Color - * **10%** Accent Color - -#### Complementary Colors - -* **Use with Caution:** They can be visually jarring if overused. -* **Best Use Cases:** They are excellent for accent colors to make specific - elements pop, but generally poor for text and background pairings as they can - cause eye strain. - -#### Example Palette - -* **Primary:** #0D47A1 (Dark Blue) -* **Secondary:** #1976D2 (Medium Blue) -* **Accent:** #FFC107 (Amber) -* **Neutral/Text:** #212121 (Almost Black) -* **Background:** #FEFEFE (Almost White) - -### Font Best Practices - -#### Font Selection - -* **Limit Font Families:** Stick to one or two font families for the entire - application. -* **Prioritize Legibility:** Choose fonts that are easy to read on screens of - all sizes. Sans-serif fonts are generally preferred for UI body text. -* **System Fonts:** Consider using platform-native system fonts. -* **Google Fonts:** For a wide selection of open-source fonts, use the - `google_fonts` package. - -#### Hierarchy and Scale - -* **Establish a Scale:** Define a set of font sizes for different text elements - (e.g., headlines, titles, body text, captions). -* **Use Font Weight:** Differentiate text effectively using font weights. -* **Color and Opacity:** Use color and opacity to de-emphasize less important - text. - -#### Readability - -* **Line Height (Leading):** Set an appropriate line height, typically **1.4x to - 1.6x** the font size. -* **Line Length:** For body text, aim for a line length of **45-75 characters**. -* **Avoid All Caps:** Do not use all caps for long-form text. - -#### Example Typographic Scale - -```dart -// In your ThemeData -textTheme: const TextTheme( - displayLarge: TextStyle(fontSize: 57.0, fontWeight: FontWeight.bold), - titleLarge: TextStyle(fontSize: 22.0, fontWeight: FontWeight.bold), - bodyLarge: TextStyle(fontSize: 16.0, height: 1.5), - bodyMedium: TextStyle(fontSize: 14.0, height: 1.4), - labelSmall: TextStyle(fontSize: 11.0, color: Colors.grey), -), -``` - -### Documentation - -* **`dartdoc`:** Write `dartdoc`-style comments for all public APIs. - - -#### Documentation Philosophy - -* **Comment wisely:** Use comments to explain why the code is written a certain - way, not what the code does. The code itself should be self-explanatory. -* **Document for the user:** Write documentation with the reader in mind. If you - had a question and found the answer, add it to the documentation where you - first looked. This ensures the documentation answers real-world questions. -* **No useless documentation:** If the documentation only restates the obvious - from the code's name, it's not helpful. Good documentation provides context - and explains what isn't immediately apparent. -* **Consistency is key:** Use consistent terminology throughout your - documentation. - -#### Commenting Style - -* **Use `///` for doc comments:** This allows documentation generation tools to - pick them up. -* **Start with a single-sentence summary:** The first sentence should be a - concise, user-centric summary ending with a period. -* **Separate the summary:** Add a blank line after the first sentence to create - a separate paragraph. This helps tools create better summaries. -* **Avoid redundancy:** Don't repeat information that's obvious from the code's - context, like the class name or signature. -* **Don't document both getter and setter:** For properties with both, only - document one. The documentation tool will treat them as a single field. - -#### Writing Style - -* **Be brief:** Write concisely. -* **Avoid jargon and acronyms:** Don't use abbreviations unless they are widely - understood. -* **Use Markdown sparingly:** Avoid excessive markdown and never use HTML for - formatting. -* **Use backticks for code:** Enclose code blocks in backtick fences, and - specify the language. - -#### What to Document - -* **Public APIs are a priority:** Always document public APIs. -* **Consider private APIs:** It's a good idea to document private APIs as well. -* **Library-level comments are helpful:** Consider adding a doc comment at the - library level to provide a general overview. -* **Include code samples:** Where appropriate, add code samples to illustrate usage. -* **Explain parameters, return values, and exceptions:** Use prose to describe - what a function expects, what it returns, and what errors it might throw. -* **Place doc comments before annotations:** Documentation should come before - any metadata annotations. - -### Accessibility (A11Y) -Implement accessibility features to empower all users, assuming a wide variety -of users with different physical abilities, mental abilities, age groups, -education levels, and learning styles. - -* **Color Contrast:** Ensure text has a contrast ratio of at least **4.5:1** - against its background. -* **Dynamic Text Scaling:** Test your UI to ensure it remains usable when users - increase the system font size. -* **Semantic Labels:** Use the `Semantics` widget to provide clear, descriptive - labels for UI elements. -* **Screen Reader Testing:** Regularly test your app with TalkBack (Android) and - VoiceOver (iOS). +Upstream's Flutter/Dart guidance used to be pasted in below this line, which +made 85% of this always-loaded file a document nobody edits. It now lives in +`docs/`, linked rather than inlined so the part loaded on every turn stays +about *this repo*: + +- [Language, style and architecture](docs/flutter-rules-language.md) +- [State, routing and data](docs/flutter-rules-state-and-data.md) +- [Code generation, testing, theming and assets](docs/flutter-rules-testing-and-assets.md) +- [UI: theming, layout and overlays](docs/flutter-rules-ui.md) +- [Colour, type, documentation and accessibility](docs/flutter-rules-design.md) + +Sourced from [flutter/flutter docs/rules/rules.md](https://github.com/flutter/flutter/blob/main/docs/rules/rules.md) +via `~/.claude/CLAUDE.md`'s Flutter AI tooling setup. Re-fetch periodically. diff --git a/docs/desktop-performance-findings.md b/docs/desktop-performance-findings.md index 822024c..6af297f 100644 --- a/docs/desktop-performance-findings.md +++ b/docs/desktop-performance-findings.md @@ -67,212 +67,7 @@ Key readings: - The stock counter app behaves identically, so this is not `todo`'s code. - Neither renderer choice, nor driver vsync, nor a compositor moves the number. -## Part 2: the interaction lag is a separate, larger problem +--- -The 60 Hz cap above is real but is **not** what the user is complaining about. -Driven by the user directly (synthetic input having proved unreliable), with an -instrument that logs individual outliers plus event-loop blocking rather than -per-second percentiles: - -- Stalls are **raster-dominated**. Build (Dart) stays at 0.1-0.9 ms throughout; - single frames reach 76 ms of raster at a 1280x1417 window. -- The UI isolate does block occasionally (65-101 ms at startup and on some - interactions), but this is the minority of the problem. -- The user reports the lag is **constant, on every interaction** — which rules - out both GitHub sync (periodic) and shader compilation (one-off, warms up). -- Impeller/Vulkan halved slow frames (18 → 7) and removed the worst 76 ms - spike, but the user perceived **no difference**. - -**Raster cost scales hard with window pixel count**, and the user runs the app -fullscreen on the 4K monitor. Measured under a nested X server: - -| Window | Achieved | build p50/p90 | raster p50/p90 | -|---|---|---|---| -| 1280x720 | 68.5 fps | 0.30 / 0.40 ms | 1.50 / 2.00 ms | -| 3840x2160 | 19.9 fps | 0.40 / 0.50 ms | 8.40 / 44.60 ms | - -Those first 4K figures came from plain (software) Xephyr and overstated raster. -Repeated **fullscreen on the real 3840x2160 monitor**: - -| Window (real display) | Achieved | build p50 | raster p50 | -|---|---|---|---| -| 1280x720 | 68.6 fps | 0.30 ms | 1.50 ms | -| 3840x2160 | **18.6 fps** | 0.30 ms | **2.70 ms** | - -This is the key result, and it is *not* a rasterisation wall. At 4K each frame -costs ~3 ms of a 16.7 ms budget, yet the app achieves 18.6 fps (~54 ms per -frame). Roughly **50 ms per frame is dead time** — neither build nor raster. -The bottleneck is the presentation/swap path, which is also why Impeller never -helped: Impeller changes rasterisation, not presentation. - -### It is Flutter, not todo - -Run through an identical harness, the **stock `flutter create` counter app** — -one spinner on a blank Scaffold, zero todo code — collapses the same way: - -| App | 1280x720 | 3840x2160 | -|---|---|---| -| stock Flutter counter | 68.5 fps | **20.5 fps** | -| todo | 68.5 fps | **20.6 fps** | - -Flutter's Linux embedder manages ~20 fps rendering a **blank screen** at 4K. -Build time is 0.1-0.4 ms throughout. No application-level change can fix this. - -Size curve (harness), showing the cliff begins at 1440p: - -| size | Mpx | fps | frame cost | dead time | -|---|---|---|---|---| -| 1280x720 | 0.92 | 68.5 | 1.70 ms | 12.9 ms | -| 1920x1080 | 2.07 | 60.5 | 2.40 ms | 14.1 ms | -| 2560x1440 | 3.69 | 40.3 | 3.70 ms | 21.1 ms | -| 3840x2160 | 8.29 | 20.6 | 8.80 ms | 39.8 ms | - -Related upstream reports: -[#66463](https://github.com/flutter/flutter/issues/66463) (Linux desktop failure -based on window size on 4K monitors), -[#88996](https://github.com/flutter/flutter/issues/88996) (high frame latency on -Linux desktop). - -## Test harness (for iterating without taking over the user's screens) - -`Xephyr -glamor` runs a nested X server that renders through OpenGL on the real -GPU. Validated against ground truth: it reproduced the 4K symptom at 19.8 fps -versus the real display's 18.6 fps. It inflates raster attribution (7.2 ms vs -2.7 ms real), so use it for pass/fail, **not** for attributing where time goes. - -Plain `Xephyr` without `-glamor` is a software X server and is useless here: it -copies a ~33 MB framebuffer per frame at 4K and dominates the measurement. - -**The harness is validated for the Flutter desktop embedder only.** A Chrome -run inside it reported 10 fps at 4K; measuring the same page on the real display -gave 142.9 fps, confirming that number was a harness artifact (Chrome disables -GPU acceleration on an unrecognised nested display). Never benchmark a browser -inside it. - -## The decisive comparison: same content, same machine, real 4K display - -The identical spinner probe, rendered at 3840x2160 on the real monitor: - -| Renderer | Achieved at 4K | -|---|---| -| Flutter **Linux desktop embedder** | **20.5 fps** | -| Flutter **Web (CanvasKit) in Chrome** | **142.9 fps** (cadence p50 7.00 ms) | - -A **7x difference**, with the same framework and the same Dart code. This is not -a hardware limit, a driver limit, or an application limit — it is specific to -Flutter's Linux desktop embedder. It validates the web route at the size that -actually matters. - -## Why the 60 Hz cap exists - -`libflutter_linux_gtk.so` imports `gdk_monitor_get_refresh_rate` (and GTK -correctly reports 144.00 Hz here — verified independently), but imports **no** -`gdk_frame_clock_*` symbols; the engine falls back to a fixed 60 FPS vsync -waiter. - -This is a known, open upstream limitation: -[flutter/flutter#183703 — "[Proposal] Support high refresh rate (>60) on -Linux"](https://github.com/flutter/flutter/issues/183703). Opened 2026-03-14, -labelled **P2**, **no assignee, no linked PR, no milestone**. Linux is the last -Flutter platform without >60 Hz support. Related: -[#93058](https://github.com/flutter/flutter/issues/93058) (Windows, since -fixed), [#49757](https://github.com/flutter/flutter/issues/49757). - -## Implication for the "switch technology" question - -A rewrite in another language is **not** warranted by the evidence. The same -Flutter framework and the same Dart code reach ~143 fps on this exact machine -when built for web and run in Chrome. Only the Linux *desktop embedder* is -capped. - -The leading candidate is therefore **shipping the desktop app as a Flutter web -build in a Chrome `--app` window**, reusing the UI and domain code. - -**Validated at 4K on the real display: 142.9 fps vs the desktop embedder's -20.5 fps on identical content.** That settles the renderer question. - -What remains unproven is todo's *own* widget tree on web: the probe is a -spinner, and web CanvasKit raster plus WASM/JS build costs differ from the -desktop's 0.30/1.50 ms. Given desktop build time is only 0.3 ms and the web -renderer has 7x the headroom, this is very likely fine — but the proof-of- -concept should build the actual app and re-measure before the port is finished. - -What is reused is the UI and domain logic. The platform-adapter layer needs web -variants throughout: persistence durability under a browser profile, token -storage, device auth, sync-on-background (Page Visibility API), and the -launch mechanism (local server vs `file://`). - -Port surface (the storage seam is small, because `crdt_sync_dart` was designed -for it): - -- `LogStore` is deliberately `dart:io`-free and documented as web-usable; - `FileLogPersistence` is already isolated behind the `crdt_sync_io.dart` - entrypoint. `LogPersistence` is a two-method interface (`read()` / - `write(String)`), so a web implementation over IndexedDB/localStorage is - roughly 15 lines. -- `github_client.dart` has **no** `dart:io` and uses `package:http`; the GitHub - REST API sends permissive CORS headers, so sync works from a browser origin. -- `sqlite_crdt`/`sqflite_common_ffi` remain only for the one-time legacy DB - migration (`note_repository.dart:350`), which does not apply on web and can be - skipped behind `kIsWeb`. -- `path_provider` is only used to choose the file path for `FileLogPersistence`; - unnecessary on web. -- `shared_preferences`, `url_launcher`, `share_plus`, `file_selector`, - `flutter_secure_storage` all have web implementations. - -### Known costs of the web route (both are security regressions) - -1. **The token loses the OS keystore.** Commit `9841aec` deliberately moved the - GitHub token off plaintext and into the OS keyring via - `flutter_secure_storage`. A Chrome `--app` build cannot reach the keyring; - `flutter_secure_storage` on web falls back to WebCrypto-wrapped - `localStorage`. That is materially weaker than what is shipping today and - partially undoes that commit. -2. **Device-flow auth breaks.** GitHub's OAuth device-flow endpoints - (`github.com/login/device/code`) do not send CORS headers, so one-tap device - auth will not work from a browser origin. The settings screen's - paste-a-personal-access-token path would have to become the desktop auth - route — which also means a long-lived PAT living in the weaker storage above. - -These two stack, and they are the strongest argument against the web route. - -## Agreed plan (decided 2026-07-20) - -The desktop app moves to a Flutter **web** build launched in Chrome. Decisions -taken, so the next session can start from here: - -- **Auth:** simple PAT pasted into `localStorage` (via - `flutter_secure_storage_web`). The local-helper design that would have kept - the OS keyring and device-flow auth was considered and **rejected** for - simplicity. Consequences accepted: the token is no longer in the keyring, and - the AES-GCM wrapping is obfuscation only (the wrap key sits in the same - `localStorage`). - - Requires a **classic PAT with "No expiration"** to be a one-time paste. - - The launcher must pin a **fixed port** and a **fixed `--user-data-dir`**, - and call `navigator.storage.persist()`. Any of those changing loses the - token: `localStorage` is keyed by origin and lives in the Chrome profile. -- **The Linux desktop build is deleted**, not kept as a fallback. (Android is - unaffected and remains a normal Flutter mobile build.) -- **Launch:** a wrapper script serves the web build on a fixed port and opens - Chrome with `--app=http://localhost:`. -- **Done-condition:** the real todo app, fullscreen at 4K, sustains **>=100 fps** - while scrolling the notes list, and sync works against `kuhyx/syncs`. - -Still to be specified before coding: the `WebLogPersistence` implementation, -what replaces `path_provider`/`sqflite` on web (the legacy sqlite migration path -at `note_repository.dart:350` does not apply), packaging/`install_arch.sh` -changes, and how the desktop entry launches the wrapper. - -## Alternatives, if the web route is rejected - -Ranked by cost. All require re-implementing the UI, and the two non-Dart options -also require a third `crdt_sync` implementation (it exists today only in Python -and Dart). - -1. **Accept 60 fps.** Zero cost. The app is genuinely smooth *at* 60. -2. **Qt 6 / QML (C++).** Vulkan RHI, real 144 Hz, best NVIDIA/X11 record. Weeks. -3. **Rust + Slint.** Lightweight, but the highest total cost. - -Not recommended: **Tauri** — on Linux it renders via WebKitGTK, which has its -own NVIDIA/X11 DMABUF rendering bugs; it trades one renderer problem for -another. +Part 2 — the interaction-lag half of this investigation, and the decision that came out of it — is in +[desktop-performance-interaction-lag.md](desktop-performance-interaction-lag.md). diff --git a/docs/desktop-performance-interaction-lag.md b/docs/desktop-performance-interaction-lag.md new file mode 100644 index 0000000..4488da9 --- /dev/null +++ b/docs/desktop-performance-interaction-lag.md @@ -0,0 +1,216 @@ +# Linux desktop performance, part 2: interaction lag + +The second half of the investigation started in +[desktop-performance-findings.md](desktop-performance-findings.md), which +covers frame rate. Split only to keep each file under the 250-line cap; +the author signposted the two parts explicitly. + +## Part 2: the interaction lag is a separate, larger problem + +The 60 Hz cap above is real but is **not** what the user is complaining about. +Driven by the user directly (synthetic input having proved unreliable), with an +instrument that logs individual outliers plus event-loop blocking rather than +per-second percentiles: + +- Stalls are **raster-dominated**. Build (Dart) stays at 0.1-0.9 ms throughout; + single frames reach 76 ms of raster at a 1280x1417 window. +- The UI isolate does block occasionally (65-101 ms at startup and on some + interactions), but this is the minority of the problem. +- The user reports the lag is **constant, on every interaction** — which rules + out both GitHub sync (periodic) and shader compilation (one-off, warms up). +- Impeller/Vulkan halved slow frames (18 → 7) and removed the worst 76 ms + spike, but the user perceived **no difference**. + +**Raster cost scales hard with window pixel count**, and the user runs the app +fullscreen on the 4K monitor. Measured under a nested X server: + +| Window | Achieved | build p50/p90 | raster p50/p90 | +|---|---|---|---| +| 1280x720 | 68.5 fps | 0.30 / 0.40 ms | 1.50 / 2.00 ms | +| 3840x2160 | 19.9 fps | 0.40 / 0.50 ms | 8.40 / 44.60 ms | + +Those first 4K figures came from plain (software) Xephyr and overstated raster. +Repeated **fullscreen on the real 3840x2160 monitor**: + +| Window (real display) | Achieved | build p50 | raster p50 | +|---|---|---|---| +| 1280x720 | 68.6 fps | 0.30 ms | 1.50 ms | +| 3840x2160 | **18.6 fps** | 0.30 ms | **2.70 ms** | + +This is the key result, and it is *not* a rasterisation wall. At 4K each frame +costs ~3 ms of a 16.7 ms budget, yet the app achieves 18.6 fps (~54 ms per +frame). Roughly **50 ms per frame is dead time** — neither build nor raster. +The bottleneck is the presentation/swap path, which is also why Impeller never +helped: Impeller changes rasterisation, not presentation. + +### It is Flutter, not todo + +Run through an identical harness, the **stock `flutter create` counter app** — +one spinner on a blank Scaffold, zero todo code — collapses the same way: + +| App | 1280x720 | 3840x2160 | +|---|---|---| +| stock Flutter counter | 68.5 fps | **20.5 fps** | +| todo | 68.5 fps | **20.6 fps** | + +Flutter's Linux embedder manages ~20 fps rendering a **blank screen** at 4K. +Build time is 0.1-0.4 ms throughout. No application-level change can fix this. + +Size curve (harness), showing the cliff begins at 1440p: + +| size | Mpx | fps | frame cost | dead time | +|---|---|---|---|---| +| 1280x720 | 0.92 | 68.5 | 1.70 ms | 12.9 ms | +| 1920x1080 | 2.07 | 60.5 | 2.40 ms | 14.1 ms | +| 2560x1440 | 3.69 | 40.3 | 3.70 ms | 21.1 ms | +| 3840x2160 | 8.29 | 20.6 | 8.80 ms | 39.8 ms | + +Related upstream reports: +[#66463](https://github.com/flutter/flutter/issues/66463) (Linux desktop failure +based on window size on 4K monitors), +[#88996](https://github.com/flutter/flutter/issues/88996) (high frame latency on +Linux desktop). + +## Test harness (for iterating without taking over the user's screens) + +`Xephyr -glamor` runs a nested X server that renders through OpenGL on the real +GPU. Validated against ground truth: it reproduced the 4K symptom at 19.8 fps +versus the real display's 18.6 fps. It inflates raster attribution (7.2 ms vs +2.7 ms real), so use it for pass/fail, **not** for attributing where time goes. + +Plain `Xephyr` without `-glamor` is a software X server and is useless here: it +copies a ~33 MB framebuffer per frame at 4K and dominates the measurement. + +**The harness is validated for the Flutter desktop embedder only.** A Chrome +run inside it reported 10 fps at 4K; measuring the same page on the real display +gave 142.9 fps, confirming that number was a harness artifact (Chrome disables +GPU acceleration on an unrecognised nested display). Never benchmark a browser +inside it. + +## The decisive comparison: same content, same machine, real 4K display + +The identical spinner probe, rendered at 3840x2160 on the real monitor: + +| Renderer | Achieved at 4K | +|---|---| +| Flutter **Linux desktop embedder** | **20.5 fps** | +| Flutter **Web (CanvasKit) in Chrome** | **142.9 fps** (cadence p50 7.00 ms) | + +A **7x difference**, with the same framework and the same Dart code. This is not +a hardware limit, a driver limit, or an application limit — it is specific to +Flutter's Linux desktop embedder. It validates the web route at the size that +actually matters. + +## Why the 60 Hz cap exists + +`libflutter_linux_gtk.so` imports `gdk_monitor_get_refresh_rate` (and GTK +correctly reports 144.00 Hz here — verified independently), but imports **no** +`gdk_frame_clock_*` symbols; the engine falls back to a fixed 60 FPS vsync +waiter. + +This is a known, open upstream limitation: +[flutter/flutter#183703 — "[Proposal] Support high refresh rate (>60) on +Linux"](https://github.com/flutter/flutter/issues/183703). Opened 2026-03-14, +labelled **P2**, **no assignee, no linked PR, no milestone**. Linux is the last +Flutter platform without >60 Hz support. Related: +[#93058](https://github.com/flutter/flutter/issues/93058) (Windows, since +fixed), [#49757](https://github.com/flutter/flutter/issues/49757). + +## Implication for the "switch technology" question + +A rewrite in another language is **not** warranted by the evidence. The same +Flutter framework and the same Dart code reach ~143 fps on this exact machine +when built for web and run in Chrome. Only the Linux *desktop embedder* is +capped. + +The leading candidate is therefore **shipping the desktop app as a Flutter web +build in a Chrome `--app` window**, reusing the UI and domain code. + +**Validated at 4K on the real display: 142.9 fps vs the desktop embedder's +20.5 fps on identical content.** That settles the renderer question. + +What remains unproven is todo's *own* widget tree on web: the probe is a +spinner, and web CanvasKit raster plus WASM/JS build costs differ from the +desktop's 0.30/1.50 ms. Given desktop build time is only 0.3 ms and the web +renderer has 7x the headroom, this is very likely fine — but the proof-of- +concept should build the actual app and re-measure before the port is finished. + +What is reused is the UI and domain logic. The platform-adapter layer needs web +variants throughout: persistence durability under a browser profile, token +storage, device auth, sync-on-background (Page Visibility API), and the +launch mechanism (local server vs `file://`). + +Port surface (the storage seam is small, because `crdt_sync_dart` was designed +for it): + +- `LogStore` is deliberately `dart:io`-free and documented as web-usable; + `FileLogPersistence` is already isolated behind the `crdt_sync_io.dart` + entrypoint. `LogPersistence` is a two-method interface (`read()` / + `write(String)`), so a web implementation over IndexedDB/localStorage is + roughly 15 lines. +- `github_client.dart` has **no** `dart:io` and uses `package:http`; the GitHub + REST API sends permissive CORS headers, so sync works from a browser origin. +- `sqlite_crdt`/`sqflite_common_ffi` remain only for the one-time legacy DB + migration (`note_repository.dart:350`), which does not apply on web and can be + skipped behind `kIsWeb`. +- `path_provider` is only used to choose the file path for `FileLogPersistence`; + unnecessary on web. +- `shared_preferences`, `url_launcher`, `share_plus`, `file_selector`, + `flutter_secure_storage` all have web implementations. + +### Known costs of the web route (both are security regressions) + +1. **The token loses the OS keystore.** Commit `9841aec` deliberately moved the + GitHub token off plaintext and into the OS keyring via + `flutter_secure_storage`. A Chrome `--app` build cannot reach the keyring; + `flutter_secure_storage` on web falls back to WebCrypto-wrapped + `localStorage`. That is materially weaker than what is shipping today and + partially undoes that commit. +2. **Device-flow auth breaks.** GitHub's OAuth device-flow endpoints + (`github.com/login/device/code`) do not send CORS headers, so one-tap device + auth will not work from a browser origin. The settings screen's + paste-a-personal-access-token path would have to become the desktop auth + route — which also means a long-lived PAT living in the weaker storage above. + +These two stack, and they are the strongest argument against the web route. + +## Agreed plan (decided 2026-07-20) + +The desktop app moves to a Flutter **web** build launched in Chrome. Decisions +taken, so the next session can start from here: + +- **Auth:** simple PAT pasted into `localStorage` (via + `flutter_secure_storage_web`). The local-helper design that would have kept + the OS keyring and device-flow auth was considered and **rejected** for + simplicity. Consequences accepted: the token is no longer in the keyring, and + the AES-GCM wrapping is obfuscation only (the wrap key sits in the same + `localStorage`). + - Requires a **classic PAT with "No expiration"** to be a one-time paste. + - The launcher must pin a **fixed port** and a **fixed `--user-data-dir`**, + and call `navigator.storage.persist()`. Any of those changing loses the + token: `localStorage` is keyed by origin and lives in the Chrome profile. +- **The Linux desktop build is deleted**, not kept as a fallback. (Android is + unaffected and remains a normal Flutter mobile build.) +- **Launch:** a wrapper script serves the web build on a fixed port and opens + Chrome with `--app=http://localhost:`. +- **Done-condition:** the real todo app, fullscreen at 4K, sustains **>=100 fps** + while scrolling the notes list, and sync works against `kuhyx/syncs`. + +Still to be specified before coding: the `WebLogPersistence` implementation, +what replaces `path_provider`/`sqflite` on web (the legacy sqlite migration path +at `note_repository.dart:350` does not apply), packaging/`install_arch.sh` +changes, and how the desktop entry launches the wrapper. + +## Alternatives, if the web route is rejected + +Ranked by cost. All require re-implementing the UI, and the two non-Dart options +also require a third `crdt_sync` implementation (it exists today only in Python +and Dart). + +1. **Accept 60 fps.** Zero cost. The app is genuinely smooth *at* 60. +2. **Qt 6 / QML (C++).** Vulkan RHI, real 144 Hz, best NVIDIA/X11 record. Weeks. +3. **Rust + Slint.** Lightweight, but the highest total cost. + +Not recommended: **Tauri** — on Linux it renders via WebKitGTK, which has its +own NVIDIA/X11 DMABUF rendering bugs; it trades one renderer problem for +another. diff --git a/docs/flutter-rules-design.md b/docs/flutter-rules-design.md new file mode 100644 index 0000000..bdfbc52 --- /dev/null +++ b/docs/flutter-rules-design.md @@ -0,0 +1,144 @@ +# Flutter AI rules — Colour, type, documentation and accessibility + +Colour schemes, fonts, dartdoc conventions and accessibility. + +_Verbatim from [flutter/flutter docs/rules/rules.md](https://github.com/flutter/flutter/blob/main/docs/rules/rules.md), split across four files only to keep each under the repo's 250-line +cap. Re-fetch periodically to stay current; do not hand-edit._ + +#### Contrast Ratios + +* **WCAG Guidelines:** Aim to meet the Web Content Accessibility Guidelines + (WCAG) 2.1 standards. +* **Minimum Contrast:** + * **Normal Text:** A contrast ratio of at least **4.5:1**. + * **Large Text:** (18pt or 14pt bold) A contrast ratio of at least **3:1**. + +#### Palette Selection + +* **Primary, Secondary, and Accent:** Define a clear color hierarchy. +* **The 60-30-10 Rule:** A classic design rule for creating a balanced color scheme. + * **60%** Primary/Neutral Color (Dominant) + * **30%** Secondary Color + * **10%** Accent Color + +#### Complementary Colors + +* **Use with Caution:** They can be visually jarring if overused. +* **Best Use Cases:** They are excellent for accent colors to make specific + elements pop, but generally poor for text and background pairings as they can + cause eye strain. + +#### Example Palette + +* **Primary:** #0D47A1 (Dark Blue) +* **Secondary:** #1976D2 (Medium Blue) +* **Accent:** #FFC107 (Amber) +* **Neutral/Text:** #212121 (Almost Black) +* **Background:** #FEFEFE (Almost White) + +### Font Best Practices + +#### Font Selection + +* **Limit Font Families:** Stick to one or two font families for the entire + application. +* **Prioritize Legibility:** Choose fonts that are easy to read on screens of + all sizes. Sans-serif fonts are generally preferred for UI body text. +* **System Fonts:** Consider using platform-native system fonts. +* **Google Fonts:** For a wide selection of open-source fonts, use the + `google_fonts` package. + +#### Hierarchy and Scale + +* **Establish a Scale:** Define a set of font sizes for different text elements + (e.g., headlines, titles, body text, captions). +* **Use Font Weight:** Differentiate text effectively using font weights. +* **Color and Opacity:** Use color and opacity to de-emphasize less important + text. + +#### Readability + +* **Line Height (Leading):** Set an appropriate line height, typically **1.4x to + 1.6x** the font size. +* **Line Length:** For body text, aim for a line length of **45-75 characters**. +* **Avoid All Caps:** Do not use all caps for long-form text. + +#### Example Typographic Scale + +```dart +// In your ThemeData +textTheme: const TextTheme( + displayLarge: TextStyle(fontSize: 57.0, fontWeight: FontWeight.bold), + titleLarge: TextStyle(fontSize: 22.0, fontWeight: FontWeight.bold), + bodyLarge: TextStyle(fontSize: 16.0, height: 1.5), + bodyMedium: TextStyle(fontSize: 14.0, height: 1.4), + labelSmall: TextStyle(fontSize: 11.0, color: Colors.grey), +), +``` + +### Documentation + +* **`dartdoc`:** Write `dartdoc`-style comments for all public APIs. + + +#### Documentation Philosophy + +* **Comment wisely:** Use comments to explain why the code is written a certain + way, not what the code does. The code itself should be self-explanatory. +* **Document for the user:** Write documentation with the reader in mind. If you + had a question and found the answer, add it to the documentation where you + first looked. This ensures the documentation answers real-world questions. +* **No useless documentation:** If the documentation only restates the obvious + from the code's name, it's not helpful. Good documentation provides context + and explains what isn't immediately apparent. +* **Consistency is key:** Use consistent terminology throughout your + documentation. + +#### Commenting Style + +* **Use `///` for doc comments:** This allows documentation generation tools to + pick them up. +* **Start with a single-sentence summary:** The first sentence should be a + concise, user-centric summary ending with a period. +* **Separate the summary:** Add a blank line after the first sentence to create + a separate paragraph. This helps tools create better summaries. +* **Avoid redundancy:** Don't repeat information that's obvious from the code's + context, like the class name or signature. +* **Don't document both getter and setter:** For properties with both, only + document one. The documentation tool will treat them as a single field. + +#### Writing Style + +* **Be brief:** Write concisely. +* **Avoid jargon and acronyms:** Don't use abbreviations unless they are widely + understood. +* **Use Markdown sparingly:** Avoid excessive markdown and never use HTML for + formatting. +* **Use backticks for code:** Enclose code blocks in backtick fences, and + specify the language. + +#### What to Document + +* **Public APIs are a priority:** Always document public APIs. +* **Consider private APIs:** It's a good idea to document private APIs as well. +* **Library-level comments are helpful:** Consider adding a doc comment at the + library level to provide a general overview. +* **Include code samples:** Where appropriate, add code samples to illustrate usage. +* **Explain parameters, return values, and exceptions:** Use prose to describe + what a function expects, what it returns, and what errors it might throw. +* **Place doc comments before annotations:** Documentation should come before + any metadata annotations. + +### Accessibility (A11Y) +Implement accessibility features to empower all users, assuming a wide variety +of users with different physical abilities, mental abilities, age groups, +education levels, and learning styles. + +* **Color Contrast:** Ensure text has a contrast ratio of at least **4.5:1** + against its background. +* **Dynamic Text Scaling:** Test your UI to ensure it remains usable when users + increase the system font size. +* **Semantic Labels:** Use the `Semantics` widget to provide clear, descriptive + labels for UI elements. +* **Screen Reader Testing:** Regularly test your app with TalkBack (Android) and + VoiceOver (iOS). diff --git a/docs/flutter-rules-language.md b/docs/flutter-rules-language.md new file mode 100644 index 0000000..dc5b401 --- /dev/null +++ b/docs/flutter-rules-language.md @@ -0,0 +1,163 @@ +# Flutter AI rules — language, style and architecture + +Dart/Flutter style, package management, code quality, and how a project is laid out. + +_Verbatim from [flutter/flutter docs/rules/rules.md](https://github.com/flutter/flutter/blob/main/docs/rules/rules.md), split across four files only to keep each under the repo's 250-line +cap. Re-fetch periodically to stay current; do not hand-edit._ + +### Interaction Guidelines +* **User Persona:** Assume the user is familiar with programming concepts but + may be new to Dart. +* **Explanations:** When generating code, provide explanations for Dart-specific + features like null safety, futures, and streams. +* **Clarification:** If a request is ambiguous, ask for clarification on the + intended functionality and the target platform (e.g., command-line, web, + server). +* **Dependencies:** When suggesting new dependencies from `pub.dev`, explain + their benefits. +* **Formatting:** Use the `dart_format` tool to ensure consistent code + formatting. +* **Fixes:** Use the `dart_fix` tool to automatically fix many common errors, + and to help code conform to configured analysis options. +* **Linting:** Use the Dart linter with a recommended set of rules to catch + common issues. Use the `analyze_files` tool to run the linter. + +### Project Structure +* **Standard Structure:** Assumes a standard Flutter project structure with + `lib/main.dart` as the primary application entry point. + +### Flutter style guide +* **SOLID Principles:** Apply SOLID principles throughout the codebase. +* **Concise and Declarative:** Write concise, modern, technical Dart code. + Prefer functional and declarative patterns. +* **Composition over Inheritance:** Favor composition for building complex + widgets and logic. +* **Immutability:** Prefer immutable data structures. Widgets (especially + `StatelessWidget`) should be immutable. +* **State Management:** Separate ephemeral state and app state. Use a state + management solution for app state to handle the separation of concerns. +* **Widgets are for UI:** Everything in Flutter's UI is a widget. Compose + complex UIs from smaller, reusable widgets. +* **Navigation:** Use a modern routing package like `auto_route` or `go_router`. + For more guidelines around navigation, see the section on [routing](#routing). + +### Package Management +* **Pub Tool:** To manage packages, use the `pub` tool, if available. +* **External Packages:** If a new feature requires an external package, use the + `pub_dev_search` tool, if it is available. Otherwise, identify the most + suitable and stable package from pub.dev. +* **Adding Dependencies:** To add a regular dependency, use the `pub` tool, if + it is available. Otherwise, run `flutter pub add `. +* **Adding Dev Dependencies:** To add a development dependency, use the `pub` + tool, if it is available, with `dev:`. Otherwise, run `flutter + pub add dev:`. +* **Dependency Overrides:** To add a dependency override, use the `pub` tool, if + it is available, with `override::1.0.0`. Otherwise, run `flutter + pub add override::1.0.0`. +* **Removing Dependencies:** To remove a dependency, use the `pub` tool, if it + is available. Otherwise, run `dart pub remove `. + +### Code Quality +* **Code structure:** Adhere to maintainable code structure and separation of + concerns (e.g., UI logic separate from business logic). +* **Naming conventions:** Avoid abbreviations and use meaningful, consistent, + descriptive names for variables, functions, and classes. +* **Conciseness:** Write code that is as short as it can be while remaining + clear. +* **Simplicity:** Write straightforward code. Code that is clever or + obscure is difficult to maintain. +* **Error Handling:** Anticipate and handle potential errors. Don't let your + code fail silently. +* **Styling:** + * Line length: Lines should be 80 characters or fewer. + * Use `PascalCase` for classes, `camelCase` for + members/variables/functions/enums, and `snake_case` for files. +* **Functions:** + * Keep functions short and with a single purpose. + Strive for less than 20 lines. +* **Testing:** Write code with testing in mind. Use the `file`, `process`, and + `platform` packages, if appropriate, so you can inject in-memory and fake + versions of the objects. +* **Logging:** Use the `logging` package instead of `print`. + +### Dart Best Practices +* **Effective Dart:** Follow the official Effective Dart guidelines + (https://dart.dev/effective-dart) +* **Class Organization:** Define related classes within the same library file. + For large libraries, export smaller, private libraries from a single top-level + library. +* **Library Organization:** Group related libraries in the same folder. +* **API Documentation:** Add documentation comments to all public APIs, + including classes, constructors, methods, and top-level functions. +* **Comments:** Write clear comments for complex or non-obvious code. Avoid + over-commenting. +* **Trailing Comments:** Don't add trailing comments. +* **Async/Await:** Ensure proper use of `async`/`await` for asynchronous + operations with robust error handling. + * Use `Future`s, `async`, and `await` for asynchronous operations. + * Use `Stream`s for sequences of asynchronous events. +* **Null Safety:** Write code that is soundly null-safe. Leverage Dart's null + safety features. Avoid `!` unless the value is guaranteed to be non-null. +* **Pattern Matching:** Use pattern matching features where they simplify the + code. +* **Records:** Use records to return multiple types in situations where defining + an entire class is cumbersome. +* **Switch Statements:** Prefer using exhaustive `switch` statements or + expressions, which don't require `break` statements. +* **Exception Handling:** Use `try-catch` blocks for handling exceptions, and + use exceptions appropriate for the type of exception. Use custom exceptions + for situations specific to your code. +* **Arrow Functions:** Use arrow syntax for simple one-line functions. + +### Flutter Best Practices +* **Immutability:** Widgets (especially `StatelessWidget`) are immutable; when + the UI needs to change, Flutter rebuilds the widget tree. +* **Composition:** Prefer composing smaller widgets over extending existing + ones. Use this to avoid deep widget nesting. +* **Private Widgets:** Use small, private `Widget` classes instead of private + helper methods that return a `Widget`. +* **Build Methods:** Break down large `build()` methods into smaller, reusable + private Widget classes. +* **List Performance:** Use `ListView.builder` or `SliverList` for long lists to + create lazy-loaded lists for performance. +* **Isolates:** Use `compute()` to run expensive calculations in a separate + isolate to avoid blocking the UI thread, such as JSON parsing. +* **Const Constructors:** Use `const` constructors for widgets and in `build()` + methods whenever possible to reduce rebuilds. +* **Build Method Performance:** Avoid performing expensive operations, like + network calls or complex computations, directly within `build()` methods. + +### API Design Principles +When building reusable APIs, such as a library, follow these principles. + +* **Consider the User:** Design APIs from the perspective of the person who will + be using them. The API should be intuitive and easy to use correctly. +* **Documentation is Essential:** Good documentation is a part of good API + design. It should be clear, concise, and provide examples. + +### Application Architecture +* **Separation of Concerns:** Aim for separation of concerns similar to MVC/MVVM, with defined Model, + View, and ViewModel/Controller roles. +* **Logical Layers:** Organize the project into logical layers: + * Presentation (widgets, screens) + * Domain (business logic classes) + * Data (model classes, API clients) + * Core (shared classes, utilities, and extension types) +* **Feature-based Organization:** For larger projects, organize code by feature, + where each feature has its own presentation, domain, and data subfolders. This + improves navigability and scalability. + +### Lint Rules + +Include the package in the `analysis_options.yaml` file. Use the following +`analysis_options.yaml` file as a starting point: + +```yaml +include: package:flutter_lints/flutter.yaml + +linter: + rules: + # Add additional lint rules here: + # avoid_print: false + # prefer_single_quotes: true +``` diff --git a/docs/flutter-rules-state-and-data.md b/docs/flutter-rules-state-and-data.md new file mode 100644 index 0000000..0da5a36 --- /dev/null +++ b/docs/flutter-rules-state-and-data.md @@ -0,0 +1,154 @@ +# Flutter AI rules — state, routing and data + +State management, data flow, routing, JSON serialization and logging. + +_Verbatim from [flutter/flutter docs/rules/rules.md](https://github.com/flutter/flutter/blob/main/docs/rules/rules.md), split across four files only to keep each under the repo's 250-line +cap. Re-fetch periodically to stay current; do not hand-edit._ + +#### State Management +* **Built-in Solutions:** Prefer Flutter's built-in state management solutions. + Do not use a third-party package unless explicitly requested. +* **Streams:** Use `Streams` and `StreamBuilder` for handling a sequence of + asynchronous events. +* **Futures:** Use `Futures` and `FutureBuilder` for handling a single + asynchronous operation that will complete in the future. +* **ValueNotifier:** Use `ValueNotifier` with `ValueListenableBuilder` for + simple, local state that involves a single value. + + ```dart + // Define a ValueNotifier to hold the state. + final ValueNotifier _counter = ValueNotifier(0); + + // Use ValueListenableBuilder to listen and rebuild. + ValueListenableBuilder( + valueListenable: _counter, + builder: (context, value, child) { + return Text('Count: $value'); + }, + ); + ``` + +* **ChangeNotifier:** For state that is more complex or shared across multiple + widgets, use `ChangeNotifier`. +* **ListenableBuilder:** Use `ListenableBuilder` to listen to changes from a + `ChangeNotifier` or other `Listenable`. +* **MVVM:** When a more robust solution is needed, structure the app using the + Model-View-ViewModel (MVVM) pattern. +* **Dependency Injection:** Use simple manual constructor dependency injection + to make a class's dependencies explicit in its API, and to manage dependencies + between different layers of the application. +* **Provider:** If a dependency injection solution beyond manual constructor + injection is explicitly requested, `provider` can be used to make services, + repositories, or complex state objects available to the UI layer without tight + coupling (note: this document generally defaults against third-party packages + for state management unless explicitly requested). + +#### Data Flow +* **Data Structures:** Define data structures (classes) to represent the data + used in the application. +* **Data Abstraction:** Abstract data sources (e.g., API calls, database + operations) using Repositories/Services to promote testability. + +#### Routing +* **GoRouter:** Use the `go_router` package for declarative navigation, deep + linking, and web support. +* **GoRouter Setup:** To use `go_router`, first add it to your `pubspec.yaml` + using the `pub` tool's `add` command. + + ```dart + // 1. Add the dependency + // flutter pub add go_router + + // 2. Configure the router + final GoRouter _router = GoRouter( + routes: [ + GoRoute( + path: '/', + builder: (context, state) => const HomeScreen(), + routes: [ + GoRoute( + path: 'details/:id', // Route with a path parameter + builder: (context, state) { + final String id = state.pathParameters['id']!; + return DetailScreen(id: id); + }, + ), + ], + ), + ], + ); + + // 3. Use it in your MaterialApp + MaterialApp.router( + routerConfig: _router, + ); + ``` +* **Authentication Redirects:** Configure `go_router`'s `redirect` property to + handle authentication flows, ensuring users are redirected to the login screen + when unauthorized, and back to their intended destination after successful + login. + +* **Navigator:** Use the built-in `Navigator` for short-lived screens that do + not need to be deep-linkable, such as dialogs or temporary views. + + ```dart + // Push a new screen onto the stack + Navigator.push( + context, + MaterialPageRoute(builder: (context) => const DetailsScreen()), + ); + + // Pop the current screen to go back + Navigator.pop(context); + ``` + +#### Data Handling & Serialization +* **JSON Serialization:** Use `json_serializable` and `json_annotation` for + parsing and encoding JSON data. +* **Field Renaming:** When encoding data, use `fieldRename: FieldRename.snake` + to convert Dart's camelCase fields to snake_case JSON keys. + + ```dart + // In your model file + import 'package:json_annotation/json_annotation.dart'; + + part 'user.g.dart'; + + @JsonSerializable(fieldRename: FieldRename.snake) + class User { + final String firstName; + final String lastName; + + User({required this.firstName, required this.lastName}); + + factory User.fromJson(Map json) => _$UserFromJson(json); + Map toJson() => _$UserToJson(this); + } + ``` + + +#### Logging +* **Structured Logging:** Use the `log` function from `dart:developer` for + structured logging that integrates with Dart DevTools. + + ```dart + import 'dart:developer' as developer; + + // For simple messages + developer.log('User logged in successfully.'); + + // For structured error logging + try { + // ... code that might fail + } catch (e, s) { + developer.log( + 'Failed to fetch data', + name: 'myapp.network', + level: 1000, // SEVERE + error: e, + stackTrace: s, + ); + } + ``` + +### Code Generation diff --git a/docs/flutter-rules-testing-and-assets.md b/docs/flutter-rules-testing-and-assets.md new file mode 100644 index 0000000..4faf4c1 --- /dev/null +++ b/docs/flutter-rules-testing-and-assets.md @@ -0,0 +1,151 @@ +# Flutter AI rules — Code generation, testing, theming and assets + +Code generation, the testing guidance, and the visual-design, theming and asset rules. + +_Verbatim from [flutter/flutter docs/rules/rules.md](https://github.com/flutter/flutter/blob/main/docs/rules/rules.md), split across four files only to keep each under the repo's 250-line +cap. Re-fetch periodically to stay current; do not hand-edit._ + +* **Build Runner:** If the project uses code generation, ensure that + `build_runner` is listed as a dev dependency in `pubspec.yaml`. +* **Code Generation Tasks:** Use `build_runner` for all code generation tasks, + such as for `json_serializable`. +* **Running Build Runner:** After modifying files that require code generation, + run the build command: + + ```shell + dart run build_runner build --delete-conflicting-outputs + ``` + +### Testing +* **Running Tests:** To run tests, use the `run_tests` tool if it is available, + otherwise use `flutter test`. +* **Unit Tests:** Use `package:test` for unit tests. +* **Widget Tests:** Use `package:flutter_test` for widget tests. +* **Integration Tests:** Use `package:integration_test` for integration tests. +* **Assertions:** Prefer using `package:checks` for more expressive and readable + assertions over the default `matchers`. + +#### Testing Best practices +* **Convention:** Follow the Arrange-Act-Assert (or Given-When-Then) pattern. +* **Unit Tests:** Write unit tests for domain logic, data layer, and state + management. +* **Widget Tests:** Write widget tests for UI components. +* **Integration Tests:** For broader application validation, use integration + tests to verify end-to-end user flows. +* **integration_test package:** Use the `integration_test` package from the + Flutter SDK for integration tests. Add it as a `dev_dependency` in + `pubspec.yaml` by specifying `sdk: flutter`. +* **Mocks:** Prefer fakes or stubs over mocks. If mocks are absolutely + necessary, use `mockito` or `mocktail` to create mocks for dependencies. While + code generation is common for state management (e.g., with `freezed`), try to + avoid it for mocks. +* **Coverage:** Aim for high test coverage. + +### Visual Design & Theming +* **UI Design:** Build beautiful and intuitive user interfaces that follow + modern design guidelines. +* **Responsiveness:** Ensure the app is mobile responsive and adapts to + different screen sizes, working perfectly on mobile and web. +* **Navigation:** If there are multiple pages for the user to interact with, + provide an intuitive and easy navigation bar or controls. +* **Typography:** Stress and emphasize font sizes to ease understanding, e.g., + hero text, section headlines, list headlines, keywords in paragraphs. +* **Background:** Keep backgrounds flat and simple — no noise texture or + gradients. Follow the `unified-design-system` skill (safe-design-rules + rule 17: simple-on-complex); let content and typography carry visual + interest instead of background texture. +* **Shadows:** Follow the `unified-design-system` skill's shadow policy: no + shadows at all in dark UI (differentiate elevation with lighter surface + fills, not shadows); in light UI, shadows are for floating/overlay + elements only (menus, dialogs), never for inline cards, and never mixed + with a border on the same surface. +* **Icons:** Incorporate icons to enhance the user’s understanding and the + logical navigation of the app. +* **Interactive Elements:** Buttons/controls signal state via color and fill + changes, not a "glow" shadow — one depth technique per surface, per + `unified-design-system`. + +#### Theming +* **Centralized Theme:** Define a centralized `ThemeData` object to ensure a + consistent application-wide style. +* **Light and Dark Themes:** Implement support for both light and dark themes, + ideal for a user-facing theme toggle (`ThemeMode.light`, `ThemeMode.dark`, + `ThemeMode.system`). +* **Color Scheme Generation:** Do NOT use `ColorScheme.fromSeed` for this + app's palette — it can't reproduce hand-picked values exactly. Build + `ThemeData` from explicit `ColorScheme.dark(...)`/`.light(...)` using the + frozen, identical-everywhere tokens in the `unified-design-system` skill + (`~/.claude/skills/unified-design-system/references/tokens.md`). + + ```dart + final ThemeData lightTheme = ThemeData( + colorScheme: ColorScheme.fromSeed( + seedColor: Colors.deepPurple, + brightness: Brightness.light, + ), + // ... other theme properties + ); + ``` +* **Color Palette:** Include a wide range of color concentrations and hues in + the palette to create a vibrant and energetic look and feel. +* **Component Themes:** Use specific theme properties (e.g., `appBarTheme`, + `elevatedButtonTheme`) to customize the appearance of individual Material + components. +* **Custom Fonts:** For custom fonts, use the `google_fonts` package. Define a + `TextTheme` to apply fonts consistently. + + ```dart + // 1. Add the dependency + // flutter pub add google_fonts + + // 2. Define a TextTheme with a custom font + final TextTheme appTextTheme = TextTheme( + displayLarge: GoogleFonts.oswald(fontSize: 57, fontWeight: FontWeight.bold), + titleLarge: GoogleFonts.roboto(fontSize: 22, fontWeight: FontWeight.w500), + bodyMedium: GoogleFonts.openSans(fontSize: 14), + ); + ``` + +#### Assets and Images +* **Image Guidelines:** If images are needed, make them relevant and meaningful, + with appropriate size, layout, and licensing (e.g., freely available). Provide + placeholder images if real ones are not available. +* **Asset Declaration:** Declare all asset paths in your `pubspec.yaml` file. + + ```yaml + flutter: + uses-material-design: true + assets: + - assets/images/ + ``` + +* **Local Images:** Use `Image.asset` for local images from your asset + bundle. + + ```dart + Image.asset('assets/images/placeholder.png') + ``` +* **Network images:** Use NetworkImage for images loaded from the network. +* **Cached images:** For cached images, use NetworkImage a package like + `cached_network_image`. +* **Custom Icons:** Use `ImageIcon` to display an icon from an `ImageProvider`, + useful for custom icons not in the `Icons` class. +* **Network Images:** Use `Image.network` to display images from a URL, and + always include `loadingBuilder` and `errorBuilder` for a better user + experience. + + ```dart + // When using network images, always provide an errorBuilder. + Image.network( + 'https://picsum.photos/200/300', + loadingBuilder: (context, child, progress) { + if (progress == null) return child; + return const Center(child: CircularProgressIndicator()); + }, + errorBuilder: (context, error, stackTrace) { + return const Icon(Icons.error); + }, + ) + ``` + +### UI Theming and Styling Code diff --git a/docs/flutter-rules-ui.md b/docs/flutter-rules-ui.md new file mode 100644 index 0000000..fc0f67f --- /dev/null +++ b/docs/flutter-rules-ui.md @@ -0,0 +1,197 @@ +# Flutter AI rules — UI: theming, layout and overlays + +Material theming, WidgetStateProperty, layout, and overlays. + +_Verbatim from [flutter/flutter docs/rules/rules.md](https://github.com/flutter/flutter/blob/main/docs/rules/rules.md), split across four files only to keep each under the repo's 250-line +cap. Re-fetch periodically to stay current; do not hand-edit._ + +* **Responsiveness:** Use `LayoutBuilder` or `MediaQuery` to create responsive + UIs. +* **Text:** Use `Theme.of(context).textTheme` for text styles. +* **Text Fields:** Configure `textCapitalization`, `keyboardType`, and + `placeholder`. + +### Material Theming Best Practices + +#### Embrace `ThemeData` and Material 3 + +* **Use `ColorScheme.fromSeed()`:** Use this to generate a complete, harmonious + color palette for both light and dark modes from a single seed color. +* **Define Light and Dark Themes:** Provide both `theme` and `darkTheme` to your + `MaterialApp` to support system brightness settings seamlessly. +* **Centralize Component Styles:** Customize specific component themes (e.g., + `elevatedButtonTheme`, `cardTheme`, `appBarTheme`) within `ThemeData` to + ensure consistency. +* **Dark/Light Mode and Theme Toggle:** Implement support for both light and + dark themes using `theme` and `darkTheme` properties of `MaterialApp`. The + `themeMode` property can be dynamically controlled (e.g., via a + `ChangeNotifierProvider`) to allow for toggling between `ThemeMode.light`, + `ThemeMode.dark`, or `ThemeMode.system`. + +```dart +// main.dart +MaterialApp( + theme: ThemeData( + colorScheme: ColorScheme.fromSeed( + seedColor: Colors.deepPurple, + brightness: Brightness.light, + ), + textTheme: const TextTheme( + displayLarge: TextStyle(fontSize: 57.0, fontWeight: FontWeight.bold), + bodyMedium: TextStyle(fontSize: 14.0, height: 1.4), + ), + ), + darkTheme: ThemeData( + colorScheme: ColorScheme.fromSeed( + seedColor: Colors.deepPurple, + brightness: Brightness.dark, + ), + ), + home: const MyHomePage(), +); +``` + +#### Implement Design Tokens with `ThemeExtension` + +For custom styles that aren't part of the standard `ThemeData`, use +`ThemeExtension` to define reusable design tokens. + +* **Create a Custom Theme Extension:** Define a class that extends + `ThemeExtension` and include your custom properties. +* **Implement `copyWith` and `lerp`:** These methods are required for the + extension to work correctly with theme transitions. +* **Register in `ThemeData`:** Add your custom extension to the `extensions` + list in your `ThemeData`. +* **Access Tokens in Widgets:** Use `Theme.of(context).extension()!` + to access your custom tokens. + +```dart +// 1. Define the extension +@immutable +class MyColors extends ThemeExtension { + const MyColors({required this.success, required this.danger}); + + final Color? success; + final Color? danger; + + @override + ThemeExtension copyWith({Color? success, Color? danger}) { + return MyColors(success: success ?? this.success, danger: danger ?? this.danger); + } + + @override + ThemeExtension lerp(ThemeExtension? other, double t) { + if (other is! MyColors) return this; + return MyColors( + success: Color.lerp(success, other.success, t), + danger: Color.lerp(danger, other.danger, t), + ); + } +} + +// 2. Register it in ThemeData +theme: ThemeData( + extensions: const >[ + MyColors(success: Colors.green, danger: Colors.red), + ], +), + +// 3. Use it in a widget +Container( + color: Theme.of(context).extension()!.success, +) +``` + +#### Styling with `WidgetStateProperty` + +* **`WidgetStateProperty.resolveWith`:** Provide a function that receives a + `Set` and returns the appropriate value for the current state. +* **`WidgetStateProperty.all`:** A shorthand for when the value is the same for + all states. + +```dart +// Example: Creating a button style that changes color when pressed. +final ButtonStyle myButtonStyle = ButtonStyle( + backgroundColor: WidgetStateProperty.resolveWith( + (Set states) { + if (states.contains(WidgetState.pressed)) { + return Colors.green; // Color when pressed + } + return Colors.red; // Default color + }, + ), +); +``` + +### Layout Best Practices + +#### Building Flexible and Overflow-Safe Layouts + +##### For Rows and Columns + +* **`Expanded`:** Use to make a child widget fill the remaining available space + along the main axis. +* **`Flexible`:** Use when you want a widget to shrink to fit, but not + necessarily grow. Don't combine `Flexible` and `Expanded` in the same `Row` or + `Column`. +* **`Wrap`:** Use when you have a series of widgets that would overflow a `Row` + or `Column`, and you want them to move to the next line. + +##### For General Content + +* **`SingleChildScrollView`:** Use when your content is intrinsically larger + than the viewport, but is a fixed size. +* **`ListView` / `GridView`:** For long lists or grids of content, always use a + builder constructor (`.builder`). +* **`FittedBox`:** Use to scale or fit a single child widget within its parent. +* **`LayoutBuilder`:** Use for complex, responsive layouts to make decisions + based on the available space. + +#### Layering Widgets with Stack + +* **`Positioned`:** Use to precisely place a child within a `Stack` by anchoring it to the edges. +* **`Align`:** Use to position a child within a `Stack` using alignments like `Alignment.center`. + +#### Advanced Layout with Overlays + +* **`OverlayPortal`:** Use this widget to show UI elements (like custom + dropdowns or tooltips) "on top" of everything else. It manages the + `OverlayEntry` for you. + + ```dart + class MyDropdown extends StatefulWidget { + const MyDropdown({super.key}); + + @override + State createState() => _MyDropdownState(); + } + + class _MyDropdownState extends State { + final _controller = OverlayPortalController(); + + @override + Widget build(BuildContext context) { + return OverlayPortal( + controller: _controller, + overlayChildBuilder: (BuildContext context) { + return const Positioned( + top: 50, + left: 10, + child: Card( + child: Padding( + padding: EdgeInsets.all(8.0), + child: Text('I am an overlay!'), + ), + ), + ); + }, + child: ElevatedButton( + onPressed: _controller.toggle, + child: const Text('Toggle Overlay'), + ), + ); + } + } + ``` + +### Color Scheme Best Practices diff --git a/pubspec.yaml b/pubspec.yaml index 4df39bf..547aa09 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # In Windows, build-name is used as the major, minor, and patch parts # of the product and file versions while build-number is used as the build suffix. -version: 1.0.17+1 +version: 1.0.18+1 environment: sdk: ^3.12.2