Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions app/api/track/route.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { NextRequest, NextResponse } from "next/server"
import { revalidatePath } from "next/cache"

import { architectureSchema, navigationSchema, stateManagementSchema } from "@/app/lib/config/schema"
import { createPublishableSupabaseClient } from "@/app/lib/supabase/server"
Expand Down Expand Up @@ -86,6 +87,10 @@ export async function POST(req: NextRequest) {
return NextResponse.json({ error: "track_failed" }, { status: 503 })
}

// Bust the landing page ISR cache so the next visitor sees updated stats
// immediately rather than waiting up to 60 seconds.
revalidatePath("/")

return NextResponse.json({ ok: true }, { status: 202 })
} catch {
return NextResponse.json({ error: "track_failed" }, { status: 503 })
Expand Down
17 changes: 8 additions & 9 deletions app/components/landing/StatsSection.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { createPublishableSupabaseClient } from "@/app/lib/supabase/server"
import { StatsShowcase, StatsShowcaseSkeleton } from "@/app/components/landing/StatsShowcase"

type StatsResponse = {
Expand All @@ -14,17 +15,15 @@ type StatsResponse = {
dark_mode_enabled?: number
}


async function getStats(): Promise<StatsResponse | null> {
try {
const res = await fetch(`${process.env.NEXT_PUBLIC_VERCEL_URL}/api/stats`, {
// next: {
// revalidate: 60,
// tags: ["generator-stats"],
// },
})
if (!res.ok) return null
return (await res.json()) as StatsResponse
const supabase = createPublishableSupabaseClient()
const { data, error } = await supabase
.from("stats_summary")
.select("*")
.single()
if (error) return null
return data as StatsResponse
} catch {
return null
}
Expand Down
21 changes: 17 additions & 4 deletions app/components/landing/WhyFlutterInit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
} from "@/components/ui/card";
import { cn } from '@/lib/utils';
import {
AiBrain01Icon,
Clock01Icon,
CpuIcon,
DashboardSquare01Icon,
Expand All @@ -25,8 +26,9 @@ const bentoClasses = [
"md:col-span-2 md:row-span-1", // Row 2: Item 3
"md:col-span-2 md:row-span-1", // Row 2: Item 4
"md:col-span-2 md:row-span-1", // Row 2: Item 5
"md:col-span-3 md:row-span-1", // Row 3: Item 6
"md:col-span-3 md:row-span-1", // Row 3: Item 7
"md:col-span-2 md:row-span-1", // Row 3: Item 6
"md:col-span-2 md:row-span-1", // Row 3: Item 7
"md:col-span-2 md:row-span-1", // Row 3: Item 8
]

export function WhyFlutterInit() {
Expand Down Expand Up @@ -66,6 +68,11 @@ export function WhyFlutterInit() {
glow: "from-cyan-500/10 to-transparent",
chip: "bg-cyan-500/8 border-cyan-500/15",
},
violet: {
iconColor: "text-violet-500",
glow: "from-violet-500/10 to-transparent",
chip: "bg-violet-500/8 border-violet-500/15",
},
} as const;

const features = [
Expand Down Expand Up @@ -117,6 +124,13 @@ export function WhyFlutterInit() {
icon: Globe02Icon,
accent: accents.cyan,
label: "Localization"
},
{
title: "AI-Ready Context",
description: "AGENTS.md, DESIGN.md, and Cursor rules ship with every project.",
icon: AiBrain01Icon,
accent: accents.violet,
label: "AI Assistants"
}
];

Expand Down Expand Up @@ -151,8 +165,7 @@ export function WhyFlutterInit() {

<div className="grid grid-cols-1 gap-6 md:grid-cols-6 md:auto-rows-[220px]">
{features.map((feature, index) => {
const isWideTile = index === 0 || index === 1 || index === 5 || index === 6;
const isCompactTile = index === 2 || index === 3 || index === 4;
const isWideTile = index === 0 || index === 1;
return (
<Card
key={feature.title}
Expand Down
18 changes: 10 additions & 8 deletions app/components/wizard/WizardShell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import Image from "next/image"
import * as React from "react"
import { PackageInfoPanel } from "./PackageInfoPanel"
import { StepContent } from "./StepContent"
import Link from "next/link"

const steps: Record<
StepId,
Expand Down Expand Up @@ -258,14 +259,15 @@ function WizardSidebar() {
<Sidebar variant="sidebar" className="border-r border-border/40 bg-background/50 backdrop-blur-xl">
<SidebarHeader className="p-4 border-b border-border/40">
<div className="flex items-center gap-3">
<Image
src="/logo.svg"
alt="FlutterInit Logo"
width={24}
height={24}
className="h-6 w-6"
priority
/>
<Link href={"/"}>
<Image
src="/logo.svg"
alt="FlutterInit Logo"
width={24}
height={24}
className="h-6 w-6"
priority
/></Link>
<Badge variant="outline" className="ml-auto bg-background/50 backdrop-blur-sm border-primary/20 text-primary hover:bg-transparent">
1.0
</Badge>
Expand Down
5 changes: 5 additions & 0 deletions app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ import { Footer } from "@/app/components/landing/Footer"
import { StatsSection, StatsSectionSkeleton } from "@/app/components/landing/StatsSection"
import { Suspense } from "react"

// Re-render this page (and re-fetch stats from Supabase) at most every 60 seconds.
// Without this, Next.js statically renders the page once at build time and the
// stats count would stay frozen forever regardless of new generations.
export const revalidate = 60

export default function Page() {
return (
<main className="flex min-h-screen flex-col items-center justify-start bg-zinc-50 font-sans selection:bg-primary/20">
Expand Down
3 changes: 3 additions & 0 deletions docs/generated-output.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ my_app/
│ └── utils/ # General utilities, logging, and error handling
├── test/ # Unit and Widget tests
├── pubspec.yaml # Flutter dependency management
├── AGENTS.md # AI/agent context (stack, architecture, safe zones)
├── DESIGN.md # Design system tokens and UI conventions
├── .cursor/rules/ # Cursor IDE rules (flutter-project.mdc)
└── SETUP.md # Post-generation setup instructions
```

Expand Down
6 changes: 6 additions & 0 deletions skills-lock.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@
"sourceType": "github",
"computedHash": "82588484b29316f3106185374cc6aedf409c38a0698d49f7034c9b5ec07a1aa7"
},
"hallmark": {
"source": "nutlope/hallmark",
"sourceType": "github",
"skillPath": "SKILL.md",
"computedHash": "a5c828f76d82801993fe9d9826720af459360720235e1a68e10290bcb1f6491e"
},
"markdown-to-html": {
"source": "github/awesome-copilot",
"sourceType": "github",
Expand Down
132 changes: 132 additions & 0 deletions templates/flutter/base/.cursor/rules/flutter-project.mdc.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
---
description: {{appName}} — FlutterInit stack, architecture, design conventions, and agent zones
alwaysApply: true
---

# {{appName}} — Cursor project context

{{#if description}}{{description}}{{/if}}

Generated by FlutterInit. Package: `{{packageId}}`.

**Extended docs (read when changing setup or deep UI):**

- **[AGENTS.md](AGENTS.md)** — full agent guide (duplicate of sections below + packages list)
- **[DESIGN.md](DESIGN.md)** — complete design system
- **[SETUP.md](SETUP.md)** — env, Firebase/Supabase/Appwrite, native config, permissions

---

{{> llm/stack-summary}}

---

{{> llm/architecture-rules}}

---

{{> llm/state-management-rules}}

---

{{> llm/navigation-rules}}

---

{{#unless (eq backend.provider "none")}}
{{> llm/backend-rules}}

---
{{/unless}}

{{> llm/networking-rules}}

---

{{> llm/services-conventions}}

---

{{> llm/design-quick-reference}}

---

{{> llm/add-feature-workflow}}

---

## File zones

### Safe to modify

| Path | Guidance |
|------|----------|
{{#if (or (eq architecture "clean") (eq architecture "feature-first"))}}| `lib/src/features/**` | Feature screens, widgets, domain/data/presentation |{{/if}}
{{#if (eq architecture "mvc")}}| `lib/src/views/**`, `lib/src/controllers/**` | UI and controllers |{{/if}}
{{#if (eq architecture "mvvm")}}| `lib/src/ui/**`, `lib/src/data/**` | UI and data for features |{{/if}}
{{#if (eq architecture "layer-first")}}| `lib/src/presentation/**`, `lib/src/domain/**`, `lib/src/data/**` | Layer slices |{{/if}}
| `lib/src/shared/widgets/**` | Shared UI components |
| `test/**` | Tests |

### Modify with caution

| Path | Why |
|------|-----|
| `lib/src/routing/app_router.dart` | All navigation |
| `lib/main.dart` | Init order (SDKs, dotenv, l10n) |
| `lib/src/config/app_config.dart` | Dio / SDK clients |
| `lib/src/theme/**` | Global design tokens |
| `lib/src/imports/**` | Barrel exports |
| `pubspec.yaml` | Dependencies |
{{#if flags.usesDotenv}}| `.env` | Secrets |{{/if}}

### Do not touch

| Path | Why |
|------|-----|
| `android/`, `ios/`, `web/`, desktop native trees | Platform config |
{{#if flags.usesDotenv}}| `.env` with production secrets | Security |{{/if}}
{{#if flags.usesFirebase}}| `google-services.json`, `GoogleService-Info.plist` | Firebase credentials |{{/if}}

---

{{> llm/packages-list}}

---

## Quick rules (always enforce)

- Import: `package:{{flags.appSlug}}/src/imports/imports.dart`
- Services: `ClassName.instance` + `runTask()` → `FutureEither<T>`; use `rootContext`, not `BuildContext` in services
- UI: `context.colors`, `context.textTheme`, `AppSpacing`, `AppBorders` — no hardcoded hex or magic padding
- Routes: only `lib/src/routing/app_router.dart`
- Network/backend: `lib/src/services/` + `app_config.dart` — never from widgets
- No second state-management or routing library
- No business logic in `build()`; no empty `catch` blocks
{{#if flags.isRiverpod}}- Riverpod: `ref.watch` in build only; `ref.read` in callbacks; no `@riverpod` codegen{{/if}}
{{#if flags.isBloc}}- Bloc: immutable state/events; delegate logic out of handlers{{/if}}
{{#if flags.isProvider}}- Provider: `watch` in build, `read` in callbacks{{/if}}
{{#if flags.isGetX}}- GetX: bindings; no GoRouter/AutoRoute mix{{/if}}
{{#if flags.isMobX}}- MobX: mutate via `@action`; run `build_runner` after store edits{{/if}}
{{#if flags.supportsLocalization}}- Copy: `easy_localization` + `assets/translations/*.json`{{/if}}

---

## Verify after changes

```bash
flutter pub get
flutter analyze
```

{{> llm/build-runner-note}}

{{#if flags.supportsLocalization}}
```bash
flutter pub run easy_localization:generate -S assets/translations -O lib/src/core/i18n -o locale_keys.g.dart
```
{{/if}}

```bash
flutter test
```
Loading
Loading