fluframe 1.4.0
fluframe: ^1.4.0 copied to clipboard
Generate production-ready Flutter apps from the fluFrame boilerplate — Riverpod 3, go_router, localization, theming, dio, and tests wired out of the box.
Changelog #
1.4.0 #
The bugs a first arrival would actually hit. Nothing here is a new feature; it is the pass you make before telling people the project exists. Backwards compatible — no CLI surface changed.
-
Fixed:
fluframe add featurewrote a broken description into yourapp_en.arb.'Label for the $entry.key screen.'is$identifierinterpolation, so every scaffolded feature landed"description": "Label for the MapEntry(billingTitle: Billing).key screen."in the@-metadata block — the contract with translators. Shipped since 1.3.0. Regenerate the block by hand, or re-runadd featureon a fresh key; the fix does not rewrite what earlier versions already wrote. -
Fixed:
fluframe doctorsaid "All set" without checking your SDK. It printedflutter --versionand compared it to nothing, so a machine on an older stable channel was told it was fine and only found out a minute intocreate, in a raw pub solver error.doctornow reads the floor out of the template's ownenvironment: sdk:— one copy, so it cannot drift — and fails with the version it found, the version required, andflutter upgrade. A constraint it cannot bound is skipped rather than failed: a wrong floor rejects a machine that would have worked. -
Fixed: the generated app never mentioned the CLI that generated it.
README.mdandAGENTS.mdtaught only the manual way to add a feature, and nothing explained the.fluframe.jsonyou were asked to commit. Both now lead withfluframe add feature, and the README documentsfluframe upgradeproperly: dry-run by default,--applyrequires a clean git tree, conflicts come back as ordinary git markers, plus--restore-deletedand--from. -
Fixed: the auth guides reproduced a boot failure the addons exist to prevent. Both told you to initialize the SDK "right after
WidgetsFlutterBinding.ensureInitialized()" — before the error hooks, so a throwinginitialize()escaped into the root zone with no widget tree to show it. A black screen with the error nowhere. The guides now match what--backend supabase/--backend firebaseactually generate, including the configured-or-fallback guard they were missing entirely and the dependency pins the CLI uses. -
Fixed: the English language picker named Japanese in English. It read
System · English · 한국어 · Japanese;app_en.arbwas the only ARB not using the endonym. Now日本語, with a test asserting the literal in all three locales — "all three agree" is also satisfied by all three regressing. -
Fixed: both example apps showed fluFrame's name in Japanese. Their
app_ja.arbstill carried the unrewrittenFluFrame アプリwhile en and ko were correctly renamed. Found by the new value check below. -
Added:
check_example_drift --fixrepairs ARB keys. It used to re-sync every file it owned and report success while the ARBs were still missing the keys the template had just gained — twice in the 1.3.0 milestone alone. It now inserts missing keys with their@-metadata, preserves existing order, never removes a key the example owns, and compares shared values after the rename tokens. A differing value is reported and deliberately left alone, because overwriting a translation is worse than the drift. -
Added: an ARB locale-parity test. Nothing compared the three ARBs to each other.
gen-l10nfalls back to English for a missing key and only warns, so a locale could silently stop being translated with CI green. Six documents also said the template had two locales; all six now say three. -
Security: every GitHub Action is pinned to a commit SHA.
publish.ymlgranted the OIDC scope that mints a pub.dev credential in the same job that ran a third-party action on a mutable@v2tag. Both workflows now default topermissions: contents: read. AddsSECURITY.mdwith a private disclosure channel, and Dependabot for actions and both pubspecs. -
Docs: prerequisites and the
PATHstep that was missing everywhere. No document stated a minimum Flutter version (3.44 / Dart 3.12), and all four entry points saiddart pub global activate fluframethenfluframe ...with nothing about the pub cache not being onPATH— the first failure a newcomer can hit, and the onedoctorcannot diagnose becausedoctoris the command that will not run. The pub.dev usage block also ranupgradeabovecreate; it is now runnable top to bottom.
1.3.0 #
create was the only command you ever ran twice — by starting over.
This release adds the one you run every time the app grows a feature, and
makes the project something you can look at before installing anything.
Backwards compatible. fluframe add feature needs anchors that ship with
this version's template, so an app generated earlier needs
fluframe upgrade first — the command says so and changes nothing until
you do.
-
Added:
fluframe add feature <name> [--tab] [--dry-run]. Scaffolds a feature module into an existing app — repository, controller, screen and two tests — registers its route (or a bottom-navigation tab with--tab), and adds the strings to all three ARBs. It names the keys that still carry the English text rather than letting a silent English string sit inapp_ja.arb.Nothing is written until the whole change has been computed: the name, the app, an existing feature directory, the router anchors and every ARB are checked first, and a failed write removes the feature directory it had already created.
--dry-runprints the plan and writes nothing. Unlikeupgrade, it is not dry-run by default — it only creates new files and makes bounded insertions — and--helpsays so.The scaffold deliberately contains no
freezedmodel: a generated@freezedclass does not compile untilbuild_runnerhas run, and the app must analyze and test cleanly the moment the command exits. The screen points atfeatures/postsfor the real pattern. -
Added: a live demo of a generated app at https://jogyoungjun.github.io/fluFrame/ — the template with zero edits, built for web and redeployed from
mainafter every gate passes. Linked from all three READMEs. -
Template: the posts list is now paginated with infinite scroll, and is the reference implementation for the four things the first attempt gets wrong: appending instead of replacing, one request per frame near the bottom, no representation of "there is no more", and discarding the list when a later page fails.
Riverpod's
ref.invalidatereuses the notifier instance and leaves it mounted, so a page still in flight during a pull-to-refresh will silently append onto the freshly reloaded first one. A generation counter is what prevents that;ref.mounteddoes not.Consequence worth knowing: the offline fallback cache holds page one only, so an offline post-detail lookup now covers the first 20 posts rather than all of them.
-
Docs:
docs/comparison.md— fluFrame againstflutter create, Very Good CLI and cloning a boilerplate, with every claim about the other tools taken from their current sources and the check date stated. It names five cases where you should pick something else. -
Corrected locale claims that went stale when Japanese was added: the READMEs said "4 locales" (there are three, plus a System option) and four places still described the template as English + Korean.
1.2.0 #
Correctness release. A full audit of 1.1.0 found that the two things
fluframe promises — generate an app, then keep it up to date — could both
fail while reporting success. upgrade --apply destroyed every non-ASCII
character in a Korean or Japanese app and printed conflicts: 0; a
generated Firebase app opened to a black screen; doctor said "All set"
a minute before create died. Nothing below is a new feature.
Everything here is backwards compatible. Two behaviours changed on
purpose: upgrade --apply now requires a clean git working tree
(--force opts out), and it exits non-zero when conflicts remain.
-
Fixed: unexpected failures printed a raw stack trace and exited -1. Only
UsageExceptionwas handled, so a corrupt.fluframe.jsonor an unpublished--fromversion dumped a trace with no advice. Each now gets a sentence and a real exit code; genuine bugs still print the trace, after the message. -
Fixed: a malicious bundle could write outside the extraction directory.
templates/../../../probe.txtpassed thestartsWith('templates/')check. Entries are now resolved against the destination and rejected by name, and every failure path cleans up its temp directory. -
Fixed:
upgrade --applyrewrote every line ending on Windows. Line endings are normalized for comparison only; each file keeps the style it had. One non-UTF-8 file no longer aborts the whole run either — it is reported and skipped. -
Fixed:
upgraderan happily in a directory that was not an app. In an unrelated empty folder it reported "unchanged: 65 / added: 3" and exited 0. It now refuses without a.fluframe.jsonor apubspec.yaml, and theunchangedcount means what it says. -
Template: boot survives a storage failure. Four reads ran before
runAppwith no error handling, so one failure meant no widget tree at all. They fall back to defaults and report through the error seam. -
Template:
AsyncValueWidgetcan show a refresh over existing content. Retry looked dead (so users tapped again, firing duplicate requests) and a failed pull-to-refresh discarded the list being read. -
Template: the login button awaits its own work and no longer loses non-
AuthExceptionfailures;authControllerstops resurrecting a signed-out session when it is invalidated. -
Template:
ApiExceptioncarries the backend's response body, so a generated app can read the server's own error code;cancelandbadCertificateare no longer both "unknown". -
Generated apps now ship their own CI workflow and an
AGENTS.mddescribing the app's conventions to a coding agent. Both are rewritten to the project's name. -
Added: an example-drift gate.
examples/had lostcore/logging/error_handlers.dart— the template's crash-reporting seam — while the docs claimed CI meant they "cannot rot". Both are re-synced (and gained the Japanese locale they were missing), andtool/check_example_drift.dartnow fails CI on new drift. -
Coverage is measured for the CLI as well as the template, published to the job summary, uploaded as an artifact, and gated by a floor for the CLI. Addon sources are format-checked, since they ship into user apps.
-
Docs: ADR 0003 (Riverpod without provider codegen) and ADR 0004 (committed code generation) record two decisions the whole product rests on;
docs/architecture.mdno longer claims three rename tokens or an en+ko-only template. -
Fixed:
upgrade --applycorrupted non-ASCII source files. Child process output was decoded with the OS codepage (cp949 on Korean Windows, cp932 on Japanese), so a mergedfind.text('한국어')came back asfind.text('?쒓뎅??— closing quote and all. Every process the CLI launches now decodes as UTF-8, andgit merge-filewrites its result to a file instead of stdout so the merged bytes never cross a pipe. The damage was silent: the run reportedconflicts: 0and exited 0. -
Fixed: a failed merge could blank a file. A hard
git merge-fileerror (binary input, unreadable file) produced empty output that was written over the user's copy. Such files are now reported and left untouched. -
Fixed: unresolved conflicts sealed off the re-run.
--applybumped.fluframe.jsonto the new version even with conflict markers still in the tree, so the next run answerednothing to upgradeand the only way out was hand-editing the metadata. The recorded version now advances only on a clean result, and a run with conflicts exits non-zero. -
Added:
--applyrefuses when it could not be undone. It keeps no backup andflutter createdoes notgit init, so it now requires a git repository with a clean working tree.fluframe upgrade --apply --forceopts out. The check runs before the bundle download, not after. -
CLI unit tests also run on Windows and macOS in CI.
-
Fixed: a failed
createleft you unable to retry. Every failure path now prints where the half-written project is and the exact command to remove it — previously the obvious retry hitDirectory "./my_app" already exists. Aborting.with no hint..fluframe.jsonis written last, so a partial directory never looks upgradable. -
Fixed:
doctorsaid "All set" and thencreatefailed. It now checks that this machine can create symbolic links, which Flutter needs for thewindowsandlinuxplugins in the default platform set. On Windows the fix is Developer Mode; the message says so, and says how to scope the app instead.createrepeats the hint ifpub getstill fails that way. The README quick start now starts withfluframe doctor. -
Fixed: project names that could not work were accepted.
dio,intl,go_router,shared_preferences,firebase_coreand the rest of the generated app's own dependencies were let through, then failed withA package may not list itself as a dependency— after generation had written the whole project. Windows device names (con,aux,nul,com1…lpt9) and leading underscores are refused too, and the rejection message now names the actual reason. A test fails if the dependency list drifts fromtemplate/pubspec.yamlor the addons. -
Fixed: an incomplete template bundle produced a "successful" empty app. The overlay deletes the scaffold's
lib/before copying, so a bundle missinglib,testorpubspec.yamlyielded a project with no source — reported as a warning, exit 0. It is now a hard failure with a reinstall pointer. -
e2e now covers the shipped default platform set (all six), which no test had ever generated, and runs on Windows and macOS.
-
Fixed: generated apps still showed fluFrame's name. The home screen greeted users with "Welcome to fluFrame!" in every locale and the root widget was called
FluFrameApp, because the rewriter only replaces four exact tokens and none of those spellings is one. The greeting now uses theFluFrame App/FluFrame 앱/FluFrame アプリtokens, the widget isAppRoot, and a test walks the real template sources so this cannot come back. -
Fixed:
upgrade --applyresurrected files you deleted. A missing local file was treated as new, so deleted files came back — and a renamed one came back beside its copy, declaring the same class twice. They are now reported asdeleted locally - not restored;--restore-deletedopts in. -
Template: uncaught async errors no longer vanish in release builds.
onPlatformErrorreturnedtrue, suppressing Flutter's default log path, while the app's only sink wasdart:developer— a VM service channel that does not exist in release. -
Template: an unmatched deep link is no longer a dead end. There was no
errorBuilder, so go_router's default error page took over, and its only button navigates to/— a route the app did not define. There is now a localized not-found screen and a/→/homeredirect. -
Template: the offline cache no longer hides server errors. It caught every
ApiException, so a 404 or a 500 was answered with a stale copy andPostDetailScreen's 404 branch could never run. OnlyNetworkExceptionfalls back now. -
Fixed:
--backend firebaseopened to a black screen.Firebase.initializeAppran beforerunAppwith a placeholder that throws untilflutterfire configure— so the very first launch of every generated app died with no widget tree, no red error screen, and nothing in the log. It is now initialized after the error hooks, inside a try/catch, and the app runs on the in-memory auth fake until it is configured.--backend supabasebehaves the same way with an emptySUPABASE_URL, matching how the Sentry and Amplitude addons already stayed inert without their keys. -
Fixed:
--error-reporting sentrylost most of what Sentry does.SentryFlutter.initwas called withoutappRunner, so zone errors were never captured — and the template then overwrote both handlers the SDK had just installed. The app now runs insideappRunner:, the SDK's integrations chain onto the template's handlers, and the hand-rolledSentry.captureExceptioncalls are gone (they double-reported and marked everythinghandled: true). -
Fixed: addon dependencies were installed unpinned.
pub addwith no constraint resolves to whatever is latest, while the injected sources target one major — so an upstream major release would break newly generated apps on its release day. All four addons now pin^major.minor, and a test fails if any addon declares a dependency without a constraint. The constraint is written intopubspec.yamlrather than passed on the command line:flutteris a batch file on Windows, andcmd.exeeats the^. -
Fixed:
--no-pubstill resolved dependencies. It skippedpub getbut ran onepub addper addon anyway. It now installs nothing and prints theflutter pub addline to run by hand. -
Fixed: setup notes sent secrets into a committed file. All three addons told users to put keys in
env/dev.json, which the template's own.gitignoredocuments as committed safe defaults — secrets belong inenv/*.local.json. The notes and the committed placeholders now agree. -
Fixed: an addon could make an app permanently un-upgradable.
upgraderebuilt the merge base by replaying the current CLI's addon anchors against an archived bundle, so moving any anchored template line blocked the upgrade at exit 70 for every app generated with an addon. Bundles now ship their owntemplates/addons.json, and when the addons cannot be replayed at all both sides are rebuilt without them and the report says so. -
Fixed: the injected
firebase_optionsimport landed at an unsorted position (its anchor named an import that stopped being last), as did the Sentry SDK import.dart fix --applyhid both — except under--no-puband on the upgrade path. -
Template: screen-view analytics report the route pattern (
/home/posts/:id), not the resolved path — concrete paths explode dashboard cardinality and would ship path secrets to a third party. The router alsoreads the analytics provider instead of watching it, which would have rebuilt the router and lost the navigation stack.
1.1.0 #
- Template: the settings language picker no longer clips its labels on phone-width screens. Four segments plus a selected-state check icon overflowed a 390pt viewport, wrapping "English" mid-word; the picker now uses the same wrapping chips as the theme-colour section. Regression test pins the labels to a single line at phone width.
- Template:
go_routermoved to^17.4.0, and the committed lockfiles (template and both examples) were refreshed — this drops the retractedbuild_daemon4.1.3 that generated apps previously inherited, and picks upbuilt_value8.12.7. - Docs: README (English and Korean) and the pub.dev page now show screenshots of a generated app — home, the sample REST feature, and the settings tab in dark mode.
1.0.0 #
Stability declaration — fluframe now follows semantic versioning
against a documented public contract (see docs/versioning.md in the
repository): CLI surface, generation guarantees, rename tokens,
.fluframe.json schema, the stackable addon mechanism, and the upgrade
path.
fluframe upgrade [--apply] [--from]— pull template updates into an existing app via a per-file three-way merge (base reconstructed from the pub.dev archive of the version the app was generated with); dry-run by default, git conflict markers, removals reported but never auto-deleted.createnow records generation metadata (.fluframe.json, schema 1: version + addon combo) — the contractupgradereads.--analytics amplitude— wires the analytics seam to Amplitude with an API-key-guarded provider swap; stackable with--backendand--error-reporting.
0.12.0 #
- Template: analytics seam — an
AnalyticsServiceinterface with a debug-logging default, automatic screen-view tracking for every navigation (bottom tabs included), and a sample domain event. Swap one provider to wire a real product-analytics SDK;--analyticsaddons are next on the roadmap.
0.11.0 #
--error-reporting sentry: wires sentry_flutter into the template's error hooks with DSN-guarded initialization (emptySENTRY_DSNkeeps Sentry disabled, so fresh apps run untouched). Stackable with--backend, e.g.fluframe create my_app --backend supabase --error-reporting sentry.
0.10.0 #
- Template: Japanese localization (full
app_ja.arb+ language picker entry) alongside English and Korean;FluFrame アプリjoins the rename tokens so generated apps stay unbranded in every locale. - Template: sixth theme preset (teal).
0.9.0 #
- Template: global error handling hooks —
FlutterError.onErrorandplatformDispatcher.onErrorroute every uncaught error through one documented file (core/logging/error_handlers.dart), the exact seam for wiring Sentry/Crashlytics later.
0.8.0 #
- Template: offline fallback cache for the posts sample — the last successful response is persisted and served automatically when the network fails, demonstrating the repository-decorator pattern with zero new dependencies.
0.7.1 #
- Actually ships the theme color presets announced in 0.7.0 — a release-ordering mistake published 0.7.0 from a commit that did not yet contain them. No other changes.
0.7.0 #
⚠️ Published without the preset feature described below — use 0.7.1.
- Template: five selectable theme color presets (persisted) join the existing light/dark mode — pick Indigo/Emerald/Crimson/Amber/Violet in Settings, survives restarts.
0.6.0 #
fluframe doctor: one command to verify the machine can generate and run fluFrame apps — Flutter/Dart/git probes with actionable fixes and a template-bundle check.
0.5.0 #
--backend firebase: generate an app with Firebase Auth wired into the auth seam. Ships a compile-safeDefaultFirebaseOptionsstub that throws with clear guidance until you runflutterfire configure— honest about the one step that cannot be automated.
0.4.0 #
--backend supabase: generate an app with Supabase Auth already wired into the auth seam —SupabaseAuthRepository, env-based configuration (SUPABASE_URL/SUPABASE_PUBLISHABLE_KEYvia--dart-define-from-file), and setup notes. The generated app's test suite stays green and offline regardless of backend.- Backend addon mechanism (ADR 0001): dependencies via
flutter pub add, bundled addon files, and anchored patches that fail loudly if the template and addon ever drift apart.
0.3.0 #
- Backend-neutral auth scaffold in the generated app (zero new
dependencies): email/password login screen with validation, auth-gated
profile tab, GoRouter redirect with return-path, and a session that
survives restarts — all behind a single
AuthRepositoryseam. - Swap-in guides for Supabase and Firebase auth (one provider
override) in the repository's
docs/guides/.
0.2.0 #
--orgis now validated before anything runs: malformed identifiers (spaces, digit-leading or empty segments) produce a clear usage error instead of a downstreamflutter createfailure.- A missing Flutter SDK now prints a friendly message with the install
guide and exits 69, instead of dying with a raw
ProcessException— on both the direct-spawn and Windows shell paths.
0.1.0 #
- Initial release.
fluframe create <name>scaffolds a production-ready Flutter app:- Riverpod 3 (manual notifiers), go_router
StatefulShellRoutetabs - freezed 3 + json_serializable models, dio with typed error mapping
- Localization (en/ko) via
flutter gen-l10n, Material 3 light/dark themes - Persisted settings (
SharedPreferencesAsyncbehind aKeyValueStore) --dart-define-from-fileflavors, very_good_analysis, unit + widget tests
- Riverpod 3 (manual notifiers), go_router
- Options:
--org,--description,--output-directory,--platforms,--no-pub.