learning spec; flutter FE arch (repo/file structure)
This commit is contained in:
parent
0fce0b62bb
commit
7c6d101756
7 changed files with 774 additions and 12 deletions
214
LEARNING-SPEC.md
Normal file
214
LEARNING-SPEC.md
Normal file
|
|
@ -0,0 +1,214 @@
|
|||
# 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
|
||||
37
README.md
37
README.md
|
|
@ -2,15 +2,36 @@
|
|||
|
||||
*a small good place. social media that visits you when you visit it.*
|
||||
|
||||
photo-first social app for small trusted communities. features:
|
||||
- profile with bio and profile picture
|
||||
- slide posts (carousel)
|
||||
- stories
|
||||
- emoji reactions
|
||||
- "posters" event page, like a bulletin board. local events, informational posts, fundraisers
|
||||
- chronological feed, no algorithm, no notifications, no tracking. invite-only.
|
||||
photo-first social app for small trusted communities — an alternative to instagram that doesn't track you, notify you, or optimize for your attention. built for real people who already trust each other, not strangers building an audience.
|
||||
|
||||
→ [docs/CONCEPT.md](docs/CONCEPT.md) for full concept, stack, and design decisions.
|
||||
## what it does
|
||||
|
||||
- **feed** — chronological, photos only, up to 10 per post. no algorithm, no ranking.
|
||||
- **stories** — 10s auto-advance, 24h expiry, no replies.
|
||||
- **reactions** — the poster picks up to 5 emoji for their post; open picker as fallback.
|
||||
- **profiles** — username, display name, avatar, bio.
|
||||
- **posters** — event page / bulletin board for local events, info posts, fundraisers.
|
||||
- **invite-only signup** — magic-link email auth, no passwords, no server-picking.
|
||||
|
||||
## what it doesn't do (by design)
|
||||
|
||||
no push notifications. no DMs. no analytics. no algorithm. no ads. no tracking. no premium tier. no "we believe in" manifesto.
|
||||
|
||||
eutopic doesn't ping you. you visit when you feel like it.
|
||||
|
||||
## stack
|
||||
|
||||
**backend** — bun + elysia, sqlite (postgres-ready), activitypub via fedify (federation off by default), resumable uploads via tus.io, nginx + systemd on a njalla VPS.
|
||||
|
||||
**mobile** — flutter (iOS + android, single codebase), go_router, lottie animations, background upload via isolates / WorkManager / URLSession.
|
||||
|
||||
**infrastructure** — radicle as primary code forge, self-hosted CI runner, liberapay for funding, F-Droid + sideloaded APK for android distribution.
|
||||
|
||||
## funding
|
||||
|
||||
eutopic is donation-funded. the about page shows real monthly hosting costs and a liberapay link. no donor badges, no premium features, no nagging.
|
||||
|
||||
→ [docs/CONCEPT.md](docs/CONCEPT.md) for full concept, architecture decisions, and design rationale.
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -3,3 +3,155 @@
|
|||
*to be fleshed out before v0 alpha.*
|
||||
|
||||
covers: backend structure, API shape, federation mechanics, background job design, endpoint inventory.
|
||||
|
||||
---
|
||||
|
||||
## frontend — flutter folder structure
|
||||
|
||||
```
|
||||
lib/
|
||||
├── main.dart // entry point. calls runApp(). almost nothing lives here — just bootstraps app.dart.
|
||||
├── app.dart // root widget. sets up theme, router, and any top-level providers. think of it as the <App /> in react.
|
||||
│
|
||||
├── core/ // framework-level setup — not domain-specific. no business logic here.
|
||||
│ ├── theme/
|
||||
│ │ ├── app_theme.dart // materialapp theme config — fonts, shape, component defaults
|
||||
│ │ └── app_colors.dart // all color constants in one place. nothing hardcodes hex values elsewhere.
|
||||
│ ├── router/
|
||||
│ │ └── app_router.dart // go_router config. every route in the app is registered here.
|
||||
│ │ // also handles deep-link invite codes (hard section — fully written).
|
||||
│ ├── widgets/
|
||||
│ │ └── bottom_nav_bar.dart // the five-tab nav bar. lives in core/ because it is framework plumbing —
|
||||
│ │ // it knows about routes, not domain data. every screen uses it.
|
||||
│ └── constants/
|
||||
│ └── app_constants.dart // magic values that would otherwise be scattered: api base url, story duration, max photos, etc.
|
||||
│
|
||||
├── shared/ // domain code used by more than one feature. if only one feature uses it, it lives in that feature folder.
|
||||
│ ├── models/
|
||||
│ │ ├── user.dart // the User type. used everywhere — feed, profile, follow suggestions, etc.
|
||||
│ │ ├── post.dart // a feed post. 1–10 photos, optional caption, reaction set, author.
|
||||
│ │ ├── poster_post.dart // a posters post. same base as post but adds tag + event fields.
|
||||
│ │ ├── story.dart // a story. 24h expiry, witness list, no replies.
|
||||
│ │ ├── reaction.dart // a single reaction: user + post + emoji + timestamp.
|
||||
│ │ ├── comment.dart // a comment on a post. flat list, chronological, no threading.
|
||||
│ │ ├── survey.dart // an active survey. multiple choice, 36h expiry.
|
||||
│ │ ├── invite_code.dart // an invite. carries the inviter's id for auto-follow suggestions.
|
||||
│ │ └── social_handle.dart // signal/telegram/custom handle + visibility setting (none/mutuals/all).
|
||||
│ ├── widgets/
|
||||
│ │ ├── photo_viewer.dart // full-screen photo viewer used by feed, posters, and profile grid.
|
||||
│ │ ├── emoji_picker_sheet.dart // bottom sheet (slides up from below) for picking an emoji reaction.
|
||||
│ │ ├── loading_indicator.dart // shared spinner/skeleton — keeps loading states visually consistent.
|
||||
│ │ └── error_view.dart // shared error state widget for failed api calls.
|
||||
│ ├── services/
|
||||
│ │ ├── api_client.dart // http client wrapper. all api calls go through here — one place to set auth headers, base url, error handling.
|
||||
│ │ ├── auth_service.dart // magic-link auth flow. stores session token, exposes current user.
|
||||
│ │ └── upload_service.dart // tus resumable upload. runs in a background isolate. hard section — fully written.
|
||||
│ └── utils/
|
||||
│ ├── date_formatter.dart // relative time formatting ("3h ago", "yesterday") used across feed, posters, stories.
|
||||
│ └── validators.dart // input validation for username, email, invite code fields.
|
||||
│
|
||||
├── features/ // one folder per screen group. each folder is self-contained: screen + widgets + state.
|
||||
│ │ // rule: a widget in features/feed/ should never be imported by features/profile/.
|
||||
│ │ // if two features need the same widget, it moves to shared/widgets/.
|
||||
│ │
|
||||
│ ├── onboarding/
|
||||
│ │ ├── onboarding_screen.dart // shell screen that hosts the multi-step onboarding flow.
|
||||
│ │ ├── widgets/
|
||||
│ │ │ ├── invite_gate.dart // step 1: invite code entry. prefilled from deep link if available.
|
||||
│ │ │ ├── email_username_form.dart // step 2: email + username.
|
||||
│ │ │ ├── magic_link_confirm.dart // step 3: "check your email" holding screen.
|
||||
│ │ │ ├── social_handle_opt_in.dart // step 4: signal/telegram/custom. skippable.
|
||||
│ │ │ └── follow_suggestions.dart // step 5: friends-of-inviter. skippable.
|
||||
│ │ └── providers/
|
||||
│ │ └── onboarding_provider.dart // holds onboarding state across steps: invite code, entered email, etc.
|
||||
│ │
|
||||
│ ├── feed/
|
||||
│ │ ├── feed_screen.dart // home tab. fetches post list, renders it, handles pull-to-refresh.
|
||||
│ │ ├── widgets/
|
||||
│ │ │ ├── post_card.dart // a single post. renders author, photos, reactions. does not fetch — receives a Post object.
|
||||
│ │ │ ├── post_photo_swipe.dart // swipeable photo carousel within a post card. up to 10 photos.
|
||||
│ │ │ ├── reaction_bar.dart // emoji reactions row below a post. shows the post's reaction set.
|
||||
│ │ │ ├── comment_sheet.dart // bottom sheet (slides up) showing flat comment thread for a post.
|
||||
│ │ │ └── survey_card.dart // injected at slot 1 when an active survey exists. not a real post.
|
||||
│ │ └── providers/
|
||||
│ │ └── feed_provider.dart // manages post list state: loading, pagination, optimistic reaction updates.
|
||||
│ │
|
||||
│ ├── posters/
|
||||
│ │ ├── posters_screen.dart // posters tab. 2-column grid, paginated, filterable by tag.
|
||||
│ │ ├── widgets/
|
||||
│ │ │ ├── poster_grid.dart // the 2-column grid layout. handles infinite scroll pagination.
|
||||
│ │ │ ├── poster_card.dart // a single poster card. shows tag, title, event date if applicable.
|
||||
│ │ │ ├── poster_filter_bar.dart // tab strip or dropdown for filtering by event/fundraiser/current affairs. TBD.
|
||||
│ │ │ └── rsvp_button.dart // placeholder — rsvp interaction model not yet designed. see OPEN-QUESTIONS.md.
|
||||
│ │ └── providers/
|
||||
│ │ └── posters_provider.dart
|
||||
│ │
|
||||
│ ├── compose/
|
||||
│ │ ├── compose_screen.dart // center nav tab. opens as modal or full screen (TBD). orchestrates the compose flow.
|
||||
│ │ ├── widgets/
|
||||
│ │ │ ├── photo_picker_tile.dart // photo selection grid. up to 10 photos.
|
||||
│ │ │ ├── caption_field.dart // optional caption text input.
|
||||
│ │ │ ├── destination_toggle.dart // feed vs posters toggle.
|
||||
│ │ │ ├── tag_picker.dart // event / fundraiser / current affairs. required if posting to posters.
|
||||
│ │ │ ├── event_fields.dart // date/time fields shown only when tag = event.
|
||||
│ │ │ ├── reaction_set_picker.dart // choose up to 5 emoji for this post's reaction set.
|
||||
│ │ │ ├── savable_toggle.dart // saveable on/off toggle. default off.
|
||||
│ │ │ └── upload_progress_animation.dart // hand-drawn lottie animation shown while tus upload runs in background.
|
||||
│ │ └── providers/
|
||||
│ │ └── compose_provider.dart // compose form state + triggers upload_service on confirm.
|
||||
│ │
|
||||
│ ├── stories/
|
||||
│ │ ├── stories_screen.dart // stories tab. shows list of active stories as thumbnails.
|
||||
│ │ ├── story_viewer_screen.dart // full-screen story playback. hard section — timer, gestures, progress bar fully written.
|
||||
│ │ ├── story_create_screen.dart // camera or photo picker → post. no drafts.
|
||||
│ │ ├── widgets/
|
||||
│ │ │ ├── story_thumbnail.dart // thumbnail shown in the stories tab list.
|
||||
│ │ │ ├── story_progress_bar.dart // segmented progress bar at top of viewer. one segment per story.
|
||||
│ │ │ ├── story_filter_overlay.dart // swipe-gesture filter layer. applied at view time (TBD).
|
||||
│ │ │ └── witness_list_sheet.dart // bottom sheet showing who has seen your story. visible to author only.
|
||||
│ │ └── providers/
|
||||
│ │ └── stories_provider.dart
|
||||
│ │
|
||||
│ ├── profile/
|
||||
│ │ ├── profile_screen.dart // own profile and others' profiles share this screen. own = shows settings. other = shows follow button.
|
||||
│ │ ├── widgets/
|
||||
│ │ │ ├── profile_header.dart // avatar, display name, username, bio.
|
||||
│ │ │ ├── social_handle_display.dart // signal/telegram/custom handle, shown per display_social_handle setting.
|
||||
│ │ │ ├── contact_prefs_display.dart // free text contact prefs ("bad texter", "weekends only").
|
||||
│ │ │ ├── profile_post_grid.dart // grid of user's own posts.
|
||||
│ │ │ ├── follow_button.dart // follow/unfollow. shown on others' profiles only.
|
||||
│ │ │ └── settings_button.dart // link to settings. shown on own profile only.
|
||||
│ │ └── providers/
|
||||
│ │ └── profile_provider.dart
|
||||
│ │
|
||||
│ ├── settings/
|
||||
│ │ ├── settings_screen.dart
|
||||
│ │ ├── widgets/
|
||||
│ │ │ ├── social_handle_settings.dart // edit signal/telegram/custom handles.
|
||||
│ │ │ └── display_preferences.dart // display_social_handle: none / mutuals / all.
|
||||
│ │ └── providers/
|
||||
│ │ └── settings_provider.dart
|
||||
│ │
|
||||
│ └── feedback/
|
||||
│ ├── feedback_screen.dart // always accessible. active survey at top if one exists, free text below always.
|
||||
│ ├── widgets/
|
||||
│ │ ├── active_survey_card.dart // renders the current multiple-choice survey if one is active.
|
||||
│ │ ├── survey_question_tile.dart // a single survey question with answer options.
|
||||
│ │ └── free_text_field.dart // permanent free-text input. always present, survey or not.
|
||||
│ └── providers/
|
||||
│ └── feedback_provider.dart
|
||||
```
|
||||
|
||||
### structural decisions
|
||||
|
||||
**`core/` vs `shared/`** — `core/` is framework plumbing (theme, router, constants). `shared/` is domain code (models, services, reusable widgets). the distinction matters when onboarding a new contributor: core is "don't touch unless you know what you're doing," shared is "add things here when two features need the same thing."
|
||||
|
||||
**feature-first, strict imports** — nothing in `features/feed/` imports from `features/profile/`. cross-feature dependencies always flow through `shared/`. this keeps features independently readable and prevents tangled refactors later.
|
||||
|
||||
**one `*_screen.dart` per feature, more where flows are genuinely distinct** — screens own data fetching and pass data down to widgets. widgets are display-only and receive everything they need as constructor parameters. this is the flutter equivalent of container/presentational component separation in react. stories is the exception: it has three screen files (`stories_screen`, `story_viewer_screen`, `story_create_screen`) because the viewer (timer + gesture layer) and creator (camera + picker) are full-screen flows with complex independent lifecycles, not components. the rule bends for stories; it does not apply everywhere.
|
||||
|
||||
**`providers/` per feature** — state management sits next to the screen it serves, not in a global `state/` folder. a provider that only `feed_screen.dart` uses has no reason to be visible from `profile/`.
|
||||
|
||||
**no notification files anywhere** — hard constraint. no FCM, no APNs, no device tokens, no unread badge logic. if a future contributor adds one, it's a deliberate reversal of a design decision, not an accident.
|
||||
|
||||
**`comment_sheet` and `witness_list_sheet` are sheets, not screens** — they slide up over the current screen without replacing it. naming them `*_sheet.dart` signals this — they are never registered as routes in `app_router.dart`.
|
||||
|
|
|
|||
|
|
@ -5,10 +5,7 @@ date: 2026-06-04
|
|||
|
||||
---
|
||||
|
||||
thinking carefully... i'm looking at concept.md, open-questions.md, and the
|
||||
implied context from AGENTS.md—even though i haven't read AGENTS.md yet, the other
|
||||
docs give enough signal to start the high-level risk assessment for a pre-build
|
||||
review, focusing on architecture rather than feature completeness yet. the core
|
||||
thinking carefully... i'm looking at concept.md, open-questions.md, and AGENTS.md . the core
|
||||
tension seems to be "simple feel vs. robust future proofing."
|
||||
|
||||
i see three high-risk areas stemming from the confluence of tech choices and
|
||||
|
|
|
|||
|
|
@ -0,0 +1,95 @@
|
|||
lib/
|
||||
├── main.dart
|
||||
├── app.dart
|
||||
│
|
||||
├── features/
|
||||
│ ├── onboarding/
|
||||
│ │ ├── onboarding_screen.dart
|
||||
│ │ ├── invite_gate_screen.dart
|
||||
│ │ ├── email_username_screen.dart
|
||||
│ │ ├── magic_link_confirm_screen.dart
|
||||
│ │ ├── social_handle_optin_screen.dart
|
||||
│ │ ├── follow_suggestions_screen.dart
|
||||
│ │ └── widgets/
|
||||
│ │ ├── invite_code_field.dart
|
||||
│ │ └── follow_suggestion_card.dart
|
||||
│ │
|
||||
│ ├── feed/
|
||||
│ │ ├── feed_screen.dart
|
||||
│ │ └── widgets/
|
||||
│ │ ├── post_card.dart
|
||||
│ │ ├── photo_carousel.dart
|
||||
│ │ ├── reaction_bar.dart
|
||||
│ │ ├── comments_sheet.dart
|
||||
│ │ └── survey_card.dart
|
||||
│ │
|
||||
│ ├── posters/
|
||||
│ │ ├── posters_screen.dart
|
||||
│ │ └── widgets/
|
||||
│ │ ├── poster_card.dart
|
||||
│ │ ├── poster_tag_filter.dart
|
||||
│ │ └── event_fields.dart
|
||||
│ │
|
||||
│ ├── compose/
|
||||
│ │ ├── compose_screen.dart
|
||||
│ │ └── widgets/
|
||||
│ │ ├── photo_picker.dart
|
||||
│ │ ├── caption_field.dart
|
||||
│ │ ├── destination_toggle.dart
|
||||
│ │ ├── poster_tag_picker.dart
|
||||
│ │ ├── reaction_set_picker.dart
|
||||
│ │ ├── saveable_toggle.dart
|
||||
│ │ └── upload_progress_animation.dart
|
||||
│ │
|
||||
│ ├── stories/
|
||||
│ │ ├── stories_screen.dart
|
||||
│ │ ├── story_viewer_screen.dart
|
||||
│ │ ├── story_create_screen.dart
|
||||
│ │ └── widgets/
|
||||
│ │ ├── story_thumbnail.dart
|
||||
│ │ ├── story_progress_bar.dart
|
||||
│ │ ├── story_filter_overlay.dart
|
||||
│ │ └── witness_list_sheet.dart
|
||||
│ │
|
||||
│ ├── profile/
|
||||
│ │ ├── profile_screen.dart
|
||||
│ │ └── widgets/
|
||||
│ │ ├── avatar_header.dart
|
||||
│ │ ├── social_handles_row.dart
|
||||
│ │ ├── posts_grid.dart
|
||||
│ │ └── follow_button.dart
|
||||
│ │
|
||||
│ ├── settings/
|
||||
│ │ ├── settings_screen.dart
|
||||
│ │ └── widgets/
|
||||
│ │ ├── social_handle_visibility_picker.dart
|
||||
│ │ └── contact_prefs_field.dart
|
||||
│ │
|
||||
│ └── feedback/
|
||||
│ ├── feedback_screen.dart
|
||||
│ └── widgets/
|
||||
│ ├── survey_question_card.dart
|
||||
│ └── freetext_field.dart
|
||||
│
|
||||
├── shared/
|
||||
│ ├── models/
|
||||
│ │ ├── user.dart
|
||||
│ │ ├── post.dart
|
||||
│ │ ├── story.dart
|
||||
│ │ ├── poster.dart
|
||||
│ │ ├── reaction.dart
|
||||
│ │ ├── survey.dart
|
||||
│ │ └── invite.dart
|
||||
│ ├── widgets/
|
||||
│ │ ├── bottom_nav_bar.dart
|
||||
│ │ ├── eutopic_photo_viewer.dart
|
||||
│ │ └── emoji_picker_sheet.dart
|
||||
│ ├── services/
|
||||
│ │ ├── auth_service.dart
|
||||
│ │ ├── upload_service.dart
|
||||
│ │ └── api_client.dart
|
||||
│ ├── router/
|
||||
│ │ └── app_router.dart
|
||||
│ └── theme/
|
||||
│ ├── app_theme.dart
|
||||
│ └── app_colors.dart
|
||||
154
model-outputs/02-flutter-folder-structure-deepseek-reasoner.md
Normal file
154
model-outputs/02-flutter-folder-structure-deepseek-reasoner.md
Normal file
|
|
@ -0,0 +1,154 @@
|
|||
# flutter folder structure — eutopic
|
||||
|
||||
model: deepseek-reasoner
|
||||
test: instruction-following, feature-first structure, domain accuracy
|
||||
|
||||
---
|
||||
|
||||
```
|
||||
lib/
|
||||
├── main.dart
|
||||
├── app.dart
|
||||
│
|
||||
├── core/
|
||||
│ ├── theme/
|
||||
│ │ ├── app_theme.dart
|
||||
│ │ └── app_colors.dart
|
||||
│ ├── router/
|
||||
│ │ └── app_router.dart
|
||||
│ └── constants/
|
||||
│ └── app_constants.dart
|
||||
│
|
||||
├── shared/
|
||||
│ ├── models/
|
||||
│ │ ├── user.dart
|
||||
│ │ ├── post.dart
|
||||
│ │ ├── poster_post.dart
|
||||
│ │ ├── story.dart
|
||||
│ │ ├── survey.dart
|
||||
│ │ ├── reaction.dart
|
||||
│ │ ├── comment.dart
|
||||
│ │ ├── invite_code.dart
|
||||
│ │ └── social_handle.dart
|
||||
│ ├── widgets/
|
||||
│ │ ├── photo_grid.dart
|
||||
│ │ ├── photo_swipe.dart
|
||||
│ │ ├── emoji_picker.dart
|
||||
│ │ ├── loading_indicator.dart
|
||||
│ │ └── error_view.dart
|
||||
│ ├── services/
|
||||
│ │ ├── api_client.dart
|
||||
│ │ ├── auth_service.dart
|
||||
│ │ └── upload_service.dart
|
||||
│ └── utils/
|
||||
│ ├── date_formatter.dart
|
||||
│ └── validators.dart
|
||||
│
|
||||
├── features/
|
||||
│ ├── onboarding/
|
||||
│ │ ├── onboarding_screen.dart
|
||||
│ │ ├── widgets/
|
||||
│ │ │ ├── invite_gate.dart
|
||||
│ │ │ ├── email_username_form.dart
|
||||
│ │ │ ├── magic_link_confirm.dart
|
||||
│ │ │ ├── social_handle_opt_in.dart
|
||||
│ │ │ └── follow_suggestions.dart
|
||||
│ │ └── providers/
|
||||
│ │ └── onboarding_provider.dart
|
||||
│ │
|
||||
│ ├── feed/
|
||||
│ │ ├── feed_screen.dart
|
||||
│ │ ├── widgets/
|
||||
│ │ │ ├── post_card.dart
|
||||
│ │ │ ├── post_photo_swipe.dart
|
||||
│ │ │ ├── reaction_bar.dart
|
||||
│ │ │ ├── comment_sheet.dart
|
||||
│ │ │ └── survey_card.dart
|
||||
│ │ └── providers/
|
||||
│ │ └── feed_provider.dart
|
||||
│ │
|
||||
│ ├── posters/
|
||||
│ │ ├── posters_screen.dart
|
||||
│ │ ├── widgets/
|
||||
│ │ │ ├── poster_grid.dart
|
||||
│ │ │ ├── poster_card.dart
|
||||
│ │ │ ├── poster_filter_bar.dart
|
||||
│ │ │ └── rsvp_button.dart
|
||||
│ │ └── providers/
|
||||
│ │ └── posters_provider.dart
|
||||
│ │
|
||||
│ ├── compose/
|
||||
│ │ ├── compose_screen.dart
|
||||
│ │ ├── widgets/
|
||||
│ │ │ ├── photo_picker_tile.dart
|
||||
│ │ │ ├── caption_field.dart
|
||||
│ │ │ ├── destination_toggle.dart
|
||||
│ │ │ ├── tag_picker.dart
|
||||
│ │ │ ├── event_fields.dart
|
||||
│ │ │ ├── reaction_set_picker.dart
|
||||
│ │ │ ├── savable_toggle.dart
|
||||
│ │ │ └── upload_progress_animation.dart
|
||||
│ │ └── providers/
|
||||
│ │ └── compose_provider.dart
|
||||
│ │
|
||||
│ ├── stories/
|
||||
│ │ ├── stories_screen.dart
|
||||
│ │ ├── widgets/
|
||||
│ │ │ ├── story_player.dart
|
||||
│ │ │ ├── story_filter_overlay.dart
|
||||
│ │ │ ├── story_capture.dart
|
||||
│ │ │ └── witness_list.dart
|
||||
│ │ └── providers/
|
||||
│ │ └── stories_provider.dart
|
||||
│ │
|
||||
│ ├── profile/
|
||||
│ │ ├── profile_screen.dart
|
||||
│ │ ├── widgets/
|
||||
│ │ │ ├── profile_header.dart
|
||||
│ │ │ ├── profile_post_grid.dart
|
||||
│ │ │ ├── social_handle_display.dart
|
||||
│ │ │ ├── contact_prefs_display.dart
|
||||
│ │ │ ├── follow_button.dart
|
||||
│ │ │ └── settings_button.dart
|
||||
│ │ └── providers/
|
||||
│ │ └── profile_provider.dart
|
||||
│ │
|
||||
│ ├── settings/
|
||||
│ │ ├── settings_screen.dart
|
||||
│ │ ├── widgets/
|
||||
│ │ │ ├── social_handle_settings.dart
|
||||
│ │ │ └── display_preferences.dart
|
||||
│ │ └── providers/
|
||||
│ │ └── settings_provider.dart
|
||||
│ │
|
||||
│ └── feedback/
|
||||
│ ├── feedback_screen.dart
|
||||
│ ├── widgets/
|
||||
│ │ ├── active_survey_card.dart
|
||||
│ │ ├── survey_question_tile.dart
|
||||
│ │ └── free_text_field.dart
|
||||
│ └── providers/
|
||||
│ └── feedback_provider.dart
|
||||
│
|
||||
└── navigation/
|
||||
├── app_navigation.dart
|
||||
└── bottom_tab_bar.dart
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### design rationale
|
||||
|
||||
**feature-first** — each screen lives under `features/{name}/` with co-located widgets and state. no flat `screens/` or `widgets/` directory.
|
||||
|
||||
**shared models** in `shared/models/` so the `post.dart` model used by feed, posters, and compose lives in one place. feature-specific models (like invite_code for onboarding) could stay in the feature folder — here i kept them in shared since they're referenced across auth boundaries.
|
||||
|
||||
**no notification files** — per the spec: no notifications, no dms, no push alerts. you won't find a `notifications/` folder or a `push_service.dart`.
|
||||
|
||||
**story as tab** — the spec is explicit that stories are a bottom tab, not a top strip. `stories_screen.dart` lives at the same level as `feed_screen.dart`, matching the five-tab nav.
|
||||
|
||||
**compose as center tab** — `compose_screen.dart` is a top-level feature folder. the upload_service in `shared/services/` handles tus.io resumable upload, referenced by the compose provider.
|
||||
|
||||
**naming consistency** — `*_screen.dart` for entry-point widgets (one per feature), `*_provider.dart` for state (using provider or riverpod — TBD), descriptive names for sub-widgets.
|
||||
|
||||
**onboarding is a feature** — not stuffed into `auth/` or `screens/`. it has its own provider and five child widgets matching the five-step flow from the spec.
|
||||
129
model-outputs/02-flutter-folder-structure-glm-4.7.md
Normal file
129
model-outputs/02-flutter-folder-structure-glm-4.7.md
Normal file
|
|
@ -0,0 +1,129 @@
|
|||
# Flutter Folder Structure — GLM-4.7
|
||||
|
||||
**tests:** instruction-following, feature-first structure, domain accuracy
|
||||
|
||||
**context:** DESIGN.md excerpt (screens + navigation)
|
||||
|
||||
**version:** 1
|
||||
|
||||
---
|
||||
|
||||
```
|
||||
lib/
|
||||
├── main.dart
|
||||
├── app.dart
|
||||
├── features/
|
||||
│ ├── onboarding/
|
||||
│ │ ├── onboarding_screen.dart
|
||||
│ │ ├── widgets/
|
||||
│ │ │ ├── invite_gate_widget.dart
|
||||
│ │ │ ├── email_entry_widget.dart
|
||||
│ │ │ ├── magic_link_confirm_widget.dart
|
||||
│ │ │ ├── social_handle_optin_widget.dart
|
||||
│ │ │ └── follow_suggestions_widget.dart
|
||||
│ │ └── onboarding_cubit.dart
|
||||
│ ├── feed/
|
||||
│ │ ├── feed_screen.dart
|
||||
│ │ ├── widgets/
|
||||
│ │ │ ├── post_card.dart
|
||||
│ │ │ ├── photo_swiper.dart
|
||||
│ │ │ ├── reaction_bar.dart
|
||||
│ │ │ └── survey_card.dart
|
||||
│ │ └── feed_cubit.dart
|
||||
│ ├── posters/
|
||||
│ │ ├── posters_screen.dart
|
||||
│ │ ├── widgets/
|
||||
│ │ │ ├── poster_card.dart
|
||||
│ │ │ └── tag_filter.dart
|
||||
│ │ └── posters_cubit.dart
|
||||
│ ├── compose/
|
||||
│ │ ├── compose_screen.dart
|
||||
│ │ ├── widgets/
|
||||
│ │ │ ├── photo_picker_widget.dart
|
||||
│ │ │ ├── caption_field.dart
|
||||
│ │ │ ├── post_type_toggle.dart
|
||||
│ │ │ ├── tag_picker.dart
|
||||
│ │ │ ├── event_form.dart
|
||||
│ │ │ ├── saveable_toggle.dart
|
||||
│ │ │ └── reaction_picker.dart
|
||||
│ │ └── compose_cubit.dart
|
||||
│ ├── stories/
|
||||
│ │ ├── stories_screen.dart
|
||||
│ │ ├── widgets/
|
||||
│ │ │ ├── story_viewer.dart
|
||||
│ │ │ ├── story_carousel.dart
|
||||
│ │ │ ├── story_capture_widget.dart
|
||||
│ │ │ └── filter_swipe_handler.dart
|
||||
│ │ └── stories_cubit.dart
|
||||
│ ├── profile/
|
||||
│ │ ├── profile_screen.dart
|
||||
│ │ ├── widgets/
|
||||
│ │ │ ├── profile_header.dart
|
||||
│ │ │ ├── social_handle_display.dart
|
||||
│ │ │ ├── contact_prefs_display.dart
|
||||
│ │ │ ├── posts_grid.dart
|
||||
│ │ │ └── settings_access_button.dart
|
||||
│ │ └── profile_cubit.dart
|
||||
│ ├── settings/
|
||||
│ │ ├── settings_screen.dart
|
||||
│ │ ├── widgets/
|
||||
│ │ │ ├── social_handle_setting.dart
|
||||
│ │ │ ├── display_social_handle_preference.dart
|
||||
│ │ │ └── contact_prefs_editor.dart
|
||||
│ │ └── settings_cubit.dart
|
||||
│ ├── feedback/
|
||||
│ │ ├── feedback_screen.dart
|
||||
│ │ ├── widgets/
|
||||
│ │ │ ├── survey_widget.dart
|
||||
│ │ │ └── free_text_field.dart
|
||||
│ │ └── feedback_cubit.dart
|
||||
│ └── navigation/
|
||||
│ ├── main_tab_bar.dart
|
||||
│ └── navigation_cubit.dart
|
||||
├── services/
|
||||
│ ├── auth_service.dart
|
||||
│ ├── posts_service.dart
|
||||
│ ├── upload_service.dart
|
||||
│ ├── users_service.dart
|
||||
│ └── tus_client.dart
|
||||
├── shared/
|
||||
│ ├── models/
|
||||
│ │ ├── user.dart
|
||||
│ │ ├── post.dart
|
||||
│ │ ├── poster.dart
|
||||
│ │ ├── story.dart
|
||||
│ │ ├── reaction.dart
|
||||
│ │ ├── comment.dart
|
||||
│ │ └── survey.dart
|
||||
│ ├── widgets/
|
||||
│ │ ├── error_boundary.dart
|
||||
│ │ ├── loading_indicator.dart
|
||||
│ │ └── pull_to_refresh.dart
|
||||
│ ├── utils/
|
||||
│ │ ├── date_formatter.dart
|
||||
│ │ ├── validators.dart
|
||||
│ │ └── image_utils.dart
|
||||
│ └── constants/
|
||||
│ ├── app_colors.dart
|
||||
│ ├── app_text_styles.dart
|
||||
│ └── navigation_routes.dart
|
||||
└── routing/
|
||||
├── app_router.dart
|
||||
└── route_handlers.dart
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**notes:**
|
||||
|
||||
- feature-first structure as specified
|
||||
- no notification-related files
|
||||
- no chat/dm-related files
|
||||
- compose modeled as a screen with post destination toggles
|
||||
- stories has capture widget for camera/photo picker
|
||||
- posters includes tag filter (UI only, implementation details TBD)
|
||||
- feedback separates survey and free-text into distinct widgets
|
||||
- social handles are opt-in, modeled in onboarding and settings
|
||||
- all state management uses cubit pattern (consistent across features)
|
||||
- tus_client for resumable uploads
|
||||
- routing handles deep links for invite codes
|
||||
Loading…
Reference in a new issue