[ Tool ] Stop generating widget preview scaffold under $TMP (#186476)

Previously, the `widget_preview_scaffold` project was generated under
the system temporary directory (`$TMP`) to work around issues with
`flutter clean`. However, generating the scaffold in `$TMP` meant that
the project was recreated on every run or could be lost, leading to
unnecessary regeneration overhead and tracking issues.

Move the generated `widget_preview_scaffold` project from `$TMP` to a
`.widget_preview` directory located directly under the project root. To
support this change, `WidgetPreviewGitignoreMigration` is introduced to
automatically add `.widget_preview/` to the host project's `.gitignore`.
In addition, `kProjectRootPath` is passed via DTD connection info to
ensure `WidgetInspectorService` correctly adds the project root to pub
root directories. Finally, file watchers (`LspPreviewDetector` and
`PreviewDetector`) are updated to ignore the new `.widget_preview`
directory.

Fixes https://github.com/flutter/flutter/issues/179036
diff --git a/dev/integration_tests/widget_preview_scaffold/lib/src/dtd/dtd_connection_info.dart b/dev/integration_tests/widget_preview_scaffold/lib/src/dtd/dtd_connection_info.dart
index def881b..1d8cacc 100644
--- a/dev/integration_tests/widget_preview_scaffold/lib/src/dtd/dtd_connection_info.dart
+++ b/dev/integration_tests/widget_preview_scaffold/lib/src/dtd/dtd_connection_info.dart
@@ -5,3 +5,4 @@
 const String kWidgetPreviewDtdUri = '';
 const String kWidgetPreviewService = 'widget-preview';
 const String kWidgetPreviewScaffoldStream = 'WidgetPreviewScaffold';
+const String kProjectRootPath = r'';
diff --git a/dev/integration_tests/widget_preview_scaffold/lib/src/widget_preview_inspector_service.dart b/dev/integration_tests/widget_preview_scaffold/lib/src/widget_preview_inspector_service.dart
index d91e39e..224fb37 100644
--- a/dev/integration_tests/widget_preview_scaffold/lib/src/widget_preview_inspector_service.dart
+++ b/dev/integration_tests/widget_preview_scaffold/lib/src/widget_preview_inspector_service.dart
@@ -3,6 +3,7 @@
 // found in the LICENSE file.
 
 import 'package:flutter/widgets.dart';
+import 'package:widget_preview_scaffold/src/dtd/dtd_connection_info.dart';
 import 'package:widget_preview_scaffold/src/dtd/dtd_services.dart';
 import 'package:widget_preview_scaffold/src/dtd/editor_service.dart';
 import 'package:widget_preview_scaffold/src/widget_preview_rendering.dart';
@@ -16,6 +17,7 @@
 class WidgetPreviewScaffoldInspectorService with WidgetInspectorService {
   WidgetPreviewScaffoldInspectorService({required this.dtdServices}) {
     WidgetInspectorService.instance = this;
+    addPubRootDirectories(<String>[kProjectRootPath]);
   }
 
   /// The DTD services instance used to communicate with the tool.
diff --git a/packages/flutter_tools/lib/src/commands/widget_preview.dart b/packages/flutter_tools/lib/src/commands/widget_preview.dart
index ccc79c6..1139235 100644
--- a/packages/flutter_tools/lib/src/commands/widget_preview.dart
+++ b/packages/flutter_tools/lib/src/commands/widget_preview.dart
@@ -28,6 +28,7 @@
 import '../features.dart';
 import '../globals.dart' as globals;
 import '../isolated/resident_web_runner.dart';
+import '../migrations/widget_preview_gitignore_migration.dart';
 import '../project.dart';
 import '../resident_runner.dart';
 import '../runner/flutter_command.dart';
@@ -322,6 +323,8 @@
     previewAnalytics.initializeLaunchStopwatch();
     logger.sendInitializingEvent();
 
+    await WidgetPreviewGitignoreMigration(rootProject, logger).migrate();
+
     final String? customPreviewScaffoldOutput = stringArg(kWidgetPreviewScaffoldOutputDir);
     widgetPreviewScaffold = customPreviewScaffoldOutput != null
         ? fs.directory(customPreviewScaffoldOutput)
@@ -414,6 +417,7 @@
         dtdUri: _dtdService.dtdUri!,
         widgetPreviewServiceName: _dtdService.widgetPreviewService,
         widgetPreviewScaffoldStreamName: _dtdService.widgetPreviewScaffoldStream,
+        projectRootPath: rootProject.directory.absolute.path,
       );
 
       final FlutterWidgetPreviews originalPreviews = await _dtdService.getFlutterWidgetPreviews();
diff --git a/packages/flutter_tools/lib/src/migrations/widget_preview_gitignore_migration.dart b/packages/flutter_tools/lib/src/migrations/widget_preview_gitignore_migration.dart
new file mode 100644
index 0000000..ce66432
--- /dev/null
+++ b/packages/flutter_tools/lib/src/migrations/widget_preview_gitignore_migration.dart
@@ -0,0 +1,42 @@
+// Copyright 2014 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import '../base/file_system.dart';
+import '../base/project_migrator.dart';
+import '../project.dart';
+
+/// Adds `.widget_preview/` to the .gitignore file.
+class WidgetPreviewGitignoreMigration extends ProjectMigrator {
+  WidgetPreviewGitignoreMigration(FlutterProject project, super.logger)
+    : _gitignoreFile = project.gitignoreFile;
+
+  final File _gitignoreFile;
+
+  @override
+  Future<void> migrate() async {
+    if (!_gitignoreFile.existsSync()) {
+      logger.printTrace('.gitignore file not found, skipping widget preview .gitignore migration.');
+      return;
+    }
+
+    final String originalContent = _gitignoreFile.readAsStringSync();
+
+    // Skip if .gitignore is already migrated.
+    if (originalContent.contains('.widget_preview/')) {
+      return;
+    }
+
+    logger.printTrace('.gitignore does not ignore .widget_preview/ directory, updating.');
+
+    final newContent = StringBuffer(originalContent);
+    if (!originalContent.endsWith('\n')) {
+      newContent.writeln();
+    }
+    newContent
+      ..writeln('# Widget Preview related')
+      ..writeln('.widget_preview/');
+
+    _gitignoreFile.writeAsStringSync(newContent.toString());
+  }
+}
diff --git a/packages/flutter_tools/lib/src/project.dart b/packages/flutter_tools/lib/src/project.dart
index bc02734..ef92e6a 100644
--- a/packages/flutter_tools/lib/src/project.dart
+++ b/packages/flutter_tools/lib/src/project.dart
@@ -249,10 +249,7 @@
 
   /// The location of the generated scaffolding project for hosting widget
   /// previews from this project.
-  // TODO(bkonyi): don't create this project in $TMP.
-  // See https://github.com/flutter/flutter/issues/179036
-  late final Directory widgetPreviewScaffold = directory.fileSystem.systemTempDirectory
-      .createTempSync('widget_preview_scaffold');
+  late final Directory widgetPreviewScaffold = directory.childDirectory('.widget_preview');
 
   /// The directory containing the generated code for this project.
   Directory get generated => directory.absolute
diff --git a/packages/flutter_tools/lib/src/widget_preview/lsp_preview_detector.dart b/packages/flutter_tools/lib/src/widget_preview/lsp_preview_detector.dart
index 4b75c14..ab0d7f6 100644
--- a/packages/flutter_tools/lib/src/widget_preview/lsp_preview_detector.dart
+++ b/packages/flutter_tools/lib/src/widget_preview/lsp_preview_detector.dart
@@ -168,7 +168,15 @@
     // Only process one FileSystemEntity at a time so we don't invalidate an AnalysisSession that's
     // in use when we call context.changeFile(...).
     await mutex.runGuarded(() async {
-      await _fileAddedOrUpdated(filePath: event.path);
+      final String eventPath = event.path;
+      // Ignore any files under .dart_tool, .widget_preview, or ephemeral directories created by
+      // the tool (e.g., build/, plugin directories, etc.).
+      if (eventPath.doesContainDartTool ||
+          eventPath.doesContainWidgetPreview ||
+          project.ephemeralDirectories.any((dir) => eventPath.contains(dir.path))) {
+        return;
+      }
+      await _fileAddedOrUpdated(filePath: eventPath);
     });
   }
 
