Date: 2026-02-25 Version audited: v2.0.0 Scope: 211 source files, 10 BLoCs, 13 file parsers, 6 AI/translation services, Windows/macOS Audit type: Read-only analysis — no code was modified
All critical issues and high-priority warnings from this audit were resolved in a single session
using 5 parallel teammates. Final state: flutter analyze — 0 errors; flutter test — 259 passed,
0 failures, 0 skipped.
| Issue | Status | Resolved By |
|---|---|---|
God widget file_comparison_view.dart (3,819 lines) |
✅ RESOLVED | T2 — extracted DiffListItem, ExportService, FilePickerDropZone, PlatformTaskbarService; 895 lines removed |
Missing path_provider / provider in pubspec.yaml |
✅ RESOLVED | T1 — flutter pub add path_provider provider |
ComparisonBloc zero test coverage |
✅ RESOLVED | T4 — 10 new tests covering all primary flows |
| WCAG Level A/AA accessibility failures | ✅ RESOLVED | T5 — Semantics, tooltips, non-color indicators, contrast fix, colorblind wizard step |
SettingsBloc (1,772 lines, 104 events) |
✅ RESOLVED | T3 — split into AppearanceBloc, AiServicesBloc, FileHandlingBloc |
Dead code in settings_state.dart |
✅ RESOLVED | T1 — removed duplicate if block |
| Event base classes missing Equatable | ✅ RESOLVED | T1 — 6 event hierarchies updated (ComparisonEvent, GitEvent, HistoryEvent, ProgressEvent, TranslationEvent, FileWatcherEvent) |
macos_ui declared but unused |
✅ RESOLVED | T1 — removed from pubspec.yaml |
57 deprecated withOpacity() calls |
✅ RESOLVED | T1 — replaced with withValues(alpha:) across 6 files |
context.watch<SettingsBloc>() in non-build helpers |
✅ RESOLVED | T1/T2 — state read at top of build(), passed as parameters |
| Duplicated HSL color computation in 4 theme factories | ✅ RESOLVED | T1 — extracted _computeColorPalette() |
windows_taskbar imported directly in widget/BLoC layers |
✅ RESOLVED | T2 — PlatformTaskbarService abstraction created |
| History recording in presentation layer BlocConsumer | ✅ RESOLVED | T2 — moved into ComparisonBloc |
| Test coverage 12% with 7 skipped tests | ✅ RESOLVED | T4 — 259 tests passing, 0 skipped; added parser tests (ARB/CSV/Properties/RESX), bloc tests (Comparison/Translation/Git), fixed all skipped tests |
Blank tooltip: '' on PopupMenuButton |
✅ RESOLVED | T5 — 'More actions' |
| Icon-only buttons with no accessible label | ✅ RESOLVED | T5 — 13 buttons given tooltip/Semantics |
| Default accent color failing contrast on dark theme | ✅ RESOLVED | T5 — 0xFF7B61FF → 0xFF9B8FFF |
| No colorblind preset visible in first-run wizard | ✅ RESOLVED | T5 — colorblind-friendly step added |
| GitBloc mutable fields outside Freezed state | ✅ RESOLVED | T1 — replaced with state-derived getters |
Four specialized agents audited the codebase across architecture, BLoC state management, test
coverage, and accessibility. The app has a well-structured foundation — BLoC layering is largely
correct, the advanced_diff subsystem is well-decomposed, and the theme system is complete. However
five cross-cutting issues stand out as the highest-priority problems across all four dimensions:
| # | Fix | Areas Affected | Effort | Risk if Left Unfixed |
|---|---|---|---|---|
| 1 | Decompose file_comparison_view.dart (3,819 lines, 34 setState, dual state sources, per-item BLoC watches) |
Architecture, BLoC | Large (1–2 weeks) | Increasing render lag, maintenance collapse, and correctness bugs as the file grows |
| 2 | Declare missing transitive dependencies (path_provider, provider absent from pubspec.yaml) |
Architecture | Trivial (< 5 min) | A single upstream package update silently breaks the build |
| 3 | Test ComparisonBloc — the core feature orchestrator has zero test coverage |
Testing | Medium (2–3 days) | Silent regression in the primary user workflow |
| 4 | Fix WCAG Level A accessibility failures — 35+ interactive elements have no accessible name/role; default diff colors fail for colorblind users | Accessibility | Medium (1–2 days) | Exclusion of users with disabilities; fails WCAG 1.4.1 and 4.1.2 |
| 5 | Split SettingsBloc (1,772 lines, 104 event classes) into 4–5 sub-BLoCs |
BLoC, Architecture | Large (3–5 days) | Continued growth of an already-unmaintainable class; every settings change touches this file |
Full report: dev/active/flutter-arch-audit/flutter-arch-audit-code-review.md
The layered architecture (presentation / business_logic / data / core) is well-intentioned and
mostly respected. The advanced_diff subsystem is the model to follow internally — focused widgets,
clean ChangeNotifierProvider scoping, dedicated controller. However file_comparison_view.dart
has grown to 3,819 lines and represents a single point of structural, performance, and correctness
risk. Two production dependencies resolve only transitively (not declared in pubspec.yaml), making
the build fragile to upstream changes.
-
file_comparison_view.dart— God Widget (3,819 lines, 34 setState)- File:
lib/presentation/views/file_comparison_view.dart - Accumulates every responsibility: file picking, drag-and-drop (lines 511–666), tutorial orchestration (lines 252–410), export logic (lines 1,839–2,070), diff list rendering (lines 3,057–3,398), analytics bar, search, pagination, chart, file watcher coordination.
_buildDiffListItemcallscontext.watch<SettingsBloc>()andcontext.watch<ThemeBloc>()inside aListView.builderitem builder — every settings or theme change rebuilds the entire visible list without selectivity.- Severity: Critical — maintenance, performance, correctness.
- File:
-
Dual state sources in
_FileComparisonViewState- File:
lib/presentation/views/file_comparison_view.dart(line 1,154) _file1,_file2,_isBilingualMode, and_latestComparisonResultare held in widget state AND in the BLoC. AfterComparisonSuccess, local fields are re-synced from BLoC viasetState. If BLoC state resets between navigations, widget state can be stale.- Severity: Critical — correctness risk.
- File:
-
package:providerused in 10 files but not declared inpubspec.yaml- Files:
lib/presentation/views/advanced_diff/(10 files), includingadvanced_diff_view.dart:6 - Resolves transitively through
flutter_bloc. A version bump in any dependency could silently break all 10 files. - Severity: Critical — build fragility.
- Files:
-
package:path_providerused inmain.dartbut not declared inpubspec.yaml- File:
lib/main.dart:14 - Same transitive-only risk.
- Severity: Critical — build fragility.
- File:
-
windows_taskbarimported directly in presentation and BLoC layers- Files:
lib/presentation/views/file_comparison_view.dart:40,lib/business_logic/blocs/progress_bloc.dart:5 - Platform-specific packages should be behind a service abstraction, not imported at widget/BLoC
level. Calling
WindowsTaskbar.setProgressMode(...)from a widget violates layering. - Severity: Critical — platform abstraction violation.
- Files:
_buildDiffListItemreconstructsTextStyle,BoxDecoration, andBorderRadiuson every call — extract to aStatelessWidgetwithBlocSelectorto allow element tree diffing.- Other views are growing toward the same pattern:
git_comparison_view.dart(2,680 lines, 21 setState),quality_dashboard_view.dart(2,465 lines),history_view.dart(2,077 lines). - Both
hiveandsqfliteare used as persistence backends — consider migrating Translation Memory to Hive to eliminatesqfliteunless relational queries are required. - 57 remaining
withOpacity()calls (deprecated) vs. 332 modernwithValues(alpha:)calls. macos_uideclared inpubspec.yaml:61but no import found inlib/— likely unused.dependency_overrides: collection: 1.19.0inpubspec.yaml— temporary workaround that should be removed when upstream packages release compatible versions.- 31 scattered
Platform.isWindows/isMacOS/isLinuxchecks in the presentation layer — platform capability guards belong in a service layer, not views.
- Add
path_providerandprovidertopubspec.yaml— zero code change, removes transitive risk immediately. Run:flutter pub add path_provider provider - Extract
_buildDiffListItemintoDiffListItemwidget — usesBlocSelectorto subscribe only to the fields it needs. Begin decomposingfile_comparison_view.dart. - Move export logic (~200 lines) to
ExportServiceinlib/core/services/export_service.dart - Extract file picking and drag-and-drop into
FilePickerDropZonewidget — removes ~15 setState calls and ~400 lines. - Encapsulate
WindowsTaskbarbehindPlatformTaskbarServicewith a no-op on non-Windows. - Remove
macos_uiafter confirming zero usage withdart pub deps. - Consolidate theme color computation — extract
_computeColorPalette(Color accent)helper to eliminate ~60 lines of duplication across the four theme factories. - Replace
withOpacity()globally withwithValues(alpha: ...).
Full report: dev/active/bloc-audit/bloc-audit-code-review.md
The BLoC architecture is largely sound: Freezed union types are used consistently for all states,
BLoC-to-BLoC coordination is correctly handled via BlocListener in the widget layer (not direct
coupling), and stream subscriptions are clean. The primary concerns are SettingsBloc's extreme
bloat (1,772 lines, 104 event classes), business logic that has leaked into FileComparisonView's
BlocConsumer listener, a dead code bug in SettingsState, and event class inconsistency across
BLoCs.
-
Dead code bug in
SettingsState.getEffectiveDefaultAiModel()- File:
lib/business_logic/blocs/settings_bloc/settings_state.dart:91–98 - The second
if (projectSettings?.defaultAiModel != null)block is an identical duplicate of the first — it is unreachable dead code. The method was not reviewed carefully after editing. - Severity: Critical — correctness.
- File:
-
Business logic in BlocConsumer listener — history recording in widget
- File:
lib/presentation/views/file_comparison_view.dart:~1178–1241 - The
BlocConsumer<ComparisonBloc>listener builds a fullComparisonSession, computes diff statistics, runs coverage metrics, and dispatchesAddToHistory. This is business logic in a widget.DirectoryComparisonBlocandGitBlocalready do this correctly inside the BLoC —ComparisonBlocdoes not, forcing the widget to compensate. - Severity: Critical — architectural inconsistency, business logic in presentation layer.
- File:
-
SettingsBlocextreme bloat — 102 handlers, 104 event classes, 1,772 lines- File:
lib/business_logic/blocs/settings_bloc/settings_bloc.dart - Handles AI services, developer tools, appearance, encoding, file handling, window bounds,
macOS integration, translation memory, telemetry, startup options, and onboarding — all in one
class. The subdirectory (
settings_bloc/) signals awareness of complexity, but no decomposition has happened. - Severity: Critical — maintainability.
- File:
- Event class inconsistency — 3 BLoCs use
Equatableevents, 7 use plain abstract classes. WithoutEquatable, duplicate events aren't deduplicated; events log as generic objects in BlocObserver. Affected (no Equatable):ComparisonBloc,GitBloc,HistoryBloc,ProgressBloc,TranslationBloc,FileWatcherBloc. GitBlocmutable instance state —_cachedBranches,_cachedCommits,_currentMode,_currentRepoPath(lines 163–165) exist as instance fields parallel to the Freezed state. If the BLoC is recreated, internal cache is lost while emitted state may have data.context.watchin non-build helper methods —_buildAnalyticsAndActionsBar(line 2525) and_buildDiffRow(line 3064–3100) callcontext.watchin helpers instead of receiving state as parameters. Hides dependencies and triggers full widget rebuilds.HistoryViewduplicates history data from BLoC — copiesHistoryLoaded.historyinto_allHistoryvia setState and re-readsProjectBloc.stateinside setState callbacks.QualityDashboardView._refreshDashboard()reads from two BLoCs, filters history, and builds a future — all inside the widget. This is orchestration logic.buildWhenunderused —SettingsBlocemits on every settings change; only 2 of 15BlocBuilder<SettingsBloc>usages havebuildWhenpredicates, causing unnecessary rebuilds.- Sealed class migration not warranted — Freezed already provides exhaustive matching. This is a future-state consideration only.
- Fix dead code in
settings_state.dart:95–97— remove the duplicateifblock. (5 minutes) - Move history recording into
ComparisonBloc— passHistoryRepositorytoComparisonBlocat construction, matchingDirectoryComparisonBlocandGitBloc. (~1–2 days) - Add
Equatableto the 6 inconsistent event base classes — mechanical, low-risk. (~0.5 day) - Move GitBloc parallel state into Freezed state — especially
_currentRepoPath. (~0.5–1 day) - Pass state as parameters to helper build methods in
FileComparisonViewinstead of callingcontext.watchinside helpers. (~1 day) - Split
SettingsBlocinto sub-BLoCs — suggested order:AppearanceBlocfirst (fewest cross-dependencies), thenAiServicesBloc,FileHandlingBloc, thenGeneralSettingsBloc. (~3–5 days, careful migration ofMultiBlocProviderinapp.dartand all consumers)
Full report: dev/active/test-coverage-audit/test-coverage-audit-code-review.md
The project has 21 test files against 176 non-generated source files (~12% file coverage). Coverage
is severely skewed toward model and utility tests. The entire presentation layer (28 views, 44+
widgets) has zero meaningful widget coverage. The core BLoC (ComparisonBloc) is completely
untested. Two test bodies are permanently disabled via skip: rather than fixed. There are no
integration tests of any kind.
-
ComparisonBlocis completely untested — the central orchestrator of the app's primary feature (file comparison) has no tests. Any regression silently breaks the core workflow. -
ComparisonResultDialogtest is empty and permanently skipped- File:
test/presentation/views/comparison_result_dialog_test.dart - Single
testWidgetscall with empty body andskip: true. The dialog is the primary result surface users see after every comparison.
- File:
-
ComparisonSettingsCardgroup is permanently skipped- File:
test/presentation/views/settings_view_test.dart:345 - 5 widget tests skipped with comment
'Widget layout assertions in tests are unstable.' - This suppresses the failure rather than fixing the root cause.
- File:
-
10 of 13 parsers have zero test coverage
- Untested: ARB (Flutter's native format), CSV, DOCX, properties, RESX, plain text, XML,
file_parser_factory.dart, XLIFF (individual units), TMX (individual units). - Parser failures are silent data loss for users.
- Untested: ARB (Flutter's native format), CSV, DOCX, properties, RESX, plain text, XML,
-
6 of 8 BLoCs are completely untested
- Untested:
ComparisonBloc,DirectoryComparisonBloc,TranslationBloc,GitBloc,HistoryBloc,FileWatcherBloc,ThemeBloc,ProgressBloc.
- Untested:
-
Zero integration tests — no end-to-end workflow is verified: file comparison, AI translation, git diff, import/export, directory comparison.
-
widget_test.dartis a dead placeholder —expect(true, isTrue)passes trivially.
- No shared mock or test utility layer — 7 files declare their own
MockSettingsBloc,MockSecureStorageService, etc. Interface changes require updating every file individually. settings_bloc_test.dartusesskip: 2withFuture.delayed(50ms)to work around event ordering — a timing-dependent anti-pattern that breaks on slow CI machines.- AI translation tests (Gemini) cover only edge cases (cache hit, missing key) — no happy path.
DeepL,OpenAI, andAdaptiveAIservices have zero tests. - 4 repositories untested:
SettingsRepository,HistoryRepository,ReviewStatusRepository,WarningSuppressionRepository. - 17+ core services untested including
BackupService,FileDiscoveryService,ProblemDetector,QualityReportExporter,FileCacheService,ComparisonEngine(full surface). comparison_engine_test.dartactually testsDiffCalculator, notComparisonEngine. Misleading name could cause false confidence.
- Un-skip
ComparisonResultDialog— write a minimal smoke test that verifies the dialog renders without crashing. - Fix and un-skip
ComparisonSettingsCardgroup — investigate the instability (pumpAndSettlemissing? surface size? locale init?) rather than suppressing 5 tests. - Write
comparison_bloc_test.dartusingbloc_test— at minimum:LoadSourceFile,LoadTargetFile,RunComparison, and error state. - Add parser tests for ARB, CSV, properties, and RESX — the four most-used formats users will bring to this tool.
- Create
test/helpers/mocks.dart— shared mock classes for all 7 test files that re-declare their own. - Fix the timing-dependent
settings_bloc_test.dart— useblocTest'swaitparameter instead ofFuture.delayed+skip: 2. - Write
translation_bloc_test.dartandgit_bloc_test.dartwith mocked services. - Add smoke-level widget tests for
home_view.dart,file_comparison_view.dart, andfirst_run_wizard_view.dart— minimal render-without-exception checks.
ComparisonBloc— core feature, completely unguarded- Untested parsers (ARB, CSV, properties, RESX, XML) — format failures = silent data loss
- Un-skip
ComparisonResultDialog— primary result surface, zero verified rendering - Un-skip
ComparisonSettingsCardgroup — 5 existing tests disabled TranslationBloc— AI translation is a must-have featureDirectoryComparisonBloc— multi-file comparison core workflowGitBloc— git integration must-have featureSettingsRepository— persistence failure would corrupt user preferencesFileDiscoveryService— detection failures break project loadingBackupService— backup failures during edits risk data loss- Shared mock library — reduces maintenance for all future tests
- Integration test: core comparison workflow (end-to-end)
- Integration test: AI translation workflow (end-to-end)
- Home view smoke test — entry point rendering check
GitServiceunit tests — service layer currently untested
Full report: dev/active/accessibility-audit/accessibility-audit-code-review.md
The app has a very thin accessibility layer. Only 1 Semantics widget exists across the entire
lib/presentation/ codebase; 16 of 43 IconButton instances have no tooltip; the default purple
accent color fails WCAG AA contrast on dark backgrounds; and the default diff colors (red/green) are
indistinguishable for approximately 8% of male users with red-green colorblindness. The testing
suite systematically disables semantics checks, eliminating automated detection of these gaps.
-
Single
Semanticswidget in the entire codebase- File:
lib/presentation/views/file_comparison_view.dart:2801(search toggle only) - Every other custom interactive widget —
InkWelltap targets inpluto_grid_adapter.dart, the theme toggle inhome_view.dart, all_AnimatedPreviewEntrycontainers — has zero semantic annotation. - Screen readers (NVDA, JAWS, VoiceOver) cannot identify or describe the majority of interactive controls.
- Fails: WCAG 2.1 SC 4.1.2 (Name, Role, Value) — Level A
- File:
-
_ActionIconButtonusesInkWellinstead ofIconButton— no semantic role- File:
lib/presentation/views/advanced_diff/widgets/table/pluto_grid_adapter.dart:563–593 - The custom button (used for Mark-as-Reviewed and Revert throughout the diff table) wraps an
IconinInkWell + Padding. Has a tooltip string, but screen readers announce it as an unlabeled container, not a button. Core diff table actions are invisible to assistive tech.
- File:
-
16 of 43
IconButtoninstances missing tooltip- Most critical: all 4 pagination buttons in
file_comparison_view.dart:1707–1752and all 4 indiff_data_table.dart:186–219— used on every comparison session. - Also missing: close buttons in two dialogs, clear-search suffix icons, settings back button,
delete button in
project_glossary_card.dart:80, and AI params toggle.
- Most critical: all 4 pagination buttons in
-
Default accent color fails WCAG AA contrast on dark theme
- Default
0xFF7B61FF(purple) achieves ~2.96:1 against dark background0xFF0F0F14. - Fails: WCAG 2.1 SC 1.4.3 (Contrast Minimum) — Level AA (requires 4.5:1 for normal text, 3:1 for UI components). Passes on light theme (~6.4:1).
- Affects: active navigation rail items, focused input borders, primary buttons, chip selection.
- Default
-
Default diff colors fail colorblind users
- Default: added =
0xFF22C55E(green), removed =0xFFEF4444(red). - For deuteranopia/protanopia (~8% of males), green and red appear as the same olive/brown color.
- Diff table rows use background color alone to communicate status — no shape, pattern, or text indicator supplements color.
- A Colorblind-Friendly preset (
0xFF0077BB/0xFFEE7733/0xFF009988) exists but is opt-in. - Fails: WCAG 2.1 SC 1.4.1 (Use of Color) — Level A (default configuration)
- Default: added =
semanticsEnabled: falsein all 8 widget tests- File:
test/presentation/views/settings_view_test.dart:156,181,209,240,288,343,370,395 - Eliminates all automated accessibility regression detection for the settings UI.
- File:
PopupMenuButtonhas intentionally blank tooltip''atpluto_grid_adapter.dart:457— suppresses Flutter's default without replacing it; screen readers announce nothing.- Theme toggle
InkWellinhome_view.dart:300–334hasTooltipbut noSemantics— not identified as a button by screen readers. - Color swatch containers in
appearance_settings_card.dart:144–155,460–470have no semantic label — a screen reader user cannot determine what color is shown. - AI temperature
Slideratai_services_settings_card.dart:~291has nolabel:parameter — screen reader announces a percentage of the 0–2 range, not the actual formatted value. - Navigation rail hover region (
home_view.dart:443–473) updates mouse state but not keyboard focus state — minor inconsistency since Material 3 NavigationRail provides its own focus ring.
| Criterion | Level | Status |
|---|---|---|
| 1.4.1 Use of Color | A | FAILING (default diff colors, red/green indistinguishable) |
| 1.4.3 Contrast Minimum | AA | FAILING (dark theme, default purple accent ≈ 2.96:1) |
| 4.1.2 Name, Role, Value | A | FAILING (~35+ interactive elements with no accessible name/role) |
| 2.4.7 Focus Visible | AA | PARTIAL (Material 3 focus rings present; custom InkWell buttons may not) |
| 2.1.1 Keyboard | A | PARTIAL (standard buttons operable; InkWell-based custom buttons may not activate on Enter/Space) |
| 1.3.1 Info and Relationships | A | AT RISK (color swatch containers convey meaning through color alone) |
Level A (highest priority — basic accessibility)
- Add
Semantics(button: true, label: '...')to allInkWellwidgets used as buttons — start with the theme toggle inhome_view.dartand convert_ActionIconButtoninpluto_grid_adapter.dartto a properIconButton. - Add
tooltip:to all 16 icon-onlyIconButtoninstances — pagination buttons first. - Add visible non-color status indicators to diff rows (e.g., "+"/"-"/"~" prefix, or icon) so colorblind users can distinguish Added, Removed, and Modified without relying on color alone.
Level AA (standard accessibility)
- Evaluate replacing default accent
0xFF7B61FFwith a higher-contrast alternative (e.g.,0xFF9B8FFFat HSL L=70% achieves ~4.6:1 on dark background). Document inTECHNICAL.md. - Replace blank
tooltip: ''atpluto_grid_adapter.dart:457with a descriptive label. - Add
label:to the AI temperatureSlideratai_services_settings_card.dart:~291. - Add
Semantics(label: 'Current accent color: ...')to color swatch containers.
Recommended
- Remove
semanticsEnabled: falsefrom all 8 tests insettings_view_test.dartand fix the underlying instability. - Surface the Colorblind-Friendly diff color preset during first-run setup
(
first_run_wizard_view.dart) with a brief explanation — highest single-change impact for colorblind users. - Verify logical tab order in the advanced diff toolbar and confirm no focus traps inside
PlutoGrid.
All detailed auditor reports are saved under dev/active/:
dev/active/flutter-arch-audit/flutter-arch-audit-code-review.mddev/active/bloc-audit/bloc-audit-code-review.mddev/active/test-coverage-audit/test-coverage-audit-code-review.mddev/active/accessibility-audit/accessibility-audit-code-review.md