This document provides instructions for AI coding agents (Claude, Copilot, etc.) working on the Cambridge Beer Festival app.
| Task | Command |
|---|---|
| Install dependencies | flutter pub get |
| Analyze code | flutter analyze --no-fatal-infos |
| Run tests | flutter test |
| Run app | flutter run |
| Build web | flutter build web --release --base-href "/cambridge-beer-festival-app/" |
This is a Flutter mobile/web app for browsing drinks at the Cambridge Beer Festival. Users can:
- Browse beers, ciders, meads, and wines
- Search and filter by category, name, brewery, or style
- Save favorites and rate drinks
- View brewery details
- State Management: Provider pattern with
ChangeNotifier - Data Layer: REST API via HTTP with JSON parsing
- Persistence: SharedPreferences for favorites/ratings
- UI: Material 3 with dark/light theme support
- Understand the structure: Review
lib/directory organization - Check existing patterns: Look at similar code for conventions
- Run tests first: Execute
flutter testto establish baseline - Analyze code: Run
flutter analyzeto check for issues
// Use single quotes
final message = 'Hello, world!';
// Prefer const constructors
const EdgeInsets.all(16);
// Use final for local variables
final drinks = provider.drinks;
// Widget keys in constructors
class MyWidget extends StatelessWidget {
const MyWidget({super.key}); // ✓ Good
}
// Sort child properties last
Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(...),
child: Text('...'), // child is last
)prefer_const_constructors: Use const when possibleprefer_const_declarations: Declare const values as constprefer_final_fields: Use final for private fieldsprefer_final_locals: Use final for local variablesavoid_print: Use debugPrint or proper loggingprefer_single_quotes: Use single quotes for stringssort_child_properties_last: child/children should be lastuse_key_in_widget_constructors: Always include key parameter
- Create
lib/screens/new_screen.dart - Export from
lib/screens/screens.dart - Add navigation from existing screens
Example:
// lib/screens/new_screen.dart
import 'package:flutter/material.dart';
class NewScreen extends StatelessWidget {
const NewScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('New Screen')),
body: const Center(child: Text('Content')),
);
}
}- Create
lib/models/new_model.dart - Include
fromJsonandtoJsonmethods - Export from
lib/models/models.dart - Add tests in
test/
Example:
class NewModel {
final String id;
final String name;
const NewModel({required this.id, required this.name});
factory NewModel.fromJson(Map<String, dynamic> json) {
return NewModel(
id: json['id'].toString(),
name: json['name'].toString(),
);
}
Map<String, dynamic> toJson() => {'id': id, 'name': name};
}- Create
lib/services/new_service.dart - Export from
lib/services/services.dart - Inject dependencies, don't use singletons
- Include
dispose()method for cleanup
- Add private field with underscore prefix
- Add public getter
- Add method to modify state
- Call
notifyListeners()after changes
import 'package:flutter_test/flutter_test.dart';
import 'package:cambridge_beer_festival/models/models.dart';
void main() {
group('ModelName', () {
test('fromJson parses correctly', () {
final json = {'id': '1', 'name': 'Test'};
final model = ModelName.fromJson(json);
expect(model.id, '1');
expect(model.name, 'Test');
});
test('handles missing optional fields', () {
// Test edge cases
});
});
}- Model JSON parsing (all field types)
- Edge cases (null, missing, wrong type)
- Provider state changes
- Service API calls (with mocks)
{
"festivals": [
{
"id": "cbf2025",
"name": "Cambridge Beer Festival 2025",
"dataBaseUrl": "https://..."
}
],
"defaultFestivalId": "cbf2025"
}{
"id": "brewery-123",
"name": "Brewery Name",
"location": "City",
"products": [
{
"id": "beer-1",
"name": "Beer Name",
"category": "beer",
"style": "IPA",
"abv": "5.5",
"dispense": "cask"
}
]
}Full API documentation and JSON schemas are in docs/api/:
- docs/api/README.md - Overview and quick reference
- docs/api/data-api-reference.md - Complete API reference
- docs/api/beer-list-schema.json - JSON Schema for beverage data
- docs/api/festival-registry-schema.json - JSON Schema for festival config
The web/data/festivals.json file is validated in CI against the schema:
cd scripts && npm install && node validate-festivals.js- Categories are dynamic from API data
- No code changes needed for new categories
- UI automatically shows all available categories
- Add enum value to
DrinkSortinbeer_provider.dart - Add case to
_applyFiltersAndSort()switch statement - Add option to sort dropdown in
drinks_screen.dart
- Create key constant for SharedPreferences
- Add to appropriate service (FavoritesService, RatingsService, or new)
- Load in
BeerProvider.initialize()
The project uses GitHub Actions for:
- Build: Analyze code, run tests, build web
- Deploy: Deploy to Cloudflare Pages (main branch and PRs)
- Worker: Deploy Cloudflare Worker when changed
.github/workflows/without explicit requestcloudflare-worker/without explicit requestpubspec.yamlversions without necessity- License or contribution guidelines
- Check barrel files: When adding new files, update exports
- Run analyze often: Catch issues early with
flutter analyze - Use const: Mark widgets as const for performance
- Handle null: API data may have missing fields
- Theme colors: Use
Theme.of(context)for consistent colors