214 lines
9 KiB
Markdown
214 lines
9 KiB
Markdown
# LEARNING-SPEC.md
|
||
|
||
context for any agent writing flutter code for eutopic.
|
||
|
||
## who is reading this code
|
||
|
||
a junior developer. no flutter or dart experience. basic understanding of frontend concepts (html/css level). goal: understand the code well enough to write business logic, modify screens, and eventually own the codebase. not a throwaway learning exercise — this is the real app.
|
||
|
||
## annotation requirements
|
||
|
||
every file must include three layers of annotation. this is non-negotiable.
|
||
|
||
### 1. file-top architecture comment
|
||
|
||
a short paragraph (3–6 sentences) at the top of every file, inside a block comment. must explain:
|
||
- what this file is and what it does
|
||
- why it exists as a separate file (not just "it's a widget")
|
||
- how it connects to the rest of the app (what calls it, what it calls)
|
||
|
||
example format:
|
||
```dart
|
||
/*
|
||
* feed_screen.dart
|
||
*
|
||
* the main home feed. this is what the user sees after login — a chronological
|
||
* list of photo posts from people they follow. it lives at the '/' route and is
|
||
* the first tab in the bottom nav bar.
|
||
*
|
||
* this screen owns fetching the post list from the api and passing individual
|
||
* posts down to PostCard widgets. it does not know how to render a post — that
|
||
* is PostCard's job. separation of concerns: screen = data, widget = display.
|
||
*/
|
||
```
|
||
|
||
### 2. boilerplate explanation comments
|
||
|
||
every structural/architectural decision in the boilerplate must have a comment explaining *why*, not just *what*. target: someone who has never seen flutter before can read this file and understand the pattern.
|
||
|
||
things that always need explanation:
|
||
- `StatelessWidget` vs `StatefulWidget` — explain the difference and why this file uses one over the other
|
||
- `BuildContext context` — what it is and why it's passed everywhere
|
||
- `const` constructors — why they exist, what they do for performance
|
||
- `@override` — what it means in dart
|
||
- `super.key` — what keys are and why the constructor takes one
|
||
- `Widget build(BuildContext context)` — what this method is and when flutter calls it
|
||
- any `initState` / `dispose` usage — explain the lifecycle
|
||
- any `setState(() {})` — explain what it triggers and why it's needed
|
||
- named parameters with `{}` braces vs positional — explain the difference
|
||
- `required` keyword — explain what it enforces
|
||
- `?` nullable types — explain dart's null safety model briefly on first use per file
|
||
|
||
### 3. logic TODO blocks
|
||
|
||
for every section the developer will fill in themselves, write a TODO comment that includes:
|
||
- a plain-english description of what needs to happen
|
||
- a complete dart syntax example showing the pattern to use (as a comment)
|
||
- any gotchas or things to watch out for
|
||
|
||
format:
|
||
```dart
|
||
// TODO: [plain english description]
|
||
//
|
||
// example:
|
||
// [complete working dart code snippet]
|
||
//
|
||
// note: [any gotcha or important thing to know]
|
||
```
|
||
|
||
the example must be real, working dart — not pseudocode. it should be close enough to the actual task that the developer can adapt it directly.
|
||
|
||
## hard sections — write fully, annotate heavily
|
||
|
||
these three areas are too complex for a junior developer to implement from a TODO. write them completely, but annotate every non-obvious line as if explaining to someone who has never seen async dart or flutter internals:
|
||
|
||
1. **tus resumable upload** (`shared/services/upload_service.dart`)
|
||
- background isolates, tus_client package usage, progress callbacks
|
||
- explain what an isolate is, why uploads need one, what happens without it
|
||
|
||
2. **go_router deep-link invite handling** (`shared/router/app_router.dart`)
|
||
- the `redirect` callback, how deep links arrive, how the invite code is extracted from the uri
|
||
- explain what a deep link is and the ios/android app link mechanism briefly
|
||
|
||
3. **story auto-advance timer + gesture layer** (`features/stories/story_viewer_screen.dart`)
|
||
- `Timer.periodic`, `AnimationController` for the progress bar, `GestureDetector` for tap-left/tap-right and swipe-down-to-exit
|
||
- explain the animation controller lifecycle and why dispose() matters here
|
||
|
||
## dart syntax reference (include in every file)
|
||
|
||
every file must include this block, verbatim, near the top after the file-top architecture comment. it is a standing reference so the developer never needs to leave the file to look up basic syntax.
|
||
|
||
```dart
|
||
/*
|
||
* dart syntax reference — patterns used in this file
|
||
*
|
||
* variables
|
||
* final String name = 'ada'; // runtime constant — set once, never reassigned
|
||
*
|
||
* const int max = 10; // compile-time constant — value must be known at build time
|
||
*
|
||
* String? bio; // nullable — this variable can be null (dart null safety)
|
||
*
|
||
* late String token; // late — will be assigned before first use, not at declaration
|
||
*
|
||
* functions
|
||
* String greet(String name) { return 'hi $name'; } // regular function
|
||
*
|
||
* String greet(String name) => 'hi $name'; // arrow shorthand — same as above, one expression only
|
||
*
|
||
* void log({required String msg}) { ... } // named parameter — caller writes: log(msg: 'x')
|
||
*
|
||
* void log({String msg = 'hello'}) { ... } // named parameter with default value
|
||
*
|
||
* void log(String msg) { ... } // positional parameter — caller writes: log('x')
|
||
*
|
||
* async
|
||
* Future<String> fetchName() async { // async function — returns a Future (like a JS Promise)
|
||
* final result = await apiCall(); // await pauses here until the Future resolves
|
||
* return result;
|
||
* }
|
||
*
|
||
* lists & maps
|
||
* final items = <String>['a', 'b']; // typed list
|
||
*
|
||
* final map = <String, int>{'apples': 3}; // typed map (like a JS object/dict)
|
||
*
|
||
* for (final item in items) { print(item); } // for-in loop over a list
|
||
*
|
||
* items.map((x) => x.toUpperCase()).toList() // transform every item (like JS .map())
|
||
*
|
||
* items.where((x) => x != 'a').toList() // keep items matching condition (like JS .filter())
|
||
*
|
||
* classes
|
||
* class Post {
|
||
* final String id;
|
||
* final String? caption; // optional field — may be null
|
||
* const Post({required this.id, this.caption}); // const constructor, named params
|
||
* }
|
||
*
|
||
* null safety
|
||
* bio?.length // safe access — returns null if bio is null, not an error
|
||
*
|
||
* bio ?? 'no bio' // fallback — use 'no bio' if bio is null
|
||
*
|
||
* bio! // force-unwrap — crashes if null. avoid unless certain.
|
||
*
|
||
* control flow
|
||
* if (x != null) { ... } else { ... }
|
||
*
|
||
* final label = isOwn ? 'you' : user.name; // ternary — shorthand if/else (same as JS)
|
||
*
|
||
* switch (tag) {
|
||
* case 'event': ... break;
|
||
* default: ...
|
||
* }
|
||
*
|
||
* string interpolation
|
||
* 'hello $name' // insert a variable directly
|
||
*
|
||
* 'count: ${list.length}' // insert an expression — use braces when it's more than a variable
|
||
*/
|
||
```
|
||
|
||
this block should appear in every file. agents must not remove or shorten it — the developer is learning dart as they go and will refer to it repeatedly.
|
||
|
||
## style rules
|
||
|
||
- use lowercase prose in comments (matches the project's communication style)
|
||
- no marketing language or enthusiasm ("great!", "easy!", "simply")
|
||
- be direct. if something is genuinely complex, say so — don't oversimplify
|
||
- prefer short sentences over long ones
|
||
- comment *why* over *what* — the code shows what, the comment shows why
|
||
|
||
## file order
|
||
|
||
write files in this order so concepts build on each other:
|
||
|
||
1. `lib/main.dart`
|
||
2. `lib/app.dart`
|
||
3. `lib/core/theme/app_colors.dart`
|
||
4. `lib/core/theme/app_theme.dart`
|
||
5. `lib/shared/models/` — all model files
|
||
6. `lib/core/router/app_router.dart` — hard section
|
||
7. `lib/shared/services/api_client.dart`
|
||
8. `lib/shared/services/auth_service.dart`
|
||
9. `lib/shared/services/upload_service.dart` — hard section
|
||
10. `lib/core/widgets/bottom_nav_bar.dart`
|
||
11. `lib/shared/widgets/` — shared widgets (photo_viewer, emoji_picker_sheet, loading_indicator, error_view)
|
||
12. `lib/features/feed/` — full feature, end to end (reference pattern for all other features)
|
||
13. `lib/features/onboarding/`
|
||
14. `lib/features/compose/`
|
||
15. `lib/features/posters/`
|
||
16. `lib/features/stories/` — hard section (story_viewer_screen.dart)
|
||
17. `lib/features/profile/`
|
||
18. `lib/features/settings/`
|
||
19. `lib/features/feedback/`
|
||
|
||
## project context
|
||
|
||
- app: eutopic — photo-first social app, invite-only, no notifications, no DMs, no algorithm
|
||
- stack: flutter (dart), go_router, tus_client, lottie animations
|
||
- folder structure: feature-first (see `docs/ARCHITECTURE.md` — frontend section)
|
||
- full concept: `docs/CONCEPT.md`
|
||
- screen/navigation spec: `model-tests/02-flutter-folder-structure.md`
|
||
- no notification-related code anywhere — this is a hard constraint
|
||
- no push alerts, no unread badges, no FCM/APNs
|
||
|
||
## what counts as done
|
||
|
||
a file is complete when:
|
||
- it compiles without errors
|
||
- all three annotation layers are present
|
||
- every TODO block has a working dart example
|
||
- hard sections are fully implemented with line-level annotation
|
||
- no notification-related imports or widgets exist anywhere
|