diff --git a/packages/flutter_tools/lib/src/widget_preview/preview_code_generator.dart b/packages/flutter_tools/lib/src/widget_preview/preview_code_generator.dart
index 23f7308..d37eec2 100644
--- a/packages/flutter_tools/lib/src/widget_preview/preview_code_generator.dart
+++ b/packages/flutter_tools/lib/src/widget_preview/preview_code_generator.dart
@@ -60,6 +60,7 @@
     required Uri dtdUri,
     required String widgetPreviewServiceName,
     required String widgetPreviewScaffoldStreamName,
+    required String projectRootPath,
   }) {
     final emitter = cb.DartEmitter.scoped(useNullSafetySyntax: true);
     final lib = cb.Library(
@@ -87,6 +88,13 @@
               ..type = cb.refer('String')
               ..assignment = cb.literalString(widgetPreviewScaffoldStreamName).code;
           }),
+          cb.Field((b) {
+            b
+              ..name = 'kProjectRootPath'
+              ..modifier = cb.FieldModifier.constant
+              ..type = cb.refer('String')
+              ..assignment = cb.literalString(projectRootPath, raw: true).code;
+          }),
         ]),
     );
     final File generatedDtdConnectionInfoFile = fs.file(
diff --git a/packages/flutter_tools/lib/src/widget_preview/preview_detector.dart b/packages/flutter_tools/lib/src/widget_preview/preview_detector.dart
index eb0ff03..72d6df0 100644
--- a/packages/flutter_tools/lib/src/widget_preview/preview_detector.dart
+++ b/packages/flutter_tools/lib/src/widget_preview/preview_detector.dart
@@ -154,9 +154,10 @@
         onPackageConfigChangeDetected?.call(event.path);
         return;
       }
