Skip to content

Require Dart 3.13 / Flutter 3.47 and ride the breaking window - #4

Merged
jamiewest merged 13 commits into
mainfrom
chore/dart-3.13
Aug 17, 2026
Merged

Require Dart 3.13 / Flutter 3.47 and ride the breaking window#4
jamiewest merged 13 commits into
mainfrom
chore/dart-3.13

Conversation

@jamiewest

Copy link
Copy Markdown
Owner

Raises the SDK floor to Dart 3.13 (extensions from ^3.6.0, extensions_flutter from ^3.10.1, plus an explicit flutter: ">=3.47.0"), and lands everything that should ride the resulting breaking release. Twelve commits, each scoped to be independently revertible.

The bump itself

  • No source migration was needed. The codebase never used final/var on normal function parameters — the one idiom 3.13 makes a compile error. porting.md's warning to the contrary was wrong and has been corrected.
  • Tall-style reformat (455 files, ±4.4k lines) in its own isolated commit — the formatter is language-version-gated at 3.7, and there is no supported way back to short style.
  • dart fix --apply for the newly gated lints (67 fixes, dominated by prefer_initializing_formals — 3.13 strips the underscore from named initializing formals, so call sites are unaffected).

Correctness fixes found along the way

  • SpeechToTextClient.getStreamingText now streams SpeechToTextResponseUpdate (was Stream<SpeechToTextResponse>, diverging from upstream; the ported update type was dead code). Interface, all four decorators, the OpenAI client, and test fakes updated; toSpeechToTextResponse() combines updates.
  • lines_longer_than_80_chars is now actually enforced. It was written as - lines_longer_than_80_chars: true, which the analyzer silently ignores; the repo's own 80-char rule had never applied. 22 violations fixed across both packages.
  • CI now gates on dart analyze --fatal-infos and dart format --set-exit-if-changed (separate job; --fatal-infos is load-bearing — every lint this repo relies on reports at info severity, verified against a probe file).

Breaking API changes (riding 0.8.0)

  • IConfigurationConfiguration (deprecated typedef kept for one release). IConfigurationSection keeps its prefix as a documented collision exception (concrete ConfigurationSection owns the bare name).
  • IMetricListenerConfigurationFactory removed — single implementation, collapsed into the concrete per the porting matrix.
  • InvalidOperationException exported from system.dart (was thrown by public APIs but unimportable).

Ported from upstream Microsoft.Extensions.AI

Six *Extensions conveniences (embeddings, image, STT, TTS-update, STT-update, hosted files), TextToSpeechResponseUpdateKind (+ kind on the update), and AnonymousDelegatingEmbeddingGenerator with EmbeddingGeneratorBuilder.useGenerate — 32 new tests.

Drift bookkeeping

Two full-scope audits (before/after) recorded in the ledger: upstream unchanged; every evaluation/OpenAI scope question now has a ruled N/A or a characterized open gap; ai reduces to the AIJsonSchemaTransform trio; dartdoc warnings 19 → 16.

Verification: both packages analyze clean, format-idempotent, 1,029 + 47 tests pass. dart doc region references all resolve (38 regions / 40 refs).

🤖 Generated with Claude Code

jamiewest and others added 13 commits August 13, 2026 13:08
Raises the SDK floor on both packages so the workspace can use Dart 3.13
language features — primary constructors above all, which line up 1:1
with the C# primary constructors used throughout upstream
dotnet/extensions.

extensions moves from ^3.6.0 and extensions_flutter from ^3.10.1.
extensions_flutter also gains an explicit flutter >=3.47.0 constraint,
the release that ships Dart 3.13.

No source changes are required by the bump: the codebase has no
final/var-on-parameter declarations, so nothing becomes a compile error
at language version 3.13.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
dart fix --apply, 67 fixes across 42 files: 63 prefer_initializing_formals,
3 unnecessary_underscores, 1 use_null_aware_elements. dart analyze is clean
again afterwards, and all 997 tests pass.

prefer_initializing_formals accounts for nearly all of it, rewriting
`Foo({required String bar}) : _bar = bar` as `Foo({required this._bar})`.
Dart 3.13 strips the leading underscore from a named initializing formal,
so `Foo(bar: …)` call sites are unaffected — that is the same language
change that lets primary constructors initialize private fields, so this
is the form the port moves toward anyway.

