Skip to content
This repository was archived by the owner on Sep 10, 2026. It is now read-only.

Give the scaffolder's foreign-file edits one whitespace rule - #58

Merged
seenu-k merged 1 commit into
mainfrom
feature/append-never-insert
Aug 12, 2026
Merged

seenu-k merged 1 commit into
mainfrom
feature/append-never-insert

Conversation

@seenu-k

@seenu-k seenu-k commented Aug 12, 2026

Copy link
Copy Markdown
Collaborator

create-eigen-game owns most of what it writes — four template trees copied whole and token-substituted. Three edits are different: they modify files flutter create produced, in a directory flutterfire configure will edit next. This gives those three one shared rule and writes down the doctrine behind them.

The bug

Each of the three normalised newlines itself, with two different answers:

Site Expression
desugaring endsWith("\n") ? "\n" : "\n\n"
release signing endsWith("\n") ? "" : "\n"
launcher icons endsWith("\n") ? "" : "\n"

Desugaring always left a blank separator line, the other two never did. Since desugaring runs first and leaves a trailing newline, the release-signing block then landed flush against it in every generated project. appendBlock now owns the whitespace and nothing else — one blank line before, one newline after, block constants carrying neither — and the scaffold test pins the separation rather than only the contents.

Verified by rendering a scaffold against a realistic flutter create stub and reading the output; the appended blocks are now spaced like Flutter's own.

A comment that said the opposite of its code

A comment beside the release-signing block claimed it still prepends import java.util.Properties. That edit was removed when AGP 9 broke it — the block parses key.properties with the Kotlin stdlib now. The comment described the exact thing the surrounding code exists to avoid, so it is rewritten to record the history without claiming a prepend exists.

The doctrine, in MAINTAINERS.md

New section, Editing files the scaffolder does not own:

  • Append, never insert, with the three edits tabled against the string that identifies each, and why includes("signingConfigs") would never fire (Flutter's own template already contains it).
  • Why FlutterFire's // START:/// END: markers are deliberately not copied. They are attribution, not machinery: in flutterfire_cli 1.4.1 the two constants appear in ten places and every one is string construction — nothing reads them back. Its idempotency comes from contains(_googleServicesPluginClassPath), the same mechanism used here. FlutterFire needs a regex anchor because it must inject into the single permitted plugins { } block; an append needs no anchor, so a marker would add text without adding capability. One shared marker text also means it could not replace selectively even if it read them.
  • The one condition that earns a marker pair: a block whose content must be found and replaced on a later run, which a content probe cannot do — includes("releaseKeyProperties") answers "is mine here?" but never "where does mine end?". That arrives with an upgrade command and not before, so the section records the shape to use then: one pair per block, versioned marker text, guarded on exactly one properly nested pair.
  • Never in templates/ — those files are wholly owned and rendered whole.
  • Configuration values are assigned, not appended, into the declared empty slots in wrangler.jsonc and app-config.json, plus the JSON-versus-JSONC rule: plain JSON is decoded and re-encoded, commented JSONC has its single assignment rewritten in place, and the safety there is the match count rather than the pattern.

A companion note lands in eigen-flutter's firebase_link.dart, where the JSONC half is implemented: eigeninteractive/eigen-flutter#36.

Verification

pnpm lint clean · pnpm -r typecheck clean · pnpm -r test 358 passed.

🤖 Generated with Claude Code

Three functions append a block to a file `flutter create` produced. Each
normalised newlines itself, with two different answers, so the desugaring
block got a blank line before it and the other two did not: in a generated
project the two Gradle blocks landed flush against each other. `appendBlock`
now owns that and nothing else, with the block constants carrying no leading
or trailing newline of their own, and the scaffold test pins the separation
rather than only the contents.

A comment beside the release-signing block claimed it still prepends
`import java.util.Properties`. That edit was removed when AGP 9 broke it, so
the comment described the one thing the surrounding code exists to avoid.

MAINTAINERS.md gains the doctrine these three follow: append never insert,
why each block is recognised by a content probe on its own payload, and why
FlutterFire's `// START:`/`// END:` markers are deliberately not copied.
Those markers are attribution rather than machinery, never read back by
flutterfire_cli itself, and an append needs no anchor. The one thing that
would earn a marker pair is a block whose content must be found and replaced
on a later run, which belongs to an upgrade command that does not exist yet;
the section records the shape to use when it does. It also records that
configuration values are assigned into declared slots instead, and the
JSON-versus-JSONC rule governing how.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@seenu-k
seenu-k merged commit 7ef586b into main Aug 12, 2026
3 checks passed
@seenu-k
seenu-k deleted the feature/append-never-insert branch August 12, 2026 09:28
@eigen-release eigen-release Bot mentioned this pull request Aug 12, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant