From c3e83cfb4e85650e9483728e23f50edfe39ca7b9 Mon Sep 17 00:00:00 2001 From: pthfndr Date: Thu, 4 Jun 2026 22:37:31 +0300 Subject: [PATCH] learning spec; flutter FE arch (repo/file structure) --- LEARNING-SPEC.md | 214 ++++++++++++++++++ README.md | 37 ++- docs/ARCHITECTURE.md | 152 +++++++++++++ ...l-risk-gemma-optiq-agents-concept-openq.md | 5 +- ...tter-folder-structure-claude-sonnet-4.6.md | 95 ++++++++ ...tter-folder-structure-deepseek-reasoner.md | 154 +++++++++++++ .../02-flutter-folder-structure-glm-4.7.md | 129 +++++++++++ 7 files changed, 774 insertions(+), 12 deletions(-) create mode 100644 LEARNING-SPEC.md create mode 100644 model-outputs/02-flutter-folder-structure-claude-sonnet-4.6.md create mode 100644 model-outputs/02-flutter-folder-structure-deepseek-reasoner.md create mode 100644 model-outputs/02-flutter-folder-structure-glm-4.7.md diff --git a/LEARNING-SPEC.md b/LEARNING-SPEC.md new file mode 100644 index 0000000..38c008e --- /dev/null +++ b/LEARNING-SPEC.md @@ -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 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 = ['a', 'b']; // typed list + * + * final map = {'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 diff --git a/README.md b/README.md index 05ceb5c..1aa64fb 100644 --- a/README.md +++ b/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. --- diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 036d437..c1c4cfe 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -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 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`. diff --git a/model-outputs/01-architectural-risk-gemma-optiq-agents-concept-openq.md b/model-outputs/01-architectural-risk-gemma-optiq-agents-concept-openq.md index db9c0ec..e5a4260 100644 --- a/model-outputs/01-architectural-risk-gemma-optiq-agents-concept-openq.md +++ b/model-outputs/01-architectural-risk-gemma-optiq-agents-concept-openq.md @@ -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 diff --git a/model-outputs/02-flutter-folder-structure-claude-sonnet-4.6.md b/model-outputs/02-flutter-folder-structure-claude-sonnet-4.6.md new file mode 100644 index 0000000..41b37ca --- /dev/null +++ b/model-outputs/02-flutter-folder-structure-claude-sonnet-4.6.md @@ -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 diff --git a/model-outputs/02-flutter-folder-structure-deepseek-reasoner.md b/model-outputs/02-flutter-folder-structure-deepseek-reasoner.md new file mode 100644 index 0000000..80046f0 --- /dev/null +++ b/model-outputs/02-flutter-folder-structure-deepseek-reasoner.md @@ -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. diff --git a/model-outputs/02-flutter-folder-structure-glm-4.7.md b/model-outputs/02-flutter-folder-structure-glm-4.7.md new file mode 100644 index 0000000..184bb30 --- /dev/null +++ b/model-outputs/02-flutter-folder-structure-glm-4.7.md @@ -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 \ No newline at end of file