Kept separate from the reformat so it can be reverted on its own if the
constructor style is not wanted; the alternative is disabling
prefer_initializing_formals in analysis_options.yaml.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Formatting only — no behavioural change. dart analyze is clean and all
997 + 47 tests pass.

The tall-style formatter is gated on language version 3.7, so raising the
floor to 3.13 switched it on for the whole workspace. Landing it as one
isolated commit keeps it out of unrelated diffs; leaving it undone would
make committed formatting non-idempotent, so the next format-on-save or
`dart format .` would drag it into whatever change happened to be in
flight. There is no supported way to stay on short style at language
version 3.7 or above — `formatter: page_width` sets width, not style.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
analysis_options.yaml wrote the rule as `- lines_longer_than_80_chars: true`.
That YAML is a list entry holding a map rather than a rule name, so the
analyzer silently ignored it and the repo's own 80-character rule had never
been enforced. Correcting it surfaced 17 violations in extensions.

extensions_flutter had no such rule at all despite being held to the same
convention; adding it surfaced 5 more.

All 22 are comments, doc text, or strings the formatter cannot break, so
they are rewrapped by hand. Three needed a small restructure:

- CollectionModelBuilder: the VectorDataStrings.configuredEmbeddingType*
  calls are too long to fit at their nesting depth, so the ternary moves
  into _unsupportedConfiguredEmbeddingTypeMessage.
- ToolCallAccuracyEvaluatorContext: the params suffix moves into
  _paramsSuffix.
- The intent-resolution and task-adherence evaluators hoist the tool list
  into a local instead of interpolating a full map/join chain.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
porting.md's primary-constructor section said the workspace could not use
them and warned that final/var-on-parameters made the bump "a real
migration". Both are now wrong: the constraints are raised, and that idiom
never appeared in this codebase — zero occurrences, zero analyzer errors at
language version 3.13. Replaces the warning with what the bump actually
cost (formatting and lints), adds the underscore-stripping rule for named
initializing formals, and adds a Formatting section covering tall style and
the correct YAML for lines_longer_than_80_chars.

Marks the drift ledger's analysis_options hygiene item closed; the other
two items in that entry are untouched.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
CI ran only `dart test`, so nothing guarded the analyzer or the formatting
the 3.13 move just normalised — the tall-style output is stable only while
the SDK floor holds, and a stray reformat would otherwise land unnoticed.

Uses --fatal-infos deliberately. Every lint this repo relies on reports at
info severity, and a bare `dart analyze` exits 0 on those: verified against
a probe file with an 81-character line, where plain analyze exits 0 and
--fatal-infos exits 1. Without the flag the step would pass on exactly the
violations it exists to catch.

Runs as its own job rather than as steps in `test`. The reporter steps are
guarded with `if: success() || failure()`, so a lint failure early in the
test job would leave them reading a test-results JSON that was never
written.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
No new upstream drift: upstream counts unchanged since 2026-08-13 and every
unmatched type across all 15 scope rows was already an N/A entry or a known
open item. API surface sample (10 abstractions) found no member gaps.

New in the ledger: the IConfiguration/IConfigurationSection I-prefix
inversion in configuration/ (public API, so a next-major item — three prior
audits missed it because they grepped only `abstract interface class I`),
test-coverage denominator recounted to 489 after the reformat, and
confirmation dates refreshed.

Also fixes the stale primary-constructor note in .claude/commands/drift.md,
which still described the workspace as pinned to ^3.6.0 after the 3.13
bump; it now defers to porting.md as authoritative.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Resolves the ledger's first open priority. Every previously-unmatched type
in the two 2026-08-13 scope rows now has a recorded ruling, each verified
against fetched upstream source rather than assumed:

- N/A by library: Evaluation.Console (executable CLI), Reporting.Azure
  (Azure Storage SDK bindings), and the entire OpenAI adapter layer (no
  allowlisted OpenAI Dart SDK to adapt; the Dart open_ai/ is a deliberate
  hand-rolled client).
- N/A by collapse (~20 types, with the Dart counterpart named in each row):
  the *Extensions helpers fold into instance methods or existing extension
  types, the STJ serialization layer into dart:convert, SimpleChatClient
  into ResponseCachingChatClient, ContentSafetyService + payloads into the
  ContentSafetyEvaluator base (with a fidelity caveat recorded).
- Four genuine gap clusters stay open, now precisely characterized: the
  report-generation pipeline, IntentResolutionRating + the evaluator's JSON
  protocol, result-level bulk helpers, and disk-store hardening
  (path-traversal guard + natural iteration ordering).