-      // Ignore any files under .dart_tool or ephemeral directories created by
+      // Ignore any files under .dart_tool, .widget_preview, or ephemeral directories created by
       // the tool (e.g., build/, plugin directories, etc.).
       if (eventPath.doesContainDartTool ||
+          eventPath.doesContainWidgetPreview ||
           project.ephemeralDirectories.any((dir) => eventPath.contains(dir.path))) {
         return;
       }
diff --git a/packages/flutter_tools/lib/src/widget_preview/utils.dart b/packages/flutter_tools/lib/src/widget_preview/utils.dart
index 60fe339..a1374e8 100644
--- a/packages/flutter_tools/lib/src/widget_preview/utils.dart
+++ b/packages/flutter_tools/lib/src/widget_preview/utils.dart
@@ -61,9 +61,12 @@
 
 /// Convenience getters for examining [String] paths.
 extension StringExtension on String {
+  static final RegExp _pathSeparator = RegExp(r'[/\\]');
+
   bool get isDartFile => endsWith('.dart');
   bool get isPubspec => endsWith('pubspec.yaml');
   bool get doesContainDartTool => contains('.dart_tool');
+  bool get doesContainWidgetPreview => split(_pathSeparator).contains('.widget_preview');
 }
 
 extension LibraryElementExtension on LibraryElement {
diff --git a/packages/flutter_tools/templates/app/.gitignore.tmpl b/packages/flutter_tools/templates/app/.gitignore.tmpl
index 3820a95..79f7eca 100644
--- a/packages/flutter_tools/templates/app/.gitignore.tmpl
+++ b/packages/flutter_tools/templates/app/.gitignore.tmpl
@@ -43,3 +43,6 @@
 /android/app/debug
 /android/app/profile
 /android/app/release
+
+# Widget Preview related
+.widget_preview/
diff --git a/packages/flutter_tools/templates/widget_preview_scaffold/lib/src/dtd/dtd_connection_info.dart.tmpl b/packages/flutter_tools/templates/widget_preview_scaffold/lib/src/dtd/dtd_connection_info.dart.tmpl
index def881b..1d8cacc 100644
--- a/packages/flutter_tools/templates/widget_preview_scaffold/lib/src/dtd/dtd_connection_info.dart.tmpl
+++ b/packages/flutter_tools/templates/widget_preview_scaffold/lib/src/dtd/dtd_connection_info.dart.tmpl
@@ -5,3 +5,4 @@
 const String kWidgetPreviewDtdUri = '';
 const String kWidgetPreviewService = 'widget-preview';
 const String kWidgetPreviewScaffoldStream = 'WidgetPreviewScaffold';
+const String kProjectRootPath = r'';
diff --git a/packages/flutter_tools/templates/widget_preview_scaffold/lib/src/widget_preview_inspector_service.dart.tmpl b/packages/flutter_tools/templates/widget_preview_scaffold/lib/src/widget_preview_inspector_service.dart.tmpl
index d91e39e..224fb37 100644
--- a/packages/flutter_tools/templates/widget_preview_scaffold/lib/src/widget_preview_inspector_service.dart.tmpl
+++ b/packages/flutter_tools/templates/widget_preview_scaffold/lib/src/widget_preview_inspector_service.dart.tmpl
@@ -3,6 +3,7 @@
 // found in the LICENSE file.
 
 import 'package:flutter/widgets.dart';
+import 'package:widget_preview_scaffold/src/dtd/dtd_connection_info.dart';
 import 'package:widget_preview_scaffold/src/dtd/dtd_services.dart';
 import 'package:widget_preview_scaffold/src/dtd/editor_service.dart';
 import 'package:widget_preview_scaffold/src/widget_preview_rendering.dart';
@@ -16,6 +17,7 @@
 class WidgetPreviewScaffoldInspectorService with WidgetInspectorService {
   WidgetPreviewScaffoldInspectorService({required this.dtdServices}) {
     WidgetInspectorService.instance = this;
+    addPubRootDirectories(<String>[kProjectRootPath]);
   }
 
   /// The DTD services instance used to communicate with the tool.
diff --git a/packages/flutter_tools/test/commands.shard/hermetic/widget_preview/preview_code_generator_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/widget_preview/preview_code_generator_test.dart
index d8f5fc4..b5b3f64 100644
--- a/packages/flutter_tools/test/commands.shard/hermetic/widget_preview/preview_code_generator_test.dart
+++ b/packages/flutter_tools/test/commands.shard/hermetic/widget_preview/preview_code_generator_test.dart
@@ -463,6 +463,7 @@
           dtdUri: dtdUri,
           widgetPreviewServiceName: 'widget-preview-service',
           widgetPreviewScaffoldStreamName: 'widget-preview-stream',
+          projectRootPath: project.directory.absolute.path,
         );
 
         final expectedDtdConnectionInfo =
@@ -472,6 +473,8 @@
 const String kWidgetPreviewDtdUri = '$dtdUri';
 const String kWidgetPreviewService = 'widget-preview-service';
 const String kWidgetPreviewScaffoldStream = 'widget-preview-stream';
+const String kProjectRootPath =
+    r'${project.directory.absolute.path}';
 ''';
         expect(generatedDtdConnectionInfoFile.readAsStringSync(), expectedDtdConnectionInfo);
       },
diff --git a/packages/flutter_tools/test/general.shard/migrations/widget_preview_gitignore_migration_test.dart b/packages/flutter_tools/test/general.shard/migrations/widget_preview_gitignore_migration_test.dart
new file mode 100644
index 0000000..7fd065c
--- /dev/null
+++ b/packages/flutter_tools/test/general.shard/migrations/widget_preview_gitignore_migration_test.dart
@@ -0,0 +1,95 @@
+// Copyright 2014 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'package:file/file.dart';
+import 'package:file/memory.dart';
+import 'package:flutter_tools/src/base/logger.dart';
+import 'package:flutter_tools/src/base/terminal.dart';
+import 'package:flutter_tools/src/migrations/widget_preview_gitignore_migration.dart';
+import 'package:flutter_tools/src/project.dart';
+import 'package:test/fake.dart';
+
+import '../../src/common.dart';
+
+void main() {
+  group('Widget Preview .gitignore migration', () {
+    late MemoryFileSystem memoryFileSystem;
+    late BufferLogger testLogger;
+    late FakeFlutterProject mockProject;
+    late File gitignoreFile;
+
+    setUp(() {
+      memoryFileSystem = MemoryFileSystem.test();
+      gitignoreFile = memoryFileSystem.file('.gitignore');
+
+      testLogger = BufferLogger(
+        terminal: Terminal.test(),
+        outputPreferences: OutputPreferences.test(),
+      );
+
+      mockProject = FakeFlutterProject(fileSystem: memoryFileSystem);
+    });
+
+    testWithoutContext('skipped if .gitignore file is missing', () async {
+      final migration = WidgetPreviewGitignoreMigration(mockProject, testLogger);
+      await migration.migrate();
+      expect(gitignoreFile.existsSync(), isFalse);
+
+      expect(
+        testLogger.traceText,
+        contains('.gitignore file not found, skipping widget preview .gitignore migration.'),
+      );
+      expect(testLogger.warningText, isEmpty);
+    });
+
+    testWithoutContext('skipped if already migrated', () async {
+      const gitignoreFileContents = '''
+.DS_Store
+.widget_preview/
+''';
+
+      gitignoreFile.writeAsStringSync(gitignoreFileContents);
+
+      final DateTime updatedAt = gitignoreFile.lastModifiedSync();
+
+      final migration = WidgetPreviewGitignoreMigration(mockProject, testLogger);
+      await migration.migrate();
+
+      expect(gitignoreFile.lastModifiedSync(), updatedAt);
+      expect(gitignoreFile.readAsStringSync(), gitignoreFileContents);
+      expect(testLogger.warningText, isEmpty);
+    });
+
+    testWithoutContext('migrates project to ignore .widget_preview directory', () async {
+      gitignoreFile.writeAsStringSync(
+        '.DS_Store\n'
+        '.atom/\n',
+      );
+
+      final migration = WidgetPreviewGitignoreMigration(mockProject, testLogger);
+      await migration.migrate();
+
+      expect(
+        gitignoreFile.readAsStringSync(),
+        '.DS_Store\n'
+        '.atom/\n'
+        '# Widget Preview related\n'
+        '.widget_preview/\n',
+      );
+
+      expect(
+        testLogger.traceText,
+        contains('.gitignore does not ignore .widget_preview/ directory, updating.'),
+      );
+    });
+  });
+}
+
+class FakeFlutterProject extends Fake implements FlutterProject {
+  FakeFlutterProject({required MemoryFileSystem fileSystem})
+    : gitignoreFile = fileSystem.file('.gitignore');
+
+  @override
+  File gitignoreFile;
+}
diff --git a/packages/flutter_tools/test/integration.shard/test_data/basic_project.dart b/packages/flutter_tools/test/integration.shard/test_data/basic_project.dart
index fd0afb9..8bb8583 100644
--- a/packages/flutter_tools/test/integration.shard/test_data/basic_project.dart
+++ b/packages/flutter_tools/test/integration.shard/test_data/basic_project.dart
@@ -5,9 +5,12 @@
 import 'project.dart';
 
 class BasicProject extends Project {
+  BasicProject({super.name});
+
   @override
-  final pubspec = '''
-  name: test
+  String get pubspec =>
+      '''
+  name: $name
   environment:
     sdk: ^3.7.0-0
 
diff --git a/packages/flutter_tools/test/integration.shard/test_data/project.dart b/packages/flutter_tools/test/integration.shard/test_data/project.dart
index 44d2743..9935586 100644
--- a/packages/flutter_tools/test/integration.shard/test_data/project.dart
+++ b/packages/flutter_tools/test/integration.shard/test_data/project.dart
@@ -27,7 +27,9 @@
   /// Creates a flutter Project for testing.
   ///
   /// If passed, `indexHtml` is used as the contents of the web/index.html file.
-  Project({this.indexHtml = _kDefaultHtml});
+  Project({this.indexHtml = _kDefaultHtml, String? name}) : name = name ?? 'test';
+
+  final String name;
 
   late Directory dir;
 
@@ -37,7 +39,7 @@
   String get generatedFile => '';
   DeferredComponentsConfig? get deferredComponents => null;
 
-  Uri get mainDart => Uri.parse('package:test/main.dart');
+  Uri get mainDart => Uri.parse('package:$name/main.dart');
 
   /// The contents for the index.html file of this `Project`.
   ///
@@ -67,7 +69,7 @@
     writeFile(fileSystem.path.join(dir.path, 'web', 'index.html'), indexHtml);
     writeFile(fileSystem.path.join(dir.path, 'web', 'flutter.js'), '');
     writeFile(fileSystem.path.join(dir.path, 'web', 'flutter_service_worker.js'), '');
-    writePackageConfigFiles(directory: dir, mainLibName: 'test');
+    writePackageConfigFiles(directory: dir, mainLibName: name);
     await getPackages(dir.path);
   }
 
diff --git a/packages/flutter_tools/test/integration.shard/widget_preview_detection_test.dart b/packages/flutter_tools/test/integration.shard/widget_preview_detection_test.dart
index 0f3f57f..cef80d3 100644
--- a/packages/flutter_tools/test/integration.shard/widget_preview_detection_test.dart
+++ b/packages/flutter_tools/test/integration.shard/widget_preview_detection_test.dart
@@ -21,11 +21,14 @@
   late Directory tempDir;
   Logger? logger;
   DtdLauncher? dtdLauncher;
-  final project = BasicProject();
+  late BasicProject project;
+  var projectCounter = 0;
 
   setUp(() async {
     logger = BufferLogger.test();
     tempDir = createResolvedTempDirectorySync('widget_preview_detection_test.');
+    projectCounter++;
+    project = BasicProject(name: 'test_detection_$projectCounter');
     await project.setUpIn(tempDir);
   });
 
@@ -74,10 +77,7 @@
       );
       await reloadSub.cancel();
 
-      final DTDResponse result = await dtdConnection.call(
-        'Lsp',
-        'dart/workspace/getFlutterWidgetPreviews',
-      );
+      final DTDResponse result = await getPreviews(dtdConnection);
       final FlutterWidgetPreviews previews = FlutterWidgetPreviews.fromJson(
         result.result['result']! as Map<String, Object?>,
       );
@@ -125,10 +125,7 @@
       );
       await initReloadSub.cancel();
 
-      DTDResponse result = await dtdConnection.call(
-        'Lsp',
-        'dart/workspace/getFlutterWidgetPreviews',
-      );
+      DTDResponse result = await getPreviews(dtdConnection);
       FlutterWidgetPreviews previews = FlutterWidgetPreviews.fromJson(
         result.result['result']! as Map<String, Object?>,
       );
@@ -151,7 +148,7 @@
       );
       await deleteReloadSub.cancel();
 
-      result = await dtdConnection.call('Lsp', 'dart/workspace/getFlutterWidgetPreviews');
+      result = await getPreviews(dtdConnection);
       previews = FlutterWidgetPreviews.fromJson(result.result['result']! as Map<String, Object?>);
       if (previews.previews.isNotEmpty) {
         throw StateError('Preview was still detected after deletion!');
@@ -197,10 +194,7 @@
       );
       await initReloadSub.cancel();
 
-      DTDResponse result = await dtdConnection.call(
-        'Lsp',
-        'dart/workspace/getFlutterWidgetPreviews',
-      );
+      DTDResponse result = await getPreviews(dtdConnection);
       FlutterWidgetPreviews previews = FlutterWidgetPreviews.fromJson(
         result.result['result']! as Map<String, Object?>,
       );
@@ -229,7 +223,7 @@
       );
       await modifyReloadSub.cancel();
 
-      result = await dtdConnection.call('Lsp', 'dart/workspace/getFlutterWidgetPreviews');
+      result = await getPreviews(dtdConnection);
       previews = FlutterWidgetPreviews.fromJson(result.result['result']! as Map<String, Object?>);
       if (previews.previews.isEmpty) {
         throw StateError('Preview was lost after modification!');
@@ -292,10 +286,7 @@
       );
       await reloadSub.cancel();
 
-      final DTDResponse result = await dtdConnection.call(
-        'Lsp',
-        'dart/workspace/getFlutterWidgetPreviews',
-      );
+      final DTDResponse result = await getPreviews(dtdConnection);
       final FlutterWidgetPreviews previews = FlutterWidgetPreviews.fromJson(
         result.result['result']! as Map<String, Object?>,
       );
diff --git a/packages/flutter_tools/test/integration.shard/widget_preview_pubspec_test.dart b/packages/flutter_tools/test/integration.shard/widget_preview_pubspec_test.dart
index 2717cf9..50b51ab 100644
--- a/packages/flutter_tools/test/integration.shard/widget_preview_pubspec_test.dart
+++ b/packages/flutter_tools/test/integration.shard/widget_preview_pubspec_test.dart
@@ -14,10 +14,13 @@
 
 void main() {
   late Directory tempDir;
-  final project = BasicProject();
+  late BasicProject project;
+  var projectCounter = 0;
 
   setUp(() async {
     tempDir = createResolvedTempDirectorySync('widget_preview_pubspec_test.');
+    projectCounter++;
+    project = BasicProject(name: 'test_pubspec_$projectCounter');
     await project.setUpIn(tempDir);
   });
 
diff --git a/packages/flutter_tools/test/integration.shard/widget_preview_smoke_test.dart b/packages/flutter_tools/test/integration.shard/widget_preview_smoke_test.dart
index ea16c7f..76af3ea 100644
--- a/packages/flutter_tools/test/integration.shard/widget_preview_smoke_test.dart
+++ b/packages/flutter_tools/test/integration.shard/widget_preview_smoke_test.dart
@@ -26,12 +26,15 @@
   Logger? logger;
   DtdLauncher? dtdLauncher;
   DevtoolsServerLauncher? devtoolsLauncher;
-  final project = BasicProject();
+  late BasicProject project;
+  var projectCounter = 0;
   const ProcessManager processManager = LocalProcessManager();
 
   setUp(() async {
     logger = BufferLogger.test();
     tempDir = createResolvedTempDirectorySync('widget_preview_smoke_test.');
+    projectCounter++;
+    project = BasicProject(name: 'test_smoke_$projectCounter');
     await project.setUpIn(tempDir);
   });
 
@@ -56,53 +59,42 @@
       );
     });
 
-    testWithoutContext(
-      'runs flutter pub get in widget_preview_scaffold if '
-      "widget_preview_scaffold/.dart_tool doesn't exist",
-      () async {
-        // Regression test for https://github.com/flutter/flutter/issues/178660
-        // Generate the widget preview scaffold, but don't bother launching it.
-        processManager.runSync(<String>[
-          flutterBin,
-          'widget-preview',
-          'start',
-          '--no-${WidgetPreviewStartCommand.kLaunchPreviewer}',
-        ], workingDirectory: tempDir.path);
+    testWithoutContext('runs flutter pub get in widget_preview_scaffold if '
+        "widget_preview_scaffold/.dart_tool doesn't exist", () async {
+      // Regression test for https://github.com/flutter/flutter/issues/178660
+      // Generate the widget preview scaffold, but don't bother launching it.
+      processManager.runSync(<String>[
+        flutterBin,
+        'widget-preview',
+        'start',
+        '--no-${WidgetPreviewStartCommand.kLaunchPreviewer}',
+      ], workingDirectory: tempDir.path);
 
-        // Ensure widget_preview_scaffold/.dart_tool/package_config.json exists.
-        final Directory widgetPreviewScaffoldDartTool = tempDir
-            .childDirectory('.dart_tool')
-            .childDirectory('widget_preview_scaffold')
-            .childDirectory('.dart_tool');
-        expect(widgetPreviewScaffoldDartTool, exists);
-        expect(widgetPreviewScaffoldDartTool.childFile('package_config.json'), exists);
+      // Ensure .widget_preview/.dart_tool/package_config.json exists.
+      final Directory widgetPreviewScaffoldDartTool = tempDir
+          .childDirectory('.widget_preview')
+          .childDirectory('.dart_tool');
+      expect(widgetPreviewScaffoldDartTool, exists);
+      expect(widgetPreviewScaffoldDartTool.childFile('package_config.json'), exists);
 
-        // Delete widget_preview_scaffold/.dart_tool/. This simulates an interrupted
-        // flutter widget-preview start where 'flutter pub get' wasn't run after
-        // the widget_preview_scaffold project was created.
-        widgetPreviewScaffoldDartTool.deleteSync(recursive: true);
+      // Delete .widget_preview/.dart_tool/. This simulates an interrupted
+      // flutter widget-preview start where 'flutter pub get' wasn't run after
+      // the widget_preview_scaffold project was created.
+      widgetPreviewScaffoldDartTool.deleteSync(recursive: true);
 
-        // Ensure we don't crash due to the package_config.json lookup pointing to
-        // the parent project's package_config.json due to
-        // widget_preview_scaffold/.dart_tool/package_config.json not existing.
-        await runWidgetPreview(tempDir: tempDir, expectedMessages: subsequentLaunchMessagesWeb);
-      },
-      // Project is always regenerated.
-      skip: true, // See https://github.com/flutter/flutter/issues/179036.
-    );
+      // Ensure we don't crash due to the package_config.json lookup pointing to
+      // the parent project's package_config.json due to
+      // .widget_preview/.dart_tool/package_config.json not existing.
+      await runWidgetPreview(tempDir: tempDir, expectedMessages: subsequentLaunchMessagesWeb);
+    });
 
-    testWithoutContext(
-      'does not recreate project on subsequent runs',
-      () async {
-        // The first run of 'flutter widget-preview start' should generate a new preview scaffold
-        await runWidgetPreview(tempDir: tempDir, expectedMessages: firstLaunchMessagesWeb);
+    testWithoutContext('does not recreate project on subsequent runs', () async {
+      // The first run of 'flutter widget-preview start' should generate a new preview scaffold
+      await runWidgetPreview(tempDir: tempDir, expectedMessages: firstLaunchMessagesWeb);
 
-        // We shouldn't regenerate the scaffold after the initial run.
-        await runWidgetPreview(tempDir: tempDir, expectedMessages: subsequentLaunchMessagesWeb);
-      },
-      // Project is always regenerated.
-      skip: true, // See https://github.com/flutter/flutter/issues/179036.
-    );
+      // We shouldn't regenerate the scaffold after the initial run.
+      await runWidgetPreview(tempDir: tempDir, expectedMessages: subsequentLaunchMessagesWeb);
+    });
 
     testUsingContext('can connect to an existing DTD instance', () async {
       dtdLauncher = DtdLauncher(
diff --git a/packages/flutter_tools/test/integration.shard/widget_preview_test_helpers.dart b/packages/flutter_tools/test/integration.shard/widget_preview_test_helpers.dart
index 9f505a7..4b9747f 100644
--- a/packages/flutter_tools/test/integration.shard/widget_preview_test_helpers.dart
+++ b/packages/flutter_tools/test/integration.shard/widget_preview_test_helpers.dart
@@ -5,6 +5,7 @@
 import 'dart:async';
 import 'dart:convert';
 
+import 'package:dtd/dtd.dart';
 import 'package:file/file.dart';
 import 'package:flutter_tools/src/base/io.dart';
 import 'package:flutter_tools/src/commands/widget_preview.dart';
@@ -60,8 +61,17 @@
   ], workingDirectory: tempDir.path);
 
   addTearDown(() async {
-    process.kill();
-    await process.exitCode;
+    if (platform.isWindows) {
+      try {
+        processManager.runSync(<String>['taskkill', '/F', '/T', '/PID', '${process.pid}']);
+      } on Object catch (_) {
+        process.kill();
+      }
+      await process.exitCode;
+    } else {
+      process.kill();
+      await process.exitCode;
+    }
   });
 
   final controller = StreamController<String>.broadcast();
@@ -141,3 +151,12 @@
 void runFlutterClean(Directory tempDir, [ProcessManager processManager = _processManager]) {
   processManager.runSync(<String>[flutterBin, 'clean'], workingDirectory: tempDir.path);
 }
+
+Future<DTDResponse> getPreviews(DartToolingDaemon dtdConnection) async {
+  if (platform.isWindows) {
+    // Give the slow Windows filesystem and analysis server plenty of time
+    // to finish subsequent analysis runs and rebuild the semantic model.
+    await Future<void>.delayed(const Duration(seconds: 2));
+  }
+  return dtdConnection.call('Lsp', 'dart/workspace/getFlutterWidgetPreviews');
+}