Instructions for Claude AI when working on the Cambridge Beer Festival app.
Cambridge Beer Festival App - A Flutter application for browsing drinks at the Cambridge Beer Festival.
- Language: Dart/Flutter
- Flutter Version: 3.38.3+
- Dart SDK: >=3.2.0 <4.0.0
- State Management: Provider
- Platforms: Android, iOS, Web
# First-time setup
flutter pub get
# Verify code quality (run before and after changes)
flutter analyze --no-fatal-infos
# Run tests
flutter test
# Run the app locally
flutter run
# Build for web deployment
flutter build web --release --base-href "/cambridge-beer-festival-app/"This project uses Mise for managing development tools and task running.
Always use ./bin/mise (not plain mise) when running mise commands in this repository.
Tools are split across two environments to optimize for different use cases:
Base environment (mise.toml):
- Flutter 3.38.3 - Required in all environments (dev, CI, production)
- Node.js 21 - For http_server and Playwright e2e tests
- Tasks - Available in all environments
Developer environment (mise.dev.toml):
- Claude Code - Only needed for human developers
- Firebase Tools - For deployment and testing
For CI/Automated Environments:
# Install base tools (Flutter + Node)
./bin/mise installFor Developer Environments:
# Install base + developer tools (Flutter + Node + Claude + Firebase)
MISE_ENV=dev ./bin/mise install
# Or set permanently in your shell rc file:
export MISE_ENV=dev
./bin/mise installFlutter Installation libgit2 Error:
If you encounter a libgit2 error when mise tries to install Flutter:
Failed to configure the transport before connecting to "https://github.com/mise-plugins/mise-flutter.git"
Apply this workaround:
- Manually clone the Flutter plugin:
mkdir -p .mise/plugins
git clone https://github.com/mise-plugins/mise-flutter.git .mise/plugins/flutter- Add Flutter install directory to git safe directories:
git config --global --add safe.directory /home/user/cambridge-beer-festival-app/.mise/installs/flutter/3.38.3-stable- Disable Flutter analytics (first run only):
./bin/mise exec flutter -- flutter --disable-analytics- Retry the installation:
./bin/mise installNote: The .mise/ directory is already gitignored, so the manually cloned plugin won't be committed.
# Development
./bin/mise run dev # Run Flutter dev server
./bin/mise run analyze # Run code analysis
# Testing
./bin/mise run test # Run all Flutter tests
./bin/mise run coverage # Run tests with coverage
# Building & Serving
./bin/mise run build:web # Build release version for local testing/e2e
./bin/mise run build:web:prod # Build for production deployment
./bin/mise run serve:release # Serve release build with SPA routing
# Screenshot Testing (requires Playwright setup)
./bin/mise run check-page <url> <output.png> # Screenshot single page
./bin/mise run screenshots:batch # Screenshot multiple pages from configNote: CI workflows use Flutter directly (via flutter-action) and do not require mise.
The app uses path-based URLs (no # in URLs) for proper deep linking support. To test deep links:
1. Setup Playwright (first time only):
MISE_ENV=dev ./bin/mise run playwright-setup2. Build and serve the release version:
# Build release version
./bin/mise run build:web
# Serve with SPA routing (in background or separate terminal)
./bin/mise run serve:release &3. Test all configured deep links:
# Captures screenshots of all URLs in screenshots.config.json
./bin/mise run screenshots:batchCustomizing URLs to test:
Edit screenshots.config.json to add/modify test URLs:
[
{ "path": "/", "name": "home" },
{ "path": "/brewery/[id]", "name": "brewery-detail" },
{ "path": "/drink/[id]", "name": "drink-detail" },
{ "path": "/style/IPA", "name": "style-ipa" }
]Why release build for testing?
- Flutter dev server has issues with multiple Playwright sessions
- Release build is stable and reliable for automated testing
serve:releaseincludes--proxyflag for proper SPA routing (required for deep links)
This project includes a Dev Container configuration for consistent development environments using VS Code, GitHub Codespaces, or any devcontainer-compatible tool.
VS Code:
- Install the Dev Containers extension
- Open the project in VS Code
- Click "Reopen in Container" when prompted (or use Command Palette: "Dev Containers: Reopen in Container")
- Wait for the container to build and tools to install
GitHub Codespaces:
- Click "Code" → "Create codespace on main" in GitHub
- Wait for the environment to initialize
The devcontainer automatically installs and configures:
- Flutter 3.38.3 - From base mise.toml
- Node.js 21 - For http_server and Playwright e2e tests
- Claude Code - AI development assistant
- Firebase Tools - For deployment
- VS Code Extensions:
- Dart & Flutter support
- Mise integration
The devcontainer uses:
- Base Image: Ubuntu (official Microsoft image)
- Mise Feature:
ghcr.io/devcontainers-extra/features/mise:1 - Environment:
MISE_ENV=dev(developer tools) - Persistent Storage: Mise cache persisted across container rebuilds
.devcontainer/devcontainer.json- Container configurationmise.toml- Base tools (Flutter, Node)mise.dev.toml- Developer-specific tools (Claude, Firebase)
To modify the devcontainer:
- Edit
.devcontainer/devcontainer.jsonfor VS Code settings/extensions - Edit
mise.tomlormise.dev.tomlfor tool versions - Rebuild container: Command Palette → "Dev Containers: Rebuild Container"
lib/
├── main.dart # Entry point, app setup, home navigation
├── models/ # Data classes (Drink, Product, Producer, Festival)
├── providers/ # State management (BeerProvider)
├── screens/ # Full-page UI components
├── services/ # API calls and storage
└── widgets/ # Reusable UI components
test/ # Unit and widget tests
web/ # Web-specific assets
cloudflare-worker/ # API proxy worker
When writing or modifying Dart code:
- Use single quotes:
'string'not"string" - Use
constconstructors where possible - Use
finalfor local variables - Include
{super.key}in widget constructors - Sort
child/childrenproperties last in widgets - Avoid
print()- usedebugPrint()if needed - Add new files to barrel exports (e.g.,
models.dart) - Add
Semanticswidgets for interactive elements (buttons, filters, navigation) - Provide meaningful labels for screen readers (see Accessibility Requirements below)
- Test with large text settings (ensure no overflow at 200% scale)
CRITICAL: This app must be accessible to all users, including those using screen readers, large text, or other assistive technologies. Accessibility is NOT optional.
📖 For complete implementation details, see docs/ACCESSIBILITY.md
- WCAG 2.1 Level AA - Web Content Accessibility Guidelines
- ADA - Americans with Disabilities Act (US)
- Section 508 - US Federal accessibility standards
- Perceivable - Users can perceive the information being presented
- Operable - Users can operate the interface with various input methods
- Understandable - Information and UI operation are understandable
- Robust - Content works with current and future assistive technologies
Every interactive element MUST have a Semantics widget with:
- label - What the element is (e.g., "Add to favorites button")
- hint (optional) - How to use it (e.g., "Double tap to toggle")
- value (optional) - Current state (e.g., "3 out of 5 stars")
// ❌ BAD - No accessibility
IconButton(
icon: Icon(Icons.favorite),
onPressed: () => toggleFavorite(),
)
// ✅ GOOD - Screen reader accessible
Semantics(
label: isFavorite ? 'Remove from favorites' : 'Add to favorites',
button: true,
hint: 'Double tap to toggle',
child: IconButton(
icon: Icon(isFavorite ? Icons.favorite : Icons.favorite_border),
onPressed: () => toggleFavorite(),
),
)// ✅ GOOD - Descriptive labels for filters
Semantics(
label: 'Filter by $styleName',
value: isSelected ? 'Selected' : 'Not selected',
button: true,
child: FilterChip(
label: Text(styleName),
selected: isSelected,
onSelected: (value) => onStyleToggled(styleName),
),
)// ✅ GOOD - Summary of card content
Semantics(
label: '${drink.name}, ${drink.abv}% ABV, by ${drink.breweryName}',
hint: 'Double tap for details',
button: true,
child: InkWell(
onTap: () => navigateToDetail(drink),
child: DrinkCard(drink: drink),
),
)// ✅ GOOD - Rating with context
Semantics(
label: 'Rate this drink',
value: '$rating out of 5 stars',
hint: 'Tap a star to rate from 1 to 5',
child: Row(children: starWidgets),
)// ✅ GOOD - Clear navigation labels
NavigationDestination(
icon: Semantics(
label: 'Drinks tab, browse all festival drinks',
child: Icon(Icons.local_bar),
),
label: 'Drinks',
)// ✅ GOOD - TextField already has built-in semantics via decoration
TextField(
controller: _searchController,
decoration: InputDecoration(
hintText: 'Search drinks, breweries, styles...', // Used by screen readers
label: Text('Search'), // Explicit label
prefixIcon: Icon(Icons.search),
suffixIcon: Semantics(
label: 'Clear search',
button: true,
child: IconButton(
icon: Icon(Icons.close),
onPressed: () => clearSearch(),
),
),
),
)When adding or modifying UI:
- Add
Semanticslabels to all interactive elements (buttons, chips, cards) - Test with TalkBack (Android) or VoiceOver (iOS)
- Enable: Settings → Accessibility → TalkBack/VoiceOver
- Navigate using swipe gestures
- Verify all elements announce correctly
- Test with large text (200% scale)
- Android: Settings → Display → Font size → Largest
- iOS: Settings → Display & Brightness → Text Size
- Verify no text overflow or clipped content
- Verify color contrast (4.5:1 minimum for text)
- Use WebAIM Contrast Checker or browser dev tools
- Check buttons, icons, and text on all backgrounds
- Test keyboard navigation (web/desktop)
- Verify logical tab order
- Ensure all actions accessible via keyboard
High Priority:
lib/widgets/drink_card.dart- Drink cards, favorite buttonslib/screens/drinks_screen.dart- Filter buttons, search, sort controlslib/screens/festival_info_screen.dart- Map and website buttonslib/main.dart- Bottom navigation barlib/widgets/star_rating.dart- Star rating widgets
Medium Priority:
lib/screens/drink_detail_screen.dart- Detail view interactionslib/screens/brewery_screen.dart- Brewery details
- Flutter Accessibility Guide
- Material Design Accessibility
- WCAG 2.1 Quick Reference
- WebAIM Contrast Checker
- Android: TalkBack, Accessibility Scanner app
- iOS: VoiceOver, Accessibility Inspector
- Web: NVDA, JAWS, ChromeVox, axe DevTools
- Flutter:
flutter test --enable-semantics
API data types can vary. Always handle type variations:
// ABV can be String, int, or double
final abvValue = json['abv'];
double parsedAbv;
if (abvValue is num) {
parsedAbv = abvValue.toDouble();
} else if (abvValue is String) {
parsedAbv = double.tryParse(abvValue) ?? 0.0;
} else {
parsedAbv = 0.0;
}- Festival: Beer festival event with API data URL
- Producer: Brewery/cidery with location and products list
- Product: Individual beverage with ABV, style, category
- Drink: Combines Product + Producer for display purposes
The app uses BeerProvider for all state:
// Reading state (triggers rebuild on changes)
final provider = context.watch<BeerProvider>();
final drinks = provider.drinks;
// One-time access (no rebuild)
final provider = context.read<BeerProvider>();
provider.setCategory('beer');initialize()- Load festivals and set up storageloadDrinks()- Fetch drinks from current festivalsetFestival(Festival)- Change active festivalsetCategory(String?)- Filter by categorysetSearchQuery(String)- Filter by search texttoggleFavorite(Drink)- Toggle favorite statussetRating(Drink, int)- Set drink rating
When adding features or fixing bugs:
- Check if existing tests cover the area
- Add tests for new functionality
- Ensure all tests pass:
flutter test
Tests go in test/ mirroring lib/ structure:
lib/models/drink.dart→test/models_test.dart
Base URL: https://data.cambeerfestival.app
Endpoints:
/{festivalId}/beer.json- Beers/{festivalId}/cider.json- Ciders/{festivalId}/perry.json- Perry/{festivalId}/mead.json- Meads/{festivalId}/wine.json- Wines/{festivalId}/international-beer.json- International beers/{festivalId}/low-no.json- Low/no alcohol
Response Format: Array of Producer objects, each containing products
Full API documentation and JSON schemas are available 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
These schemas define the expected API response structure and can be used for validation.
- Create
lib/screens/my_screen.dart:
import 'package:flutter/material.dart';
class MyScreen extends StatelessWidget {
const MyScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('My Screen')),
body: const Center(child: Text('Content')),
);
}
}- Export in
lib/screens/screens.dart:
export 'my_screen.dart';- Add private field:
String? _myField; - Add getter:
String? get myField => _myField; - Add setter method:
void setMyField(String? value) {
_myField = value;
notifyListeners();
}After making changes:
flutter analyze --no-fatal-infos- Check for issuesflutter test- Run all tests- Review changes for const/final usage
- Verify barrel exports are updated
- GitHub Actions workflows (
.github/workflows/) - Cloudflare Worker (
cloudflare-worker/) - Package versions in
pubspec.yaml - Analysis rules in
analysis_options.yaml