62 of the audit's 112 open types dissolve into recorded decisions.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Closes most of the ledger's ai priority. Ported from upstream
Microsoft.Extensions.AI(.Abstractions), each as a Dart extension with the
getService overloads collapsed per the porting rules, all with tests:

- EmbeddingGeneratorExtensions: generateEmbedding, generateVector,
  generateAndZip (Dart records for the C# tuple array)
- ImageGeneratorExtensions: generateImages, editImage, editImages (the
  bytes+filename overload stays unported pending a MediaTypeMap)
- SpeechToTextClientExtensions: DataContent conveniences
- SpeechToTextResponseUpdateExtensions / TextToSpeechResponseUpdateExtensions:
  update-stream combining, with adjacent-text coalescing on the STT side
  and audio-chunk concatenation on the TTS side (the Dart response carries
  a single DataContent rather than upstream's content list)
- HostedFileClientExtensions: uploadDataContent, downloadFromContent,
  downloadAsDataContent (metadata via getFile), getMetadata
- TextToSpeechResponseUpdateKind + a kind field on the update, defaulting
  to audioUpdating per the upstream property initializer
- AnonymousDelegatingEmbeddingGenerator, wired via
  EmbeddingGeneratorBuilder.useGenerate

Breaking fix riding the 0.8.0 window: SpeechToTextClient.getStreamingText
returned Stream<SpeechToTextResponse> while upstream streams updates — the
ported SpeechToTextResponseUpdate type was dead code. The interface, all
four decorators, the OpenAI client, and the test fakes now stream updates.

Also exports InvalidOperationException from system.dart: public APIs threw
it, but consumers could not catch it by type without importing from src/.

N/A rulings recorded: TextToSpeechClientExtensions (getService-only),
HostedFileDownloadStream (collapsed onto Stream<List<int>> + getFile
metadata), AIJsonSchemaCreate{Context,Options} (creation side). The
AIJsonSchema transform walker is re-scoped in the ledger as the one
remaining open ai port. New small gap recorded: hosted-file scope
plumbing.

1,029 tests pass (32 new); analyze and format clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The no-I-prefix rule inverted in two places, both public API, both fixed
now because the 3.13 floor bump already makes the next release breaking:

- configuration: `Configuration` is the abstract type; `IConfiguration`
  stays as a @deprecated typedef for one release. The old backwards
  compat alias pointed the wrong way (bare name aliasing the I-name).
  `IConfigurationSection` keeps its prefix as a documented exception —
  the bare name is taken by the concrete `ConfigurationSection`, the same
  collision rule as the globbing IPattern family.
- diagnostics: `IMetricListenerConfigurationFactory` had exactly one
  implementation and no other references, so it collapses into the
  concrete `MetricListenerConfigurationFactory` per the porting matrix
  ("interface with one implementation -> single concrete class"). The
  ledger's earlier rename suggestion assumed no concrete existed.

Also fixes the four stale `[ILoggerProviderConfiguration*]` dartdoc
references in logging/ — those types were renamed long ago but their docs
still pointed at the C# names (4 of the 19 standing dartdoc warnings).
The two `i_*.dart` filenames in logging/ stay (types inside are already
clean); recorded in the ledger as cosmetic.

Both packages: analyze clean, format clean, 1,029 + 47 tests pass.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
dart doc could not resolve a bare [audio] from the extension's scope; the
target is TextToSpeechResponse.audio. Back to 16 standing warnings, three
fewer than before this branch.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Second same-day full-scope run. Upstream file sets verified identical to
the morning fetch; the open set reduces to exactly what the ledger
records. The OpenAI and safety-payload N/A rows previously used glob
shorthand (OpenAIRealtime*, Payload{Format,...}) that the audit's literal
matcher cannot expand, which made ruled types look open — names are now
spelled out.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The new CI formatting gate failed on its first run, correctly:
logger_provider_configuration_factory.dart was edited by the
IConfiguration rename sweep and never re-formatted. The local checks
before pushing ran `dart format --output=none --set-exit-if-changed` at
the end of an && chain and read its text output — where "1 changed" is
the failure signal, not a fix — so the non-zero exit was dropped twice.

Verified by exit code this time; both packages format clean, analyze
clean, 1,029 tests pass.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@jamiewest
jamiewest merged commit b6ea6aa into main Aug 17, 2026
2 checks passed
@jamiewest
jamiewest deleted the chore/dart-3.13 branch August 17, 2026 16:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant