Split CLAUDE.md and the performance doc; the whole repo is under the cap

CLAUDE.md 925 -> 165. 789 of those lines were a verbatim paste of upstream's
flutter/flutter rules.md -- 85% of an always-loaded file given over to a
document nobody edits. It moves to five topic files under docs/, linked
rather than @-imported: inlining it back would pass the cap while keeping it
in context on every turn, which is the cost the cap exists to remove.

Every `###`/`####` heading in the upstream block survives the split; the two
that do not are its own wrapper headings, replaced by the per-file titles
(verified by diffing the sorted heading lists).

desktop-performance-findings.md 278 -> 73 + 216. It was already narratively
two-part; the author signposted "Part 2" explicitly at the seam.

Also updates two things that had drifted: the suite is 313 tests at 100%
coverage (not 238), and Conventions now documents the cap, where the gate
lives, and the two patterns used to split without breaking callers
(re-export barrels, and `part` files where a private State must stay in one
library).

`bash ~/utils/scripts/check_file_length.sh --all` now exits 0.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Krzysztof Rudnicki 2026-08-14 16:46:54 +02:00
parent 95c358cb5c
commit 25e2c9bb20
9 changed files with 1058 additions and 998 deletions

818
CLAUDE.md
View File

@ -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 <package_name>`.
* **Adding Dev Dependencies:** To add a development dependency, use the `pub`
tool, if it is available, with `dev:<package name>`. Otherwise, run `flutter
pub add dev:<package_name>`.
* **Dependency Overrides:** To add a dependency override, use the `pub` tool, if
it is available, with `override:<package name>:1.0.0`. Otherwise, run `flutter
pub add override:<package_name>:1.0.0`.
* **Removing Dependencies:** To remove a dependency, use the `pub` tool, if it
is available. Otherwise, run `dart pub remove <package_name>`.
### 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<int> _counter = ValueNotifier<int>(0);
// Use ValueListenableBuilder to listen and rebuild.
ValueListenableBuilder<int>(
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: <RouteBase>[
GoRoute(
path: '/',
builder: (context, state) => const HomeScreen(),
routes: <RouteBase>[
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<String, dynamic> json) => _$UserFromJson(json);
Map<String, dynamic> 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<T>` 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<MyColors>()!`
to access your custom tokens.
```dart
// 1. Define the extension
@immutable
class MyColors extends ThemeExtension<MyColors> {
const MyColors({required this.success, required this.danger});
final Color? success;
final Color? danger;
@override
ThemeExtension<MyColors> copyWith({Color? success, Color? danger}) {
return MyColors(success: success ?? this.success, danger: danger ?? this.danger);
}
@override
ThemeExtension<MyColors> lerp(ThemeExtension<MyColors>? 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 <ThemeExtension<dynamic>>[
MyColors(success: Colors.green, danger: Colors.red),
],
),
// 3. Use it in a widget
Container(
color: Theme.of(context).extension<MyColors>()!.success,
)
```
#### Styling with `WidgetStateProperty`
* **`WidgetStateProperty.resolveWith`:** Provide a function that receives a
`Set<WidgetState>` 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<Color>(
(Set<WidgetState> 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<MyDropdown> createState() => _MyDropdownState();
}
class _MyDropdownState extends State<MyDropdown> {
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.

View File

@ -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:<port>`.
- **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).

View File

@ -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:<port>`.
- **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.

View File

@ -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).

View File

@ -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 <package_name>`.
* **Adding Dev Dependencies:** To add a development dependency, use the `pub`
tool, if it is available, with `dev:<package name>`. Otherwise, run `flutter
pub add dev:<package_name>`.
* **Dependency Overrides:** To add a dependency override, use the `pub` tool, if
it is available, with `override:<package name>:1.0.0`. Otherwise, run `flutter
pub add override:<package_name>:1.0.0`.
* **Removing Dependencies:** To remove a dependency, use the `pub` tool, if it
is available. Otherwise, run `dart pub remove <package_name>`.
### 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
```

View File

@ -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<int> _counter = ValueNotifier<int>(0);
// Use ValueListenableBuilder to listen and rebuild.
ValueListenableBuilder<int>(
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: <RouteBase>[
GoRoute(
path: '/',
builder: (context, state) => const HomeScreen(),
routes: <RouteBase>[
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<String, dynamic> json) => _$UserFromJson(json);
Map<String, dynamic> 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

View File

@ -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

197
docs/flutter-rules-ui.md Normal file
View File

@ -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<T>` 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<MyColors>()!`
to access your custom tokens.
```dart
// 1. Define the extension
@immutable
class MyColors extends ThemeExtension<MyColors> {
const MyColors({required this.success, required this.danger});
final Color? success;
final Color? danger;
@override
ThemeExtension<MyColors> copyWith({Color? success, Color? danger}) {
return MyColors(success: success ?? this.success, danger: danger ?? this.danger);
}
@override
ThemeExtension<MyColors> lerp(ThemeExtension<MyColors>? 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 <ThemeExtension<dynamic>>[
MyColors(success: Colors.green, danger: Colors.red),
],
),
// 3. Use it in a widget
Container(
color: Theme.of(context).extension<MyColors>()!.success,
)
```
#### Styling with `WidgetStateProperty`
* **`WidgetStateProperty.resolveWith`:** Provide a function that receives a
`Set<WidgetState>` 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<Color>(
(Set<WidgetState> 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<MyDropdown> createState() => _MyDropdownState();
}
class _MyDropdownState extends State<MyDropdown> {
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

View File

@